NS/main/source/detour/DetourTileCache.cpp
pierow 58358d0927
Bot integration for v3.3b8 (#156)
* Initial bot commit

* Added server commands and cvars for adding AI players to the game.
* Added auto modes for automating the adding and removal of bots
* Bots connect to the server and join teams correctly

* Added round restart and new map detection for AI system

Push before new project added for detour

* Initial bot integration

* Integrated all basic bot code for navigation and task performing
* Added support for multi_managers to better understand how buttons and triggers affect doors

* Improved bot understanding of door triggers and weldables

* Reworked nav profiles

Nav profiles for bots are now dynamically updated to take into account changing capabilities, such as picking up a welder

* Improved bot door usage

* Added weldable obstacles back into navigation

Bots now understand how to get around weldable barriers

* Replaced fixed arrays with vectors

* Resource node and hive lists are now vectors.
* Further improved bot weld behaviour

* Added dynamic reachability calculations

When barriers and doors are open/closed, new reachability calculations are done for structures and items so bots understand when items/structures become reachable or unreachable as the match progresses.

* Added team-based reachability calculations

Reachabilities for structures and items are now based on the team, so bots understand when they can't reach a structure from their spawn point.

* Implemented long-range off-mesh connections and dynamic off-mesh connections

* Implemented fully dynamic off-mesh connections

Phase gates now use connections rather than custom path finding. Much more performant.

* Replaced arrays with vectors for simpler code

* Started Bot Swimming

* Bots understand trigger_changetarget

Bots can now navigate doors operated with a trigger_changetarget so they understand the sequence in which triggers must be activated to make it work

* Push before trying to fix long-range connections

* Implement new off-mesh connection system

* Redid population of door triggers

* Fixed trigger types and links to doors

* Added lift and moving platform support

* Lift improvements

* Bots avoid getting crushed under a lift when summoning it
* Bots are better at judging which stop a platform needs to be at

* Tweak lift and welder usage

* Fixed bug with multiple off-mesh connections close together

* Finish lift movement

* Fixed dodgy path finding

* Improved skulk ladder usage and lerk lift usage

* Fix crash with path finding

* Re-implement commander AI

* Commander improvements

* Improve commander sieging

* Commander scanning tweak

* Reimplemented regular marine AI

* Start reimplementing alien AI

* Implement gorge building behaviours

* Start alien tactical decisioning

* Continuing alien building and other non-combat logic

* More alien role work

* Adjusted base node definitions

* Iterate Capper Logic

* Alien assault AI

* Alien Combat

* Fix grenade throwing, better combat

* Marine combat AI improvements

* Commander improvements

* Commander + nav improvements

* Drop mines

* Improved bot stuck detection

* Commander supply improvements

* Bot fill timing config

* Added nsbots.cfg to configure internal bots

* Changed bot config file to "nsbots.cfg"

* Bug fixing with navigation

* Fix skulk movement on ladders

* Improved commander placement and tactical refresh

* Fixed bug with ladder climbing

* Doors block off-mesh connections

* Finished doors blocking connections

* Marine and alien tactical bug fixes

* Add commander beacon back in

* Start combat mode stuff

* First pass at combat mode

* Bots attack turrets

* Fix ladder and wall climbing

* Commander chat request

* Improved skulk ladders

* Added nav meshes for new bot code

* Added bot configuration to listen server menu

* Added bot config file

* Added default bot config to listenserver.cfg

* Added default bot settings to server.cfg

* Include VS filter for bot files

* Crash fixes

* Bot improvements

* Bot stability and mine placement improvements

* Fixed crash on new map start with bots

* Reverted Svencoop fix

* Fixed crash, added more cvars

* Performance improvement

* Commander building improvements

* Stop bot spasming when waiting to take command

* Fixed doors not blocking connections

* Added bot disabled guard to round start

* Commander improvements, movement improvements

* Tweaked level load sequence

* Performance improvements

* Bot load spread

* Fixed commander update

* Refactor bot frame handling

* Bug fixes + Pierow's dynamic load spread

* Minor bug fixes

* Fix door detection, prep for test

* Fixed commander siege spam

* linux compile test

* fix hardcoded inlcudes

* O1 compile flag for detour
- fix linux server crash

* Revert detour compile flags to original for windows

* linux build update

* remove x64 build configs

* update bot nav meshes and configs

* fix bot physics at high server fps, update navmeshes. from @RGreenlees

---------

Co-authored-by: RGreenlees <RGreenlees@users.noreply.github.com>
Co-authored-by: RichardGreenlees <richard.greenlees@forecast.global>
2024-03-21 14:17:18 -04:00

1065 lines
28 KiB
C++

#include "DetourTileCache.h"
#include "DetourTileCacheBuilder.h"
#include "DetourNavMeshBuilder.h"
#include "DetourNavMesh.h"
#include "DetourCommon.h"
#include "DetourMath.h"
#include "DetourAlloc.h"
#include "DetourAssert.h"
#include <string.h>
#include <new>
dtTileCache* dtAllocTileCache()
{
void* mem = dtAlloc(sizeof(dtTileCache), DT_ALLOC_PERM);
if (!mem) return 0;
return new(mem) dtTileCache;
}
void dtFreeTileCache(dtTileCache* tc)
{
if (!tc) return;
tc->~dtTileCache();
dtFree(tc);
}
static bool contains(const dtCompressedTileRef* a, const int n, const dtCompressedTileRef v)
{
for (int i = 0; i < n; ++i)
if (a[i] == v)
return true;
return false;
}
inline int computeTileHash(int x, int y, const int mask)
{
const unsigned int h1 = 0x8da6b343; // Large multiplicative constants;
const unsigned int h2 = 0xd8163841; // here arbitrarily chosen primes
unsigned int n = h1 * x + h2 * y;
return (int)(n & mask);
}
struct NavMeshTileBuildContext
{
inline NavMeshTileBuildContext(struct dtTileCacheAlloc* a) : layer(0), lcset(0), lmesh(0), alloc(a) {}
inline ~NavMeshTileBuildContext() { purge(); }
void purge()
{
dtFreeTileCacheLayer(alloc, layer);
layer = 0;
dtFreeTileCacheContourSet(alloc, lcset);
lcset = 0;
dtFreeTileCachePolyMesh(alloc, lmesh);
lmesh = 0;
}
struct dtTileCacheLayer* layer;
struct dtTileCacheContourSet* lcset;
struct dtTileCachePolyMesh* lmesh;
struct dtTileCacheAlloc* alloc;
};
dtTileCache::dtTileCache() :
m_tileLutSize(0),
m_tileLutMask(0),
m_posLookup(0),
m_nextFreeTile(0),
m_tiles(0),
m_saltBits(0),
m_tileBits(0),
m_talloc(0),
m_tcomp(0),
m_tmproc(0),
m_obstacles(0),
m_nextFreeObstacle(0),
m_nreqs(0),
m_nOffMeshReqs(0),
m_nupdate(0)
{
memset(&m_params, 0, sizeof(m_params));
memset(m_reqs, 0, sizeof(ObstacleRequest) * MAX_REQUESTS);
memset(m_OffMeshReqs, 0, sizeof(OffMeshRequest) * MAX_REQUESTS);
}
dtTileCache::~dtTileCache()
{
for (int i = 0; i < m_params.maxTiles; ++i)
{
if (m_tiles[i].flags & DT_COMPRESSEDTILE_FREE_DATA)
{
dtFree(m_tiles[i].data);
m_tiles[i].data = 0;
}
}
dtFree(m_obstacles);
m_obstacles = 0;
dtFree(m_offMeshConnections);
m_offMeshConnections = 0;
dtFree(m_posLookup);
m_posLookup = 0;
dtFree(m_tiles);
m_tiles = 0;
m_nreqs = 0;
m_nOffMeshReqs = 0;
m_nupdate = 0;
}
const dtCompressedTile* dtTileCache::getTileByRef(dtCompressedTileRef ref) const
{
if (!ref)
return 0;
unsigned int tileIndex = decodeTileIdTile(ref);
unsigned int tileSalt = decodeTileIdSalt(ref);
if ((int)tileIndex >= m_params.maxTiles)
return 0;
const dtCompressedTile* tile = &m_tiles[tileIndex];
if (tile->salt != tileSalt)
return 0;
return tile;
}
dtStatus dtTileCache::init(const dtTileCacheParams* params,
dtTileCacheAlloc* talloc,
dtTileCacheCompressor* tcomp,
dtTileCacheMeshProcess* tmproc)
{
m_talloc = talloc;
m_tcomp = tcomp;
m_tmproc = tmproc;
m_nreqs = 0;
memcpy(&m_params, params, sizeof(m_params));
// Alloc space for obstacles.
m_obstacles = (dtTileCacheObstacle*)dtAlloc(sizeof(dtTileCacheObstacle)*m_params.maxObstacles, DT_ALLOC_PERM);
if (!m_obstacles)
return DT_FAILURE | DT_OUT_OF_MEMORY;
memset(m_obstacles, 0, sizeof(dtTileCacheObstacle)*m_params.maxObstacles);
m_nextFreeObstacle = 0;
for (int i = m_params.maxObstacles-1; i >= 0; --i)
{
m_obstacles[i].salt = 1;
m_obstacles[i].next = m_nextFreeObstacle;
m_nextFreeObstacle = &m_obstacles[i];
}
// Alloc space for off-mesh connections.
m_offMeshConnections = (dtOffMeshConnection*)dtAlloc(sizeof(dtOffMeshConnection) * m_params.maxOffMeshConnections, DT_ALLOC_PERM);
if (!m_offMeshConnections)
return DT_FAILURE | DT_OUT_OF_MEMORY;
memset(m_offMeshConnections, 0, sizeof(dtOffMeshConnection) * m_params.maxOffMeshConnections);
m_nextFreeOffMeshConnection = 0;
for (int i = m_params.maxOffMeshConnections - 1; i >= 0; --i)
{
m_offMeshConnections[i].salt = 1;
m_offMeshConnections[i].next = m_nextFreeOffMeshConnection;
m_offMeshConnections[i].userId = i;
m_nextFreeOffMeshConnection = &m_offMeshConnections[i];
}
// Init tiles
m_tileLutSize = dtNextPow2(m_params.maxTiles/4);
if (!m_tileLutSize) m_tileLutSize = 1;
m_tileLutMask = m_tileLutSize-1;
m_tiles = (dtCompressedTile*)dtAlloc(sizeof(dtCompressedTile)*m_params.maxTiles, DT_ALLOC_PERM);
if (!m_tiles)
return DT_FAILURE | DT_OUT_OF_MEMORY;
m_posLookup = (dtCompressedTile**)dtAlloc(sizeof(dtCompressedTile*)*m_tileLutSize, DT_ALLOC_PERM);
if (!m_posLookup)
return DT_FAILURE | DT_OUT_OF_MEMORY;
memset(m_tiles, 0, sizeof(dtCompressedTile)*m_params.maxTiles);
memset(m_posLookup, 0, sizeof(dtCompressedTile*)*m_tileLutSize);
m_nextFreeTile = 0;
for (int i = m_params.maxTiles-1; i >= 0; --i)
{
m_tiles[i].salt = 1;
m_tiles[i].next = m_nextFreeTile;
m_nextFreeTile = &m_tiles[i];
}
// Init ID generator values.
m_tileBits = dtIlog2(dtNextPow2((unsigned int)m_params.maxTiles));
// Only allow 31 salt bits, since the salt mask is calculated using 32bit uint and it will overflow.
m_saltBits = dtMin((unsigned int)31, 32 - m_tileBits);
if (m_saltBits < 10)
return DT_FAILURE | DT_INVALID_PARAM;
return DT_SUCCESS;
}
int dtTileCache::getTilesAt(const int tx, const int ty, dtCompressedTileRef* tiles, const int maxTiles) const
{
int n = 0;
// Find tile based on hash.
int h = computeTileHash(tx,ty,m_tileLutMask);
dtCompressedTile* tile = m_posLookup[h];
while (tile)
{
if (tile->header &&
tile->header->tx == tx &&
tile->header->ty == ty)
{
if (n < maxTiles)
tiles[n++] = getTileRef(tile);
}
tile = tile->next;
}
return n;
}
dtCompressedTile* dtTileCache::getTileAt(const int tx, const int ty, const int tlayer)
{
// Find tile based on hash.
int h = computeTileHash(tx,ty,m_tileLutMask);
dtCompressedTile* tile = m_posLookup[h];
while (tile)
{
if (tile->header &&
tile->header->tx == tx &&
tile->header->ty == ty &&
tile->header->tlayer == tlayer)
{
return tile;
}
tile = tile->next;
}
return 0;
}
dtCompressedTileRef dtTileCache::getTileRef(const dtCompressedTile* tile) const
{
if (!tile) return 0;
const unsigned int it = (unsigned int)(tile - m_tiles);
return (dtCompressedTileRef)encodeTileId(tile->salt, it);
}
dtObstacleRef dtTileCache::getObstacleRef(const dtTileCacheObstacle* ob) const
{
if (!ob) return 0;
const unsigned int idx = (unsigned int)(ob - m_obstacles);
return encodeObstacleId(ob->salt, idx);
}
dtOffMeshConnectionRef dtTileCache::getOffMeshRef(const dtOffMeshConnection* con) const
{
if (!con) return 0;
const unsigned int idx = (unsigned int)(con - m_offMeshConnections);
return encodeObstacleId(con->salt, idx);
}
const dtTileCacheObstacle* dtTileCache::getObstacleByRef(dtObstacleRef ref)
{
if (!ref)
return 0;
unsigned int idx = decodeObstacleIdObstacle(ref);
if ((int)idx >= m_params.maxObstacles)
return 0;
const dtTileCacheObstacle* ob = &m_obstacles[idx];
unsigned int salt = decodeObstacleIdSalt(ref);
if (ob->salt != salt)
return 0;
return ob;
}
dtOffMeshConnection* dtTileCache::getOffMeshConnectionByRef(dtOffMeshConnectionRef ref)
{
if (!ref)
return 0;
unsigned int idx = decodeOffMeshIdCon(ref);
if ((int)idx >= m_params.maxOffMeshConnections)
return 0;
dtOffMeshConnection* con = &m_offMeshConnections[idx];
unsigned int salt = decodeOffMeshIdSalt(ref);
if (con->salt != salt)
return 0;
return con;
}
dtStatus dtTileCache::addTile(unsigned char* data, const int dataSize, unsigned char flags, dtCompressedTileRef* result)
{
// Make sure the data is in right format.
dtTileCacheLayerHeader* header = (dtTileCacheLayerHeader*)data;
if (header->magic != DT_TILECACHE_MAGIC)
return DT_FAILURE | DT_WRONG_MAGIC;
if (header->version != DT_TILECACHE_VERSION)
return DT_FAILURE | DT_WRONG_VERSION;
// Make sure the location is free.
if (getTileAt(header->tx, header->ty, header->tlayer))
return DT_FAILURE;
// Allocate a tile.
dtCompressedTile* tile = 0;
if (m_nextFreeTile)
{
tile = m_nextFreeTile;
m_nextFreeTile = tile->next;
tile->next = 0;
}
// Make sure we could allocate a tile.
if (!tile)
return DT_FAILURE | DT_OUT_OF_MEMORY;
// Insert tile into the position lut.
int h = computeTileHash(header->tx, header->ty, m_tileLutMask);
tile->next = m_posLookup[h];
m_posLookup[h] = tile;
// Init tile.
const int headerSize = dtAlign4(sizeof(dtTileCacheLayerHeader));
tile->header = (dtTileCacheLayerHeader*)data;
tile->data = data;
tile->dataSize = dataSize;
tile->compressed = tile->data + headerSize;
tile->compressedSize = tile->dataSize - headerSize;
tile->flags = flags;
if (result)
*result = getTileRef(tile);
return DT_SUCCESS;
}
dtStatus dtTileCache::removeTile(dtCompressedTileRef ref, unsigned char** data, int* dataSize)
{
if (!ref)
return DT_FAILURE | DT_INVALID_PARAM;
unsigned int tileIndex = decodeTileIdTile(ref);
unsigned int tileSalt = decodeTileIdSalt(ref);
if ((int)tileIndex >= m_params.maxTiles)
return DT_FAILURE | DT_INVALID_PARAM;
dtCompressedTile* tile = &m_tiles[tileIndex];
if (tile->salt != tileSalt)
return DT_FAILURE | DT_INVALID_PARAM;
// Remove tile from hash lookup.
const int h = computeTileHash(tile->header->tx,tile->header->ty,m_tileLutMask);
dtCompressedTile* prev = 0;
dtCompressedTile* cur = m_posLookup[h];
while (cur)
{
if (cur == tile)
{
if (prev)
prev->next = cur->next;
else
m_posLookup[h] = cur->next;
break;
}
prev = cur;
cur = cur->next;
}
// Reset tile.
if (tile->flags & DT_COMPRESSEDTILE_FREE_DATA)
{
// Owns data
dtFree(tile->data);
tile->data = 0;
tile->dataSize = 0;
if (data) *data = 0;
if (dataSize) *dataSize = 0;
}
else
{
if (data) *data = tile->data;
if (dataSize) *dataSize = tile->dataSize;
}
tile->header = 0;
tile->data = 0;
tile->dataSize = 0;
tile->compressed = 0;
tile->compressedSize = 0;
tile->flags = 0;
// Update salt, salt should never be zero.
tile->salt = (tile->salt+1) & ((1<<m_saltBits)-1);
if (tile->salt == 0)
tile->salt++;
// Add to free list.
tile->next = m_nextFreeTile;
m_nextFreeTile = tile;
return DT_SUCCESS;
}
dtStatus dtTileCache::modifyOffMeshConnection(dtOffMeshConnectionRef ConRef, const unsigned int newFlag)
{
if (m_nOffMeshReqs >= MAX_REQUESTS)
return DT_FAILURE | DT_BUFFER_TOO_SMALL;
dtOffMeshConnection* con = getOffMeshConnectionByRef(ConRef);
if (!con) { return DT_FAILURE; }
con->state = DT_OFFMESH_DIRTY;
con->flags = newFlag;
OffMeshRequest* req = &m_OffMeshReqs[m_nOffMeshReqs++];
memset(req, 0, sizeof(OffMeshRequest));
req->action = REQUEST_OFFMESH_REFRESH;
req->ref = ConRef;
return DT_SUCCESS;
}
dtStatus dtTileCache::addOffMeshConnection(const float* spos, const float* epos, const float radius, const unsigned char area, const unsigned int flags, const bool bBiDirectional, dtOffMeshConnectionRef* result)
{
if (m_nOffMeshReqs >= MAX_REQUESTS)
return DT_FAILURE | DT_BUFFER_TOO_SMALL;
dtOffMeshConnection* con = 0;
if (m_nextFreeOffMeshConnection)
{
con = m_nextFreeOffMeshConnection;
m_nextFreeOffMeshConnection = con->next;
con->next = 0;
}
if (!con)
return DT_FAILURE | DT_OUT_OF_MEMORY;
unsigned short salt = con->salt;
unsigned int userId = con->userId;
memset(con, 0, sizeof(dtOffMeshConnection));
con->userId = userId;
con->salt = salt;
con->state = DT_OFFMESH_NEW;
dtVcopy(&con->pos[0], spos);
dtVcopy(&con->pos[3], epos);
con->rad = radius;
con->area = area;
con->flags = flags;
con->bBiDir = bBiDirectional;
OffMeshRequest* req = &m_OffMeshReqs[m_nOffMeshReqs++];
memset(req, 0, sizeof(OffMeshRequest));
req->action = REQUEST_OFFMESH_ADD;
req->ref = getOffMeshRef(con);
if (result)
*result = req->ref;
return DT_SUCCESS;
}
dtStatus dtTileCache::addObstacle(const float* pos, const float radius, const float height, const int area, dtObstacleRef* result)
{
if (m_nreqs >= MAX_REQUESTS)
return DT_FAILURE | DT_BUFFER_TOO_SMALL;
dtTileCacheObstacle* ob = 0;
if (m_nextFreeObstacle)
{
ob = m_nextFreeObstacle;
m_nextFreeObstacle = ob->next;
ob->next = 0;
}
if (!ob)
return DT_FAILURE | DT_OUT_OF_MEMORY;
unsigned short salt = ob->salt;
memset(ob, 0, sizeof(dtTileCacheObstacle));
ob->salt = salt;
ob->state = DT_OBSTACLE_PROCESSING;
ob->type = DT_OBSTACLE_CYLINDER;
dtVcopy(ob->cylinder.pos, pos);
ob->cylinder.radius = radius;
ob->cylinder.height = height;
ob->cylinder.area = area;
ObstacleRequest* req = &m_reqs[m_nreqs++];
memset(req, 0, sizeof(ObstacleRequest));
req->action = REQUEST_ADD;
req->ref = getObstacleRef(ob);
if (result)
*result = req->ref;
return DT_SUCCESS;
}
dtStatus dtTileCache::addBoxObstacle(const float* bmin, const float* bmax, dtObstacleRef* result)
{
if (m_nreqs >= MAX_REQUESTS)
return DT_FAILURE | DT_BUFFER_TOO_SMALL;
dtTileCacheObstacle* ob = 0;
if (m_nextFreeObstacle)
{
ob = m_nextFreeObstacle;
m_nextFreeObstacle = ob->next;
ob->next = 0;
}
if (!ob)
return DT_FAILURE | DT_OUT_OF_MEMORY;
unsigned short salt = ob->salt;
memset(ob, 0, sizeof(dtTileCacheObstacle));
ob->salt = salt;
ob->state = DT_OBSTACLE_PROCESSING;
ob->type = DT_OBSTACLE_BOX;
dtVcopy(ob->box.bmin, bmin);
dtVcopy(ob->box.bmax, bmax);
ObstacleRequest* req = &m_reqs[m_nreqs++];
memset(req, 0, sizeof(ObstacleRequest));
req->action = REQUEST_ADD;
req->ref = getObstacleRef(ob);
if (result)
*result = req->ref;
return DT_SUCCESS;
}
dtStatus dtTileCache::addBoxObstacle(const float* center, const float* halfExtents, const float yRadians, dtObstacleRef* result)
{
if (m_nreqs >= MAX_REQUESTS)
return DT_FAILURE | DT_BUFFER_TOO_SMALL;
dtTileCacheObstacle* ob = 0;
if (m_nextFreeObstacle)
{
ob = m_nextFreeObstacle;
m_nextFreeObstacle = ob->next;
ob->next = 0;
}
if (!ob)
return DT_FAILURE | DT_OUT_OF_MEMORY;
unsigned short salt = ob->salt;
memset(ob, 0, sizeof(dtTileCacheObstacle));
ob->salt = salt;
ob->state = DT_OBSTACLE_PROCESSING;
ob->type = DT_OBSTACLE_ORIENTED_BOX;
dtVcopy(ob->orientedBox.center, center);
dtVcopy(ob->orientedBox.halfExtents, halfExtents);
float coshalf= cosf(0.5f*yRadians);
float sinhalf = sinf(-0.5f*yRadians);
ob->orientedBox.rotAux[0] = coshalf*sinhalf;
ob->orientedBox.rotAux[1] = coshalf*coshalf - 0.5f;
ObstacleRequest* req = &m_reqs[m_nreqs++];
memset(req, 0, sizeof(ObstacleRequest));
req->action = REQUEST_ADD;
req->ref = getObstacleRef(ob);
if (result)
*result = req->ref;
return DT_SUCCESS;
}
dtStatus dtTileCache::removeObstacle(const dtObstacleRef ref)
{
if (!ref)
return DT_SUCCESS;
if (m_nreqs >= MAX_REQUESTS)
return DT_FAILURE | DT_BUFFER_TOO_SMALL;
ObstacleRequest* req = &m_reqs[m_nreqs++];
memset(req, 0, sizeof(ObstacleRequest));
req->action = REQUEST_REMOVE;
req->ref = ref;
return DT_SUCCESS;
}
dtStatus dtTileCache::removeOffMeshConnection(const dtOffMeshConnectionRef ref)
{
if (!ref)
return DT_SUCCESS;
if (m_nOffMeshReqs >= MAX_REQUESTS)
return DT_FAILURE | DT_BUFFER_TOO_SMALL;
OffMeshRequest* req = &m_OffMeshReqs[m_nOffMeshReqs++];
memset(req, 0, sizeof(OffMeshRequest));
req->action = REQUEST_OFFMESH_REMOVE;
req->ref = ref;
return DT_SUCCESS;
}
dtStatus dtTileCache::queryTiles(const float* bmin, const float* bmax,
dtCompressedTileRef* results, int* resultCount, const int maxResults) const
{
const int MAX_TILES = 32;
dtCompressedTileRef tiles[MAX_TILES];
int n = 0;
const float tw = m_params.width * m_params.cs;
const float th = m_params.height * m_params.cs;
const int tx0 = (int)dtMathFloorf((bmin[0]-m_params.orig[0]) / tw);
const int tx1 = (int)dtMathFloorf((bmax[0]-m_params.orig[0]) / tw);
const int ty0 = (int)dtMathFloorf((bmin[2]-m_params.orig[2]) / th);
const int ty1 = (int)dtMathFloorf((bmax[2]-m_params.orig[2]) / th);
for (int ty = ty0; ty <= ty1; ++ty)
{
for (int tx = tx0; tx <= tx1; ++tx)
{
const int ntiles = getTilesAt(tx,ty,tiles,MAX_TILES);
for (int i = 0; i < ntiles; ++i)
{
const dtCompressedTile* tile = &m_tiles[decodeTileIdTile(tiles[i])];
float tbmin[3], tbmax[3];
calcTightTileBounds(tile->header, tbmin, tbmax);
if (dtOverlapBounds(bmin,bmax, tbmin,tbmax))
{
if (n < maxResults)
results[n++] = tiles[i];
}
}
}
}
*resultCount = n;
return DT_SUCCESS;
}
dtStatus dtTileCache::update(const float /*dt*/, dtNavMesh* navmesh,
bool* upToDate)
{
if (m_nupdate == 0)
{
// Process requests.
for (int i = 0; i < m_nreqs; ++i)
{
ObstacleRequest* req = &m_reqs[i];
unsigned int idx = decodeObstacleIdObstacle(req->ref);
if ((int)idx >= m_params.maxObstacles)
continue;
dtTileCacheObstacle* ob = &m_obstacles[idx];
unsigned int salt = decodeObstacleIdSalt(req->ref);
if (ob->salt != salt)
continue;
if (req->action == REQUEST_ADD)
{
// Find touched tiles.
float bmin[3], bmax[3];
getObstacleBounds(ob, bmin, bmax);
int ntouched = 0;
queryTiles(bmin, bmax, ob->touched, &ntouched, DT_MAX_TOUCHED_TILES);
ob->ntouched = (unsigned char)ntouched;
// Add tiles to update list.
ob->npending = 0;
for (int j = 0; j < ob->ntouched; ++j)
{
if (m_nupdate < MAX_UPDATE)
{
if (!contains(m_update, m_nupdate, ob->touched[j]))
m_update[m_nupdate++] = ob->touched[j];
ob->pending[ob->npending++] = ob->touched[j];
}
}
}
else if (req->action == REQUEST_REMOVE)
{
// Prepare to remove obstacle.
ob->state = DT_OBSTACLE_REMOVING;
// Add tiles to update list.
ob->npending = 0;
for (int j = 0; j < ob->ntouched; ++j)
{
if (m_nupdate < MAX_UPDATE)
{
if (!contains(m_update, m_nupdate, ob->touched[j]))
m_update[m_nupdate++] = ob->touched[j];
ob->pending[ob->npending++] = ob->touched[j];
}
}
}
}
m_nreqs = 0;
for (int i = 0; i < m_nOffMeshReqs; ++i)
{
OffMeshRequest* req = &m_OffMeshReqs[i];
unsigned int idx = decodeOffMeshIdCon(req->ref);
if ((int)idx >= m_params.maxOffMeshConnections)
continue;
dtOffMeshConnection* con = &m_offMeshConnections[idx];
con->userId = idx;
unsigned int salt = decodeOffMeshIdSalt(req->ref);
if (con->salt != salt)
continue;
if (req->action == REQUEST_OFFMESH_ADD)
{
con->state = DT_OFFMESH_DIRTY;
// Find touched tiles.
float bmin[3], bmax[3];
float ext[3] = { con->rad, 18.0f, con->rad };
dtVsub(bmin, &con->pos[0], ext);
dtVadd(bmax, &con->pos[0], ext);
int ntouched = 0;
dtCompressedTileRef touched[DT_MAX_TOUCHED_TILES];
dtCompressedTileRef StartTileRef = 0;
dtCompressedTileRef EndTileRef = 0;
queryTiles(bmin, bmax, touched, &ntouched, DT_MAX_TOUCHED_TILES);
if (ntouched > 0)
{
StartTileRef = touched[0];
}
dtVsub(bmin, &con->pos[3], ext);
dtVadd(bmax, &con->pos[3], ext);
queryTiles(bmin, bmax, touched, &ntouched, DT_MAX_TOUCHED_TILES);
if (ntouched > 0)
{
EndTileRef = touched[0];
}
if (!StartTileRef || !EndTileRef) { continue; }
const dtCompressedTile* StartTile = getTileByRef(StartTileRef);
const dtCompressedTile* EndTile = getTileByRef(EndTileRef);
con->FromTileX = StartTile->header->tx;
con->FromTileY = StartTile->header->ty;
con->FromTileLayer = StartTile->header->tlayer;
con->ToTileX = EndTile->header->tx;
con->ToTileY = EndTile->header->ty;
con->ToTileLayer = EndTile->header->tlayer;
if (m_nupdate < MAX_UPDATE)
{
if (!contains(m_update, m_nupdate, StartTileRef))
m_update[m_nupdate++] = StartTileRef;
}
}
else if (req->action == REQUEST_OFFMESH_REMOVE)
{
// Prepare to remove obstacle.
con->state = DT_OFFMESH_REMOVING;
// Add tiles to update list.
navmesh->unconnectOffMeshLink(con);
if (m_nupdate < MAX_UPDATE)
{
dtCompressedTile* Tile = getTileAt(con->FromTileX, con->FromTileY, con->FromTileLayer);
dtCompressedTileRef TileRef = getTileRef(Tile);
if (!contains(m_update, m_nupdate, TileRef))
m_update[m_nupdate++] = TileRef;
}
}
else if (req->action == REQUEST_OFFMESH_REFRESH)
{
con->state = DT_OFFMESH_DIRTY;
navmesh->unconnectOffMeshLink(con);
if (m_nupdate < MAX_UPDATE)
{
dtCompressedTile* Tile = getTileAt(con->FromTileX, con->FromTileY, con->FromTileLayer);
dtCompressedTileRef TileRef = getTileRef(Tile);
if (!contains(m_update, m_nupdate, TileRef))
m_update[m_nupdate++] = TileRef;
}
}
}
m_nOffMeshReqs = 0;
}
dtStatus status = DT_SUCCESS;
// Process updates
if (m_nupdate)
{
const dtCompressedTileRef ref = m_update[0];
status = buildNavMeshTile(ref, navmesh);
m_nupdate--;
if (m_nupdate > 0)
memmove(m_update, m_update + 1, m_nupdate * sizeof(dtCompressedTileRef));
// Update obstacle states.
for (int i = 0; i < m_params.maxObstacles; ++i)
{
dtTileCacheObstacle* ob = &m_obstacles[i];
if (ob->state == DT_OBSTACLE_PROCESSING || ob->state == DT_OBSTACLE_REMOVING)
{
// Remove handled tile from pending list.
for (int j = 0; j < (int)ob->npending; j++)
{
if (ob->pending[j] == ref)
{
ob->pending[j] = ob->pending[(int)ob->npending - 1];
ob->npending--;
break;
}
}
// If all pending tiles processed, change state.
if (ob->npending == 0)
{
if (ob->state == DT_OBSTACLE_PROCESSING)
{
ob->state = DT_OBSTACLE_PROCESSED;
}
else if (ob->state == DT_OBSTACLE_REMOVING)
{
ob->state = DT_OBSTACLE_EMPTY;
// Update salt, salt should never be zero.
ob->salt = (ob->salt + 1) & ((1 << 16) - 1);
if (ob->salt == 0)
ob->salt++;
// Return obstacle to free list.
ob->next = m_nextFreeObstacle;
m_nextFreeObstacle = ob;
}
}
}
}
}
if (m_nupdate == 0)
{
for (int i = 0; i < m_params.maxOffMeshConnections; ++i)
{
dtOffMeshConnection* con = &m_offMeshConnections[i];
if (con->state == DT_OFFMESH_DIRTY)
{
navmesh->unconnectOffMeshLink(con);
navmesh->baseOffMeshLinks(con);
navmesh->GlobalOffMeshLinks(con);
con->state = DT_OFFMESH_CLEAN;
}
if (con->state == DT_OFFMESH_REMOVING)
{
con->state = DT_OFFMESH_EMPTY;
con->salt = (con->salt + 1) & ((1 << 16) - 1);
if (con->salt == 0)
con->salt++;
con->next = m_nextFreeOffMeshConnection;
m_nextFreeOffMeshConnection = con;
}
}
}
if (upToDate)
*upToDate = m_nupdate == 0 && m_nreqs == 0 && m_nOffMeshReqs == 0;
return status;
}
dtStatus dtTileCache::buildNavMeshTilesAt(const int tx, const int ty, dtNavMesh* navmesh)
{
const int MAX_TILES = 32;
dtCompressedTileRef tiles[MAX_TILES];
const int ntiles = getTilesAt(tx,ty,tiles,MAX_TILES);
for (int i = 0; i < ntiles; ++i)
{
dtStatus status = buildNavMeshTile(tiles[i], navmesh);
if (dtStatusFailed(status))
return status;
}
return DT_SUCCESS;
}
dtStatus dtTileCache::buildNavMeshTile(const dtCompressedTileRef ref, dtNavMesh* navmesh)
{
dtAssert(m_talloc);
dtAssert(m_tcomp);
unsigned int idx = decodeTileIdTile(ref);
if (idx > (unsigned int)m_params.maxTiles)
return DT_FAILURE | DT_INVALID_PARAM;
const dtCompressedTile* tile = &m_tiles[idx];
unsigned int salt = decodeTileIdSalt(ref);
if (tile->salt != salt)
return DT_FAILURE | DT_INVALID_PARAM;
m_talloc->reset();
NavMeshTileBuildContext bc(m_talloc);
const int walkableClimbVx = (int)(m_params.walkableClimb / m_params.ch);
dtStatus status;
// Decompress tile layer data.
status = dtDecompressTileCacheLayer(m_talloc, m_tcomp, tile->data, tile->dataSize, &bc.layer);
if (dtStatusFailed(status))
return status;
// Rasterize obstacles.
for (int i = 0; i < m_params.maxObstacles; ++i)
{
const dtTileCacheObstacle* ob = &m_obstacles[i];
if (ob->state == DT_OBSTACLE_EMPTY || ob->state == DT_OBSTACLE_REMOVING)
continue;
if (contains(ob->touched, ob->ntouched, ref))
{
if (ob->type == DT_OBSTACLE_CYLINDER)
{
dtMarkCylinderArea(*bc.layer, tile->header->bmin, m_params.cs, m_params.ch,
ob->cylinder.pos, ob->cylinder.radius, ob->cylinder.height, ob->cylinder.area);
}
else if (ob->type == DT_OBSTACLE_BOX)
{
dtMarkBoxArea(*bc.layer, tile->header->bmin, m_params.cs, m_params.ch,
ob->box.bmin, ob->box.bmax, ob->box.area);
}
else if (ob->type == DT_OBSTACLE_ORIENTED_BOX)
{
dtMarkBoxArea(*bc.layer, tile->header->bmin, m_params.cs, m_params.ch,
ob->orientedBox.center, ob->orientedBox.halfExtents, ob->orientedBox.rotAux, ob->box.area);
}
}
}
// Build navmesh
status = dtBuildTileCacheRegions(m_talloc, *bc.layer, walkableClimbVx);
if (dtStatusFailed(status))
return status;
bc.lcset = dtAllocTileCacheContourSet(m_talloc);
if (!bc.lcset)
return DT_FAILURE | DT_OUT_OF_MEMORY;
status = dtBuildTileCacheContours(m_talloc, *bc.layer, walkableClimbVx,
m_params.maxSimplificationError, *bc.lcset);
if (dtStatusFailed(status))
return status;
bc.lmesh = dtAllocTileCachePolyMesh(m_talloc);
if (!bc.lmesh)
return DT_FAILURE | DT_OUT_OF_MEMORY;
status = dtBuildTileCachePolyMesh(m_talloc, *bc.lcset, *bc.lmesh);
if (dtStatusFailed(status))
return status;
// Early out if the mesh tile is empty.
if (!bc.lmesh->npolys)
{
// Remove existing tile.
navmesh->removeTile(navmesh->getTileRefAt(tile->header->tx,tile->header->ty,tile->header->tlayer),0,0);
return DT_SUCCESS;
}
dtNavMeshCreateParams params;
memset(&params, 0, sizeof(params));
params.verts = bc.lmesh->verts;
params.vertCount = bc.lmesh->nverts;
params.polys = bc.lmesh->polys;
params.polyAreas = bc.lmesh->areas;
params.polyFlags = bc.lmesh->flags;
params.polyCount = bc.lmesh->npolys;
params.nvp = DT_VERTS_PER_POLYGON;
params.walkableHeight = m_params.walkableHeight;
params.walkableRadius = m_params.walkableRadius;
params.walkableClimb = m_params.walkableClimb;
params.tileX = tile->header->tx;
params.tileY = tile->header->ty;
params.tileLayer = tile->header->tlayer;
params.cs = m_params.cs;
params.ch = m_params.ch;
params.buildBvTree = false;
dtVcopy(params.bmin, tile->header->bmin);
dtVcopy(params.bmax, tile->header->bmax);
if (m_tmproc)
{
m_tmproc->process(&params, bc.lmesh->areas, bc.lmesh->flags);
}
params.GlobalOffMeshConnections = m_offMeshConnections;
params.NumOffMeshConnections = getOffMeshCount();
unsigned char* navData = 0;
int navDataSize = 0;
if (!dtCreateNavMeshData(&params, &navData, &navDataSize))
return DT_FAILURE;
// Remove existing tile.
navmesh->removeTile(navmesh->getTileRefAt(tile->header->tx,tile->header->ty,tile->header->tlayer),0,0);
// Add new tile, or leave the location empty.
if (navData)
{
status = navmesh->addTile(navData,navDataSize,DT_TILE_FREE_DATA,0,0);
if (dtStatusFailed(status))
{
dtFree(navData);
return status;
}
}
return DT_SUCCESS;
}
void dtTileCache::calcTightTileBounds(const dtTileCacheLayerHeader* header, float* bmin, float* bmax) const
{
const float cs = m_params.cs;
bmin[0] = header->bmin[0] + header->minx*cs;
bmin[1] = header->bmin[1];
bmin[2] = header->bmin[2] + header->miny*cs;
bmax[0] = header->bmin[0] + (header->maxx+1)*cs;
bmax[1] = header->bmax[1];
bmax[2] = header->bmin[2] + (header->maxy+1)*cs;
}
void dtTileCache::getObstacleBounds(const struct dtTileCacheObstacle* ob, float* bmin, float* bmax) const
{
if (ob->type == DT_OBSTACLE_CYLINDER)
{
const dtObstacleCylinder &cl = ob->cylinder;
bmin[0] = cl.pos[0] - cl.radius;
bmin[1] = cl.pos[1];
bmin[2] = cl.pos[2] - cl.radius;
bmax[0] = cl.pos[0] + cl.radius;
bmax[1] = cl.pos[1] + cl.height;
bmax[2] = cl.pos[2] + cl.radius;
}
else if (ob->type == DT_OBSTACLE_BOX)
{
dtVcopy(bmin, ob->box.bmin);
dtVcopy(bmax, ob->box.bmax);
}
else if (ob->type == DT_OBSTACLE_ORIENTED_BOX)
{
const dtObstacleOrientedBox &orientedBox = ob->orientedBox;
float maxr = 1.41f*dtMax(orientedBox.halfExtents[0], orientedBox.halfExtents[2]);
bmin[0] = orientedBox.center[0] - maxr;
bmax[0] = orientedBox.center[0] + maxr;
bmin[1] = orientedBox.center[1] - orientedBox.halfExtents[1];
bmax[1] = orientedBox.center[1] + orientedBox.halfExtents[1];
bmin[2] = orientedBox.center[2] - maxr;
bmax[2] = orientedBox.center[2] + maxr;
}
}