#include "precompile.h" #include #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; 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; }; dtNavMesh* NavMeshBuilder::Build(MapInstance* map_instance) { BuilderParams builder_params; // Init cache int gw = 0, gh = 0; rcCalcGridSize(builder_params.bmin, builder_params.bmax, builder_params.kCellSize, &gw, &gh); const int ts = (int)kTileSize; const int tw = (gw + ts-1) / ts; const int th = (gh + ts-1) / 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 < th; ++y) { for (int x = 0; x < 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 < th; ++y) { for (int x = 0; x < tw; ++x) { tile_cache->buildNavMeshTilesAt(x,y, navmesh); } } #if 0 m_cacheBuildTimeMs = ctx->getAccumulatedTime(RC_TIMER_TOTAL)/1000.0f; m_cacheBuildMemUsage = builder_params.talloc->high; #endif 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 vertexs; std::vector> 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) { #if 0 memset(&cfg, 0, sizeof(cfg)); cfg.cs = kCellSize; cfg.ch = 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 / 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 : kCellSize * kDetailSampleDist; cfg.detailSampleMaxError = kCellHeight * kDetailSampleMaxError; rcVcopy(cfg.bmin, bmin); rcVcopy(cfg.bmax, bmax); #endif } void NavMeshBuilder::InitTileCacheParams(BuilderParams& builder_params) { #if 0 // Tile cache params. memset(&tcparams, 0, sizeof(tcparams)); rcVcopy(tcparams.orig, bmin); tcparams.cs = kCellSize; tcparams.ch = kCellHeight; tcparams.width = (int)kTileSize; tcparams.height = (int)kTileSize; tcparams.walkableHeight = kAgentHeight; tcparams.walkableRadius = kAgentRadius; tcparams.walkableClimb = kAgentMaxClimb; tcparams.maxSimplificationError = kEdgeMaxError; tcparams.maxTiles = tw*th*EXPECTED_LAYERS_PER_TILE; tcparams.maxObstacles = 128; #endif } 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; }