740 lines
23 KiB
C++
740 lines
23 KiB
C++
#include "precompile.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include "Recast.h"
|
|
#include "RecastAlloc.h"
|
|
#include "DetourTileCache.h"
|
|
#include "DetourTileCacheBuilder.h"
|
|
|
|
#include "DetourNavMeshBuilder.h"
|
|
#include "DetourNavMeshQuery.h"
|
|
#include "DetourCommon.h"
|
|
#include "DetourNavMesh.h"
|
|
#include "fastlz.h"
|
|
|
|
#include "navmeshbuilder.h"
|
|
#include "mapinstance.h"
|
|
#include "collider.h"
|
|
#include "entity.h"
|
|
#include "metamgr.h"
|
|
|
|
static const float kCellHeight = 1;
|
|
|
|
static const float kAgentMaxSlope = 90;
|
|
static const float kAgentHeight = 1;
|
|
static const float kAgentMaxClimb = 1;
|
|
static const float kAgentRadius = 40;
|
|
|
|
static const float kEdgeMaxLen = 6;
|
|
static const float kEdgeMaxError = 6;
|
|
|
|
static const float kRegionMinSize = 6;
|
|
static const float kRegionMergeSize = 6;
|
|
|
|
static const int kVertsPerPoly = 1;
|
|
|
|
static const int kTileSize = 48;
|
|
|
|
static const float kDetailSampleDist = 1;
|
|
static const float kDetailSampleMaxError = 1;
|
|
|
|
static const int EXPECTED_LAYERS_PER_TILE = 4;
|
|
|
|
static const int MAX_LAYERS = 32;
|
|
|
|
static const int kMaxTiles = 0;
|
|
static const int kMaxPolysPerTile = 0;
|
|
|
|
/// Mask of the ceil part of the area id (3 lower bits)
|
|
/// the 0 value (RC_NULL_AREA) is left unused
|
|
static const unsigned char SAMPLE_POLYAREA_TYPE_MASK = 0x07;
|
|
/// Value for the kind of ceil "ground"
|
|
static const unsigned char SAMPLE_POLYAREA_TYPE_GROUND = 0x1;
|
|
/// Value for the kind of ceil "water"
|
|
static const unsigned char SAMPLE_POLYAREA_TYPE_WATER = 0x2;
|
|
/// Value for the kind of ceil "road"
|
|
static const unsigned char SAMPLE_POLYAREA_TYPE_ROAD = 0x3;
|
|
/// Value for the kind of ceil "grass"
|
|
static const unsigned char SAMPLE_POLYAREA_TYPE_GRASS = 0x4;
|
|
/// Flag for door area. Can be combined with area types and jump flag.
|
|
static const unsigned char SAMPLE_POLYAREA_FLAG_DOOR = 0x08;
|
|
/// Flag for jump area. Can be combined with area types and door flag.
|
|
static const unsigned char SAMPLE_POLYAREA_FLAG_JUMP = 0x10;
|
|
|
|
struct TileCacheData
|
|
{
|
|
unsigned char* data;
|
|
int dataSize;
|
|
};
|
|
|
|
struct rcChunkyTriMeshNode
|
|
{
|
|
float bmin[2];
|
|
float bmax[2];
|
|
int i;
|
|
int n;
|
|
};
|
|
|
|
struct rcChunkyTriMesh
|
|
{
|
|
inline rcChunkyTriMesh() : nodes(0), nnodes(0), tris(0), ntris(0), maxTrisPerChunk(0) {};
|
|
inline ~rcChunkyTriMesh() { delete [] nodes; delete [] tris; }
|
|
|
|
rcChunkyTriMeshNode* nodes;
|
|
int nnodes;
|
|
int* tris;
|
|
int ntris;
|
|
int maxTrisPerChunk;
|
|
|
|
private:
|
|
// Explicitly disabled copy constructor and copy assignment operator.
|
|
#if 0
|
|
rcChunkyTriMesh(const rcChunkyTriMesh&);
|
|
rcChunkyTriMesh& operator=(const rcChunkyTriMesh&);
|
|
#endif
|
|
};
|
|
|
|
static const int MAX_CONVEXVOL_PTS = 12;
|
|
struct ConvexVolume
|
|
{
|
|
ConvexVolume(): areaMod(RC_AREA_FLAGS_MASK) {}
|
|
float verts[MAX_CONVEXVOL_PTS*3];
|
|
float hmin, hmax;
|
|
int nverts;
|
|
rcAreaModification areaMod;
|
|
};
|
|
|
|
static int calcLayerBufferSize(const int gridWidth, const int gridHeight)
|
|
{
|
|
const int headerSize = dtAlign4(sizeof(dtTileCacheLayerHeader));
|
|
const int gridSize = gridWidth * gridHeight;
|
|
return headerSize + gridSize*4;
|
|
}
|
|
|
|
inline bool checkOverlapRect(const float amin[2], const float amax[2],
|
|
const float bmin[2], const float bmax[2])
|
|
{
|
|
bool overlap = true;
|
|
overlap = (amin[0] > bmax[0] || amax[0] < bmin[0]) ? false : overlap;
|
|
overlap = (amin[1] > bmax[1] || amax[1] < bmin[1]) ? false : overlap;
|
|
return overlap;
|
|
}
|
|
|
|
static int rcGetChunksOverlappingRect(const rcChunkyTriMesh* cm,
|
|
float bmin[2], float bmax[2],
|
|
int* ids, const int maxIds)
|
|
{
|
|
// Traverse tree
|
|
int i = 0;
|
|
int n = 0;
|
|
while (i < cm->nnodes) {
|
|
const rcChunkyTriMeshNode* node = &cm->nodes[i];
|
|
const bool overlap = checkOverlapRect(bmin, bmax, node->bmin, node->bmax);
|
|
const bool isLeafNode = node->i >= 0;
|
|
|
|
if (isLeafNode && overlap) {
|
|
if (n < maxIds) {
|
|
ids[n] = i;
|
|
n++;
|
|
}
|
|
}
|
|
|
|
if (overlap || isLeafNode)
|
|
i++;
|
|
else {
|
|
const int escapeIndex = -node->i;
|
|
i += escapeIndex;
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
struct RasterizationContext
|
|
{
|
|
RasterizationContext() :
|
|
solid(0),
|
|
triareas(0),
|
|
lset(0),
|
|
chf(0),
|
|
ntiles(0)
|
|
{
|
|
memset(tiles, 0, sizeof(TileCacheData)*MAX_LAYERS);
|
|
}
|
|
|
|
~RasterizationContext()
|
|
{
|
|
rcFreeHeightField(solid);
|
|
delete [] triareas;
|
|
rcFreeHeightfieldLayerSet(lset);
|
|
rcFreeCompactHeightfield(chf);
|
|
for (int i = 0; i < MAX_LAYERS; ++i)
|
|
{
|
|
dtFree(tiles[i].data);
|
|
tiles[i].data = 0;
|
|
}
|
|
}
|
|
|
|
rcHeightfield* solid;
|
|
unsigned char* triareas;
|
|
rcHeightfieldLayerSet* lset;
|
|
rcCompactHeightfield* chf;
|
|
TileCacheData tiles[MAX_LAYERS];
|
|
int ntiles;
|
|
};
|
|
|
|
struct LinearAllocator : public dtTileCacheAlloc
|
|
{
|
|
unsigned char* buffer;
|
|
size_t capacity;
|
|
size_t top;
|
|
size_t high;
|
|
|
|
LinearAllocator(const size_t cap) : buffer(0), capacity(0), top(0), high(0)
|
|
{
|
|
resize(cap);
|
|
}
|
|
|
|
~LinearAllocator()
|
|
{
|
|
dtFree(buffer);
|
|
}
|
|
|
|
void resize(const size_t cap)
|
|
{
|
|
if (buffer) dtFree(buffer);
|
|
buffer = (unsigned char*)dtAlloc(cap, DT_ALLOC_PERM);
|
|
capacity = cap;
|
|
}
|
|
|
|
virtual void reset()
|
|
{
|
|
high = dtMax(high, top);
|
|
top = 0;
|
|
}
|
|
|
|
virtual void* alloc(const size_t size)
|
|
{
|
|
if (!buffer)
|
|
return 0;
|
|
if (top+size > capacity)
|
|
return 0;
|
|
unsigned char* mem = &buffer[top];
|
|
top += size;
|
|
return mem;
|
|
}
|
|
|
|
virtual void free(void* /*ptr*/)
|
|
{
|
|
// Empty
|
|
}
|
|
};
|
|
|
|
struct FastLZCompressor : public dtTileCacheCompressor
|
|
{
|
|
virtual int maxCompressedSize(const int bufferSize)
|
|
{
|
|
return (int)(bufferSize* 1.05f);
|
|
}
|
|
|
|
virtual dtStatus compress(const unsigned char* buffer, const int bufferSize,
|
|
unsigned char* compressed, const int /*maxCompressedSize*/, int* compressedSize)
|
|
{
|
|
*compressedSize = fastlz_compress((const void *const)buffer, bufferSize, compressed);
|
|
return DT_SUCCESS;
|
|
}
|
|
|
|
virtual dtStatus decompress(const unsigned char* compressed, const int compressedSize,
|
|
unsigned char* buffer, const int maxBufferSize, int* bufferSize)
|
|
{
|
|
*bufferSize = fastlz_decompress(compressed, compressedSize, buffer, maxBufferSize);
|
|
return *bufferSize < 0 ? DT_FAILURE : DT_SUCCESS;
|
|
}
|
|
};
|
|
|
|
struct MeshProcess : public dtTileCacheMeshProcess
|
|
{
|
|
inline MeshProcess()
|
|
{
|
|
}
|
|
|
|
virtual void process(struct dtNavMeshCreateParams* params,
|
|
unsigned char* polyAreas,
|
|
unsigned short* polyFlags)
|
|
{
|
|
#if 0
|
|
// Update poly flags from areas.
|
|
for (int i = 0; i < params->polyCount; ++i)
|
|
{
|
|
polyFlags[i] = sampleAreaToFlags(polyAreas[i]);
|
|
}
|
|
|
|
// Pass in off-mesh connections.
|
|
if (m_geom)
|
|
{
|
|
params->offMeshConVerts = m_geom->getOffMeshConnectionVerts();
|
|
params->offMeshConRad = m_geom->getOffMeshConnectionRads();
|
|
params->offMeshConDir = m_geom->getOffMeshConnectionDirs();
|
|
params->offMeshConAreas = m_geom->getOffMeshConnectionAreas();
|
|
params->offMeshConFlags = m_geom->getOffMeshConnectionFlags();
|
|
params->offMeshConUserID = m_geom->getOffMeshConnectionId();
|
|
params->offMeshConCount = m_geom->getOffMeshConnectionCount();
|
|
}
|
|
#endif
|
|
}
|
|
};
|
|
|
|
void NavMeshBuilder::Init()
|
|
{
|
|
|
|
}
|
|
|
|
void NavMeshBuilder::UnInit()
|
|
{
|
|
|
|
}
|
|
|
|
struct BuilderParams
|
|
{
|
|
float kCellSize = 64;
|
|
float kCellHeight = 64;
|
|
const float* bmin = nullptr;
|
|
const float* bmax = nullptr;
|
|
rcConfig cfg;
|
|
dtTileCacheParams tcparams;
|
|
MapInstance* map_instance = nullptr;
|
|
LinearAllocator* talloc = nullptr;
|
|
FastLZCompressor* tcomp = nullptr;
|
|
MeshProcess* tmproc = nullptr;
|
|
int gw = 0;
|
|
int gh = 0;
|
|
|
|
int ts = 0;
|
|
int tw = 0;
|
|
int th = 0;
|
|
};
|
|
|
|
dtNavMesh* NavMeshBuilder::Build(MapInstance* map_instance)
|
|
{
|
|
BuilderParams builder_params;
|
|
// Init cache
|
|
rcCalcGridSize(builder_params.bmin,
|
|
builder_params.bmax,
|
|
builder_params.kCellSize,
|
|
&builder_params.gw,
|
|
&builder_params.gh);
|
|
builder_params.ts = (int)kTileSize;
|
|
builder_params.tw = (builder_params.gw + builder_params.ts-1) / builder_params.ts;
|
|
builder_params.th = (builder_params.gh + builder_params.ts-1) / builder_params.ts;
|
|
|
|
InitRcConfig(builder_params);
|
|
InitTileCacheParams(builder_params);
|
|
|
|
dtStatus status;
|
|
|
|
dtTileCache* tile_cache = dtAllocTileCache();
|
|
status = tile_cache->init(&builder_params.tcparams,
|
|
builder_params.talloc,
|
|
builder_params.tcomp,
|
|
builder_params.tmproc);
|
|
dtNavMeshParams params;
|
|
{
|
|
memset(¶ms, 0, sizeof(params));
|
|
rcVcopy(params.orig, builder_params.bmin);
|
|
params.tileWidth = kTileSize * builder_params.kCellSize;
|
|
params.tileHeight = kTileSize * builder_params.kCellSize;
|
|
params.maxTiles = kMaxTiles;
|
|
params.maxPolys = kMaxPolysPerTile;
|
|
}
|
|
|
|
dtNavMesh* navmesh = dtAllocNavMesh();
|
|
|
|
status = navmesh->init(¶ms);
|
|
if (dtStatusFailed(status)) {
|
|
abort();
|
|
}
|
|
|
|
int m_cacheLayerCount = 0;
|
|
int m_cacheCompressedSize = 0;
|
|
int m_cacheRawSize = 0;
|
|
|
|
for (int y = 0; y < builder_params.th; ++y) {
|
|
for (int x = 0; x < builder_params.tw; ++x) {
|
|
TileCacheData tiles[MAX_LAYERS];
|
|
memset(tiles, 0, sizeof(tiles));
|
|
int ntiles = RasterizeTileLayers(x, y, builder_params.cfg, tiles, MAX_LAYERS);
|
|
|
|
for (int i = 0; i < ntiles; ++i) {
|
|
TileCacheData* tile = &tiles[i];
|
|
status = tile_cache->addTile(tile->data, tile->dataSize, DT_COMPRESSEDTILE_FREE_DATA, 0);
|
|
if (dtStatusFailed(status))
|
|
{
|
|
dtFree(tile->data);
|
|
tile->data = 0;
|
|
continue;
|
|
}
|
|
|
|
m_cacheLayerCount++;
|
|
m_cacheCompressedSize += tile->dataSize;
|
|
m_cacheRawSize += calcLayerBufferSize(builder_params.tcparams.width,
|
|
builder_params.tcparams.height);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build initial meshes
|
|
for (int y = 0; y < builder_params.th; ++y) {
|
|
for (int x = 0; x < builder_params.tw; ++x) {
|
|
tile_cache->buildNavMeshTilesAt(x,y, navmesh);
|
|
}
|
|
}
|
|
|
|
const dtNavMesh* nav = navmesh;
|
|
int navmeshMemUsage = 0;
|
|
for (int i = 0; i < nav->getMaxTiles(); ++i) {
|
|
const dtMeshTile* tile = nav->getTile(i);
|
|
if (tile->header) {
|
|
navmeshMemUsage += tile->dataSize;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void NavMeshBuilder::OutputObjFile(MapInstance* map_instance)
|
|
{
|
|
std::vector<a8::Vec2> vertexs;
|
|
std::vector<std::tuple<int, int, int>> faces;
|
|
vertexs.reserve(10000);
|
|
for (auto& pair : map_instance->uniid_hash_) {
|
|
for (ColliderComponent* collider : *pair.second->GetColliders()) {
|
|
if (collider->type == CT_Aabb) {
|
|
AabbCollider* aabb_box = (AabbCollider*)collider;
|
|
{
|
|
a8::Vec2 vert = collider->owner->GetPos() + aabb_box->_min;
|
|
vert.x = vert.x - map_instance->map_meta_->i->map_width() / 2.0f;
|
|
vert.y = vert.y - map_instance->map_meta_->i->map_height() / 2.0f;
|
|
|
|
vertexs.push_back(vert);
|
|
vert.y += aabb_box->_max.y - aabb_box->_min.y;
|
|
vertexs.push_back(vert);
|
|
vert.x += aabb_box->_max.x - aabb_box->_min.x;
|
|
vertexs.push_back(vert);
|
|
vert.y -= aabb_box->_max.y - aabb_box->_min.y;
|
|
vertexs.push_back(vert);
|
|
}
|
|
//0 1 2
|
|
faces.push_back
|
|
(std::make_tuple
|
|
(
|
|
vertexs.size() - 4 + 1,
|
|
vertexs.size() - 3 + 1,
|
|
vertexs.size() - 2 + 1
|
|
));
|
|
//0 2 3
|
|
faces.push_back
|
|
(std::make_tuple
|
|
(
|
|
vertexs.size() - 4 + 1,
|
|
vertexs.size() - 2 + 1,
|
|
vertexs.size() - 1 + 1
|
|
));
|
|
}
|
|
}
|
|
}
|
|
{
|
|
std::string filename = a8::Format("%s.obj", {map_instance->map_tpl_name_});
|
|
FILE* fp = fopen(filename.c_str(), "wb");
|
|
#if 1
|
|
{
|
|
vertexs.clear();
|
|
faces.clear();
|
|
{
|
|
a8::Vec2 vert;
|
|
vert.x = 0 - map_instance->map_meta_->i->map_width() / 2.0f;
|
|
vert.y = 0 - map_instance->map_meta_->i->map_height() / 2.0f;
|
|
|
|
vertexs.push_back(vert);
|
|
vert.y += map_instance->map_meta_->i->map_height();
|
|
vertexs.push_back(vert);
|
|
vert.x += map_instance->map_meta_->i->map_width();
|
|
vertexs.push_back(vert);
|
|
vert.y -= map_instance->map_meta_->i->map_height();
|
|
vertexs.push_back(vert);
|
|
|
|
//0 1 2
|
|
faces.push_back
|
|
(std::make_tuple
|
|
(
|
|
vertexs.size() - 4 + 1,
|
|
vertexs.size() - 3 + 1,
|
|
vertexs.size() - 2 + 1
|
|
));
|
|
//0 2 3
|
|
faces.push_back
|
|
(std::make_tuple
|
|
(
|
|
vertexs.size() - 4 + 1,
|
|
vertexs.size() - 2 + 1,
|
|
vertexs.size() - 1 + 1
|
|
));
|
|
}
|
|
}
|
|
#endif
|
|
for (auto& vert : vertexs) {
|
|
std::string data = a8::Format("v %f %f %f\r\n",
|
|
{
|
|
vert.x,
|
|
-10,
|
|
vert.y,
|
|
});
|
|
fwrite(data.data(), 1, data.size(), fp);
|
|
}
|
|
for (auto& tuple : faces) {
|
|
std::string data = a8::Format("f %d %d %d\r\n",
|
|
{
|
|
std::get<0>(tuple),
|
|
std::get<1>(tuple),
|
|
std::get<2>(tuple)
|
|
});
|
|
fwrite(data.data(), 1, data.size(), fp);
|
|
}
|
|
fclose(fp);
|
|
}
|
|
}
|
|
|
|
void NavMeshBuilder::InitRcConfig(BuilderParams& builder_params)
|
|
{
|
|
rcConfig& cfg = builder_params.cfg;
|
|
memset(&cfg, 0, sizeof(cfg));
|
|
cfg.cs = builder_params.kCellSize;
|
|
cfg.ch = builder_params.kCellHeight;
|
|
cfg.walkableSlopeAngle = kAgentMaxSlope;
|
|
cfg.walkableHeight = (int)ceilf(kAgentHeight / cfg.ch);
|
|
cfg.walkableClimb = (int)floorf(kAgentMaxClimb / cfg.ch);
|
|
cfg.walkableRadius = (int)ceilf(kAgentRadius / cfg.cs);
|
|
cfg.maxEdgeLen = (int)(kEdgeMaxLen / builder_params.kCellSize);
|
|
cfg.maxSimplificationError = kEdgeMaxError;
|
|
cfg.minRegionArea = (int)rcSqr(kRegionMinSize); // Note: area = size*size
|
|
cfg.mergeRegionArea = (int)rcSqr(kRegionMergeSize); // Note: area = size*size
|
|
cfg.maxVertsPerPoly = (int)kVertsPerPoly;
|
|
cfg.tileSize = (int)kTileSize;
|
|
cfg.borderSize = cfg.walkableRadius + 3; // Reserve enough padding.
|
|
cfg.width = cfg.tileSize + cfg.borderSize*2;
|
|
cfg.height = cfg.tileSize + cfg.borderSize*2;
|
|
cfg.detailSampleDist = kDetailSampleDist < 0.9f ? 0 : builder_params.kCellSize * kDetailSampleDist;
|
|
cfg.detailSampleMaxError = kCellHeight * kDetailSampleMaxError;
|
|
rcVcopy(cfg.bmin, builder_params.bmin);
|
|
rcVcopy(cfg.bmax, builder_params.bmax);
|
|
}
|
|
|
|
void NavMeshBuilder::InitTileCacheParams(BuilderParams& builder_params)
|
|
{
|
|
dtTileCacheParams& tcparams = builder_params.tcparams;
|
|
// Tile cache params.
|
|
memset(&tcparams, 0, sizeof(tcparams));
|
|
rcVcopy(tcparams.orig, builder_params.bmin);
|
|
tcparams.cs = builder_params.kCellSize;
|
|
tcparams.ch = builder_params.kCellHeight;
|
|
tcparams.width = (int)kTileSize;
|
|
tcparams.height = (int)kTileSize;
|
|
tcparams.walkableHeight = kAgentHeight;
|
|
tcparams.walkableRadius = kAgentRadius;
|
|
tcparams.walkableClimb = kAgentMaxClimb;
|
|
tcparams.maxSimplificationError = kEdgeMaxError;
|
|
tcparams.maxTiles = builder_params.tw*builder_params.th*EXPECTED_LAYERS_PER_TILE;
|
|
tcparams.maxObstacles = 128;
|
|
}
|
|
|
|
int NavMeshBuilder::RasterizeTileLayers(const int tx, const int ty,
|
|
const rcConfig& cfg,
|
|
TileCacheData* tiles,
|
|
const int maxTiles)
|
|
{
|
|
#if 0
|
|
if (!m_geom || !m_geom->getMesh() || !m_geom->getChunkyMesh())
|
|
{
|
|
m_ctx->log(RC_LOG_ERROR, "buildTile: Input mesh is not specified.");
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#if 1
|
|
rcContext* ctx = nullptr;
|
|
rcAreaModification SAMPLE_AREAMOD_GROUND(SAMPLE_POLYAREA_TYPE_GROUND, SAMPLE_POLYAREA_TYPE_MASK);
|
|
#endif
|
|
|
|
FastLZCompressor comp;
|
|
RasterizationContext rc;
|
|
|
|
#if 1
|
|
const float* verts = nullptr;
|
|
const int nverts = 0;
|
|
const rcChunkyTriMesh* chunkyMesh = nullptr;
|
|
#else
|
|
const float* verts = m_geom->getMesh()->getVerts();
|
|
const int nverts = m_geom->getMesh()->getVertCount();
|
|
const rcChunkyTriMesh* chunkyMesh = m_geom->getChunkyMesh();
|
|
#endif
|
|
|
|
// Tile bounds.
|
|
const float tcs = cfg.tileSize * cfg.cs;
|
|
|
|
rcConfig tcfg;
|
|
memcpy(&tcfg, &cfg, sizeof(tcfg));
|
|
|
|
tcfg.bmin[0] = cfg.bmin[0] + tx*tcs;
|
|
tcfg.bmin[1] = cfg.bmin[1];
|
|
tcfg.bmin[2] = cfg.bmin[2] + ty*tcs;
|
|
tcfg.bmax[0] = cfg.bmin[0] + (tx+1)*tcs;
|
|
tcfg.bmax[1] = cfg.bmax[1];
|
|
tcfg.bmax[2] = cfg.bmin[2] + (ty+1)*tcs;
|
|
tcfg.bmin[0] -= tcfg.borderSize*tcfg.cs;
|
|
tcfg.bmin[2] -= tcfg.borderSize*tcfg.cs;
|
|
tcfg.bmax[0] += tcfg.borderSize*tcfg.cs;
|
|
tcfg.bmax[2] += tcfg.borderSize*tcfg.cs;
|
|
|
|
// Allocate voxel heightfield where we rasterize our input data to.
|
|
rc.solid = rcAllocHeightfield();
|
|
if (!rc.solid) {
|
|
return 0;
|
|
}
|
|
if (!rcCreateHeightfield(ctx, *rc.solid, tcfg.width, tcfg.height, tcfg.bmin, tcfg.bmax, tcfg.cs, tcfg.ch)) {
|
|
return 0;
|
|
}
|
|
|
|
// Allocate array that can hold triangle flags.
|
|
// If you have multiple meshes you need to process, allocate
|
|
// and array which can hold the max number of triangles you need to process.
|
|
rc.triareas = new unsigned char[chunkyMesh->maxTrisPerChunk];
|
|
if (!rc.triareas) {
|
|
ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'm_triareas' (%d).", chunkyMesh->maxTrisPerChunk);
|
|
return 0;
|
|
}
|
|
|
|
float tbmin[2], tbmax[2];
|
|
tbmin[0] = tcfg.bmin[0];
|
|
tbmin[1] = tcfg.bmin[2];
|
|
tbmax[0] = tcfg.bmax[0];
|
|
tbmax[1] = tcfg.bmax[2];
|
|
int cid[512];// TODO: Make grow when returning too many items.
|
|
const int ncid = rcGetChunksOverlappingRect(chunkyMesh, tbmin, tbmax, cid, 512);
|
|
if (!ncid) {
|
|
return 0; // empty
|
|
}
|
|
|
|
for (int i = 0; i < ncid; ++i) {
|
|
const rcChunkyTriMeshNode& node = chunkyMesh->nodes[cid[i]];
|
|
const int* tris = &chunkyMesh->tris[node.i*3];
|
|
const int ntris = node.n;
|
|
|
|
memset(rc.triareas, 0, ntris*sizeof(unsigned char));
|
|
rcMarkWalkableTriangles(ctx, tcfg.walkableSlopeAngle,
|
|
verts, nverts, tris, ntris, rc.triareas,
|
|
SAMPLE_AREAMOD_GROUND);
|
|
|
|
if (!rcRasterizeTriangles(ctx, verts, nverts, tris, rc.triareas, ntris, *rc.solid, tcfg.walkableClimb)) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Once all geometry is rasterized, we do initial pass of filtering to
|
|
// remove unwanted overhangs caused by the conservative rasterization
|
|
// as well as filter spans where the character cannot possibly stand.
|
|
#if 0
|
|
if (m_filterLowHangingObstacles)
|
|
rcFilterLowHangingWalkableObstacles(m_ctx, tcfg.walkableClimb, *rc.solid);
|
|
if (m_filterLedgeSpans)
|
|
rcFilterLedgeSpans(m_ctx, tcfg.walkableHeight, tcfg.walkableClimb, *rc.solid);
|
|
if (m_filterWalkableLowHeightSpans)
|
|
rcFilterWalkableLowHeightSpans(m_ctx, tcfg.walkableHeight, *rc.solid);
|
|
#endif
|
|
|
|
rc.chf = rcAllocCompactHeightfield();
|
|
if (!rc.chf) {
|
|
ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'chf'.");
|
|
return 0;
|
|
}
|
|
if (!rcBuildCompactHeightfield(ctx, tcfg.walkableHeight, tcfg.walkableClimb, *rc.solid, *rc.chf)) {
|
|
ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build compact data.");
|
|
return 0;
|
|
}
|
|
|
|
// Erode the walkable area by agent radius.
|
|
if (!rcErodeWalkableArea(ctx, tcfg.walkableRadius, *rc.chf)) {
|
|
ctx->log(RC_LOG_ERROR, "buildNavigation: Could not erode.");
|
|
return 0;
|
|
}
|
|
|
|
// (Optional) Mark areas.
|
|
#if 1
|
|
const ConvexVolume* vols = nullptr;
|
|
int vol_count = 0;
|
|
#else
|
|
const ConvexVolume* vols = m_geom->getConvexVolumes();
|
|
#endif
|
|
for (int i = 0; i < vol_count; ++i) {
|
|
rcMarkConvexPolyArea(ctx, vols[i].verts, vols[i].nverts,
|
|
vols[i].hmin, vols[i].hmax,
|
|
vols[i].areaMod, *rc.chf);
|
|
}
|
|
|
|
rc.lset = rcAllocHeightfieldLayerSet();
|
|
if (!rc.lset) {
|
|
ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'lset'.");
|
|
return 0;
|
|
}
|
|
if (!rcBuildHeightfieldLayers(ctx, *rc.chf, tcfg.borderSize, tcfg.walkableHeight, *rc.lset)) {
|
|
ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build heighfield layers.");
|
|
return 0;
|
|
}
|
|
|
|
rc.ntiles = 0;
|
|
for (int i = 0; i < rcMin(rc.lset->nlayers, MAX_LAYERS); ++i) {
|
|
TileCacheData* tile = &rc.tiles[rc.ntiles++];
|
|
const rcHeightfieldLayer* layer = &rc.lset->layers[i];
|
|
|
|
// Store header
|
|
dtTileCacheLayerHeader header;
|
|
header.magic = DT_TILECACHE_MAGIC;
|
|
header.version = DT_TILECACHE_VERSION;
|
|
|
|
// Tile layer location in the navmesh.
|
|
header.tx = tx;
|
|
header.ty = ty;
|
|
header.tlayer = i;
|
|
dtVcopy(header.bmin, layer->bmin);
|
|
dtVcopy(header.bmax, layer->bmax);
|
|
|
|
// Tile info.
|
|
header.width = (unsigned char)layer->width;
|
|
header.height = (unsigned char)layer->height;
|
|
header.minx = (unsigned char)layer->minx;
|
|
header.maxx = (unsigned char)layer->maxx;
|
|
header.miny = (unsigned char)layer->miny;
|
|
header.maxy = (unsigned char)layer->maxy;
|
|
header.hmin = (unsigned short)layer->hmin;
|
|
header.hmax = (unsigned short)layer->hmax;
|
|
|
|
dtStatus status = dtBuildTileCacheLayer(&comp,
|
|
&header,
|
|
layer->heights,
|
|
layer->areas,
|
|
layer->cons,
|
|
&tile->data,
|
|
&tile->dataSize);
|
|
if (dtStatusFailed(status)) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Transfer ownsership of tile data from build context to the caller.
|
|
int n = 0;
|
|
for (int i = 0; i < rcMin(rc.ntiles, maxTiles); ++i) {
|
|
tiles[n++] = rc.tiles[i];
|
|
rc.tiles[i].data = 0;
|
|
rc.tiles[i].dataSize = 0;
|
|
}
|
|
|
|
return n;
|
|
}
|