here my tilecache impl: bool NavMeshBuilder::BuildTC() { if (!m_World || !m_World->GetStaticWorldMesh().isValid()) { printf("NavMeshBuilder::Build: No vertices and triangles."); return false; } Setup(); // Init cache const float* bmin = m_World->GetWorldMeshBoundsMin(); const float* bmax = m_World->GetWorldMeshBoundsMax(); int gw = 0, gh = 0; rcCalcGridSize(bmin, bmax, m_cellSize, &gw, &gh); const int ts = (int)m_tileSize; const int tw = (gw + ts - 1) / ts; const int th = (gh + ts - 1) / ts; rcConfig cfg; memset(&cfg, 0, sizeof(cfg)); cfg.cs = m_cellSize; cfg.ch = m_cellHeight; cfg.walkableSlopeAngle = m_agentMaxSlope; cfg.walkableHeight = (int)ceilf(m_agentHeight / cfg.ch); cfg.walkableClimb = (int)floorf(m_agentMaxClimb / cfg.ch); cfg.walkableRadius = (int)ceilf(m_agentRadius / cfg.cs); cfg.maxEdgeLen = (int)(m_edgeMaxLen / m_cellSize); cfg.maxSimplificationError = m_edgeMaxError; cfg.minRegionArea = (int)rcSqr(m_regionMinSize); // Note: area = size*size cfg.mergeRegionArea = (int)rcSqr(m_regionMergeSize); // Note: area = size*size cfg.maxVertsPerPoly = (int)m_vertsPerPoly; cfg.tileSize = (int)m_tileSize; cfg.borderSize = cfg.walkableRadius + 3; // Reserve enough padding. cfg.width = cfg.tileSize + cfg.borderSize * 2; cfg.height = cfg.tileSize + cfg.borderSize * 2; cfg.detailSampleDist = m_detailSampleDist < 0.9f ? 0 : m_cellSize * m_detailSampleDist; cfg.detailSampleMaxError = m_cellHeight * m_detailSampleMaxError; rcVcopy(cfg.bmin, bmin); rcVcopy(cfg.bmax, bmax); // Tile cache params. dtTileCacheParams tcparams; memset(&tcparams, 0, sizeof(tcparams)); rcVcopy(tcparams.orig, bmin); tcparams.cs = m_cellSize; tcparams.ch = m_cellHeight; tcparams.width = (int)m_tileSize; tcparams.height = (int)m_tileSize; tcparams.walkableHeight = m_agentHeight; tcparams.walkableRadius = m_agentRadius; tcparams.walkableClimb = m_agentMaxClimb; tcparams.maxSimplificationError = m_edgeMaxError; tcparams.maxTiles = tw * th * 4; //EXPECTED_LAYERS_PER_TILE=4 tcparams.maxObstacles = 128; dtFreeTileCache(m_tileCache); dtStatus status; m_tileCache = dtAllocTileCache(); if (!m_tileCache) { printf("buildTiledNavigation: Could not allocate tile cache.\n"); return false; } m_navQuery = dtAllocNavMeshQuery(); if (!m_navQuery) { printf("NavMeshBuilder::Build: Could not allocate m_navQuery."); return false; } LinearAllocator* m_talloc; FastLZCompressor* m_tcomp; MeshProcess* m_tmproc; m_talloc = new LinearAllocator(32000); m_tcomp = new FastLZCompressor; m_tmproc = new MeshProcess; status = m_tileCache->init(&tcparams, m_talloc, m_tcomp, m_tmproc); if (dtStatusFailed(status)) { printf("buildTiledNavigation: Could not init tile cache."); return false; } dtFreeNavMesh(m_navMesh); m_navMesh = dtAllocNavMesh(); if (!m_navMesh) { printf("buildTiledNavigation: Could not allocate navmesh.\n"); return false; } dtNavMeshParams params; memset(&params, 0, sizeof(params)); rcVcopy(params.orig, bmin); params.tileWidth = m_tileSize * m_cellSize; params.tileHeight = m_tileSize * m_cellSize; params.maxTiles = m_maxTiles; params.maxPolys = m_maxPolysPerTile; status = m_navMesh->init(&params); if (dtStatusFailed(status)) { printf("buildTiledNavigation: Could not init navmesh.\n"); return false; } status = m_navQuery->init(m_navMesh, 2048); if (dtStatusFailed(status)) { printf("buildTiledNavigation: Could not init Detour navmesh query"); return false; } std::vector<std::thread> threads; int numThreads = std::thread::hardware_concurrency(); std::atomic<int> processedTiles(0); int totalTiles = tw * th; auto updateProgress = [&processedTiles, totalTiles]() { const int barWidth = 50; while (processedTiles < totalTiles) { float progress = static_cast<float>(processedTiles) / totalTiles; int pos = barWidth * progress; std::cout << "\r["; for (int i = 0; i < barWidth; ++i) { if (i < pos) std::cout << "="; else if (i == pos) std::cout << ">"; else std::cout << " "; } std::cout << "] " << std::setw(3) << static_cast<int>(progress * 100.0) << "% " << processedTiles << "/" << totalTiles << " tiles processed"; std::cout.flush(); std::this_thread::sleep_for(std::chrono::milliseconds(100)); } std::cout << std::endl; }; auto processTileRange = [this, &cfg, tw, th, &processedTiles](int startY, int endY) { for (int y = startY; y < endY; ++y) { for (int x = 0; x < tw; ++x) { TileCacheData tiles[MAX_LAYERS]; memset(tiles, 0, sizeof(tiles)); int ntiles = rasterizeTileLayers(x, y, cfg, tiles, MAX_LAYERS); std::lock_guard<std::mutex> lock(m_tileCacheMutex); for (int i = 0; i < ntiles; ++i) { TileCacheData* tile = &tiles[i]; dtStatus status = m_tileCache->addTile(tile->data, tile->dataSize, DT_COMPRESSEDTILE_FREE_DATA, 0); if (dtStatusFailed(status)) { dtFree(tile->data); tile->data = 0; continue; } } processedTiles++; } } }; // Start the progress update thread std::thread progressThread(updateProgress); int tilePerThread = (th + numThreads - 1) / numThreads; for (int i = 0; i < numThreads; ++i) { int startY = i * tilePerThread; int endY = std::min((i + 1) * tilePerThread, th); threads.emplace_back(processTileRange, startY, endY); } for (auto& thread : threads) { thread.join(); } // Ensure all tiles are processed before joining the progress thread processedTiles = totalTiles; progressThread.join(); for (int y = 0; y < th; ++y) for (int x = 0; x < tw; ++x) m_tileCache->buildNavMeshTilesAt(x, y, m_navMesh); return true; } int NavMeshBuilder::rasterizeTileLayers(const int tx, const int ty, const rcConfig& cfg, TileCacheData* tiles, const int maxTiles) { //if (!m_World || !m_World->GetStaticWorldMesh().isValid()) //{ // std::cout << "NavMeshBuilder::rasterizeTileLayers: Input mesh is not specified.\n"; // return 0; //} FastLZCompressor comp; RasterizationContext rc; const float* verts; int nverts = 0; const rcChunkyTriMesh* chunkyMesh; const rcChunkyTriMesh* chunkyWaterMesh; int waternverts; const float* waterverts; { verts = m_World->GetStaticWorldMesh().getVerts(); nverts = m_World->GetStaticWorldMesh().getVertCount(); chunkyMesh = m_World->GetChunkyMesh(); chunkyWaterMesh = m_World->GetWaterChunkyMesh(); waternverts = m_World->GetWaterWorldMesh().getVertCount(); waterverts = m_World->GetWaterWorldMesh().getVerts(); } 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) { printf("buildNavigation: Out of memory 'solid'.\n"); return 0; } std::unique_ptr<rcContext> m_ctx = std::make_unique<rcContext>(); if (!rcCreateHeightfield(m_ctx.get(), *rc.solid, tcfg.width, tcfg.height, tcfg.bmin, tcfg.bmax, tcfg.cs, tcfg.ch)) { printf("buildNavigation: Could not create solid heightfield.\n"); return 0; } rc.triareas = new unsigned char[chunkyMesh->maxTrisPerChunk]; if (!rc.triareas) { printf("buildNavigation: Out of memory 'm_triareas' (%d).\n", 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]; const int ncid = rcChunkyTriMesh::rcGetChunksOverlappingRect(chunkyMesh, tbmin, tbmax, cid, 512); if (!ncid) { return 0; } 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(m_ctx.get(), tcfg.walkableSlopeAngle, verts, nverts, tris, ntris, rc.triareas); if (!rcRasterizeTriangles(m_ctx.get(), verts, nverts, tris, rc.triareas, ntris, *rc.solid, tcfg.walkableClimb)) return 0; } if (chunkyWaterMesh) { const int nwcid = rcChunkyTriMesh::rcGetChunksOverlappingRect(chunkyWaterMesh, tbmin, tbmax, cid, 512); for (int i = 0; i < nwcid; ++i) { const rcChunkyTriMeshNode& node = chunkyWaterMesh->nodes[cid[i]]; const int* tris = &chunkyWaterMesh->tris[node.i * 3]; const int ntris = node.n; memset(rc.triareas, SAMPLE_POLYAREA_WATER, ntris * sizeof(unsigned char)); if (!rcRasterizeTriangles(m_ctx.get(), waterverts, waternverts, tris, rc.triareas, ntris, *rc.solid, tcfg.walkableClimb)) return 0; } for (int y = 0; y < rc.solid->height; ++y) { for (int x = 0; x < rc.solid->width; ++x) { for (rcSpan* s = rc.solid->spans[x + y * rc.solid->width]; s; s = s->next) { if (s->area == SAMPLE_POLYAREA_WATER) { s->area = SAMPLE_POLYAREA_WATER; } } } } } rcFilterLowHangingWalkableObstacles(m_ctx.get(), tcfg.walkableClimb, *rc.solid); rcFilterLedgeSpans(m_ctx.get(), tcfg.walkableHeight, tcfg.walkableClimb, *rc.solid); rcFilterWalkableLowHeightSpans(m_ctx.get(), tcfg.walkableHeight, *rc.solid); for (int y = 0; y < rc.solid->height; ++y) { for (int x = 0; x < rc.solid->width; ++x) { for (rcSpan* s = rc.solid->spans[x + y * rc.solid->width]; s; s = s->next) { if (s->area == SAMPLE_POLYAREA_WATER) { s->area = SAMPLE_POLYAREA_WATER; } } } } rc.chf = rcAllocCompactHeightfield(); if (!rc.chf) { printf("buildNavigation: Out of memory 'chf'.\n"); return 0; } if (!rcBuildCompactHeightfield(m_ctx.get(), tcfg.walkableHeight, tcfg.walkableClimb, *rc.solid, *rc.chf)) { printf("buildNavigation: Could not build compact data.\n"); return 0; } for (int i = 0; i < rc.chf->spanCount; ++i) { if (rc.chf->areas[i] == SAMPLE_POLYAREA_WATER) { rc.chf->areas[i] = SAMPLE_POLYAREA_WATER; } } if (!rcErodeWalkableArea(m_ctx.get(), tcfg.walkableRadius, *rc.chf)) { printf("buildNavigation: Could not erode.\n"); return 0; } for (int i = 0; i < rc.chf->spanCount; ++i) { if (rc.chf->areas[i] == SAMPLE_POLYAREA_WATER) { rc.chf->areas[i] = SAMPLE_POLYAREA_WATER; } } rc.lset = rcAllocHeightfieldLayerSet(); if (!rc.lset) { printf("buildNavigation: Out of memory 'lset'.\n"); return 0; } if (!rcBuildHeightfieldLayers(m_ctx.get(), *rc.chf, tcfg.borderSize, tcfg.walkableHeight, *rc.lset)) { //printf("buildNavigation: Could not build heighfield layers.\n"); return 0; } for (int i = 0; i < rc.lset->nlayers; ++i) { rcHeightfieldLayer* layer = &rc.lset->layers[i]; for (int j = 0; j < layer->width * layer->height; ++j) { if (layer->areas[j] == SAMPLE_POLYAREA_WATER) { layer->areas[j] = SAMPLE_POLYAREA_WATER; } } } 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]; dtTileCacheLayerHeader header; header.magic = DT_TILECACHE_MAGIC; header.version = DT_TILECACHE_VERSION; header.tx = tx; header.ty = ty; header.tlayer = i; dtVcopy(header.bmin, layer->bmin); dtVcopy(header.bmax, layer->bmax); 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 ownership 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; } and here old tile navmesh: unsigned char* NavMeshBuilder::buildTileMesh(const int tx, const int ty, const float* bmin, const float* bmax, int& dataSize) { //if (!m_World || !m_World->GetStaticWorldMesh().isValid() || !m_World->GetChunkyMesh()) //{ // std::cout << "NavMeshBuilder::buildTileMesh: Input mesh is not specified.\n"; // return 0; //} // Local variables to avoid modifying shared state rcHeightfield* solid = nullptr; rcCompactHeightfield* chf = nullptr; rcContourSet* cset = nullptr; rcPolyMesh* pmesh = nullptr; rcPolyMeshDetail* dmesh = nullptr; std::vector<unsigned char> triareas; // Use RAII for resource management auto cleanup = [&]() { rcFreeHeightField(solid); rcFreeCompactHeightfield(chf); rcFreeContourSet(cset); rcFreePolyMesh(pmesh); rcFreePolyMeshDetail(dmesh); }; rcConfig cfg; const float* verts; int nverts; const rcChunkyTriMesh* chunkyMesh; const rcChunkyTriMesh* chunkyWaterMesh; int waternverts; const float* waterverts; { std::lock_guard<std::mutex> lock(m_mutex); cfg = m_cfg; verts = m_World->GetStaticWorldMesh().getVerts(); nverts = m_World->GetStaticWorldMesh().getVertCount(); chunkyMesh = m_World->GetChunkyMesh(); chunkyWaterMesh = m_World->GetWaterChunkyMesh(); waternverts = m_World->GetWaterWorldMesh().getVertCount(); waterverts = m_World->GetWaterWorldMesh().getVerts(); } // Init build configuration from GUI { std::lock_guard<std::mutex> lock(m_mutex); // Init build configuration from shared data memset(&cfg, 0, sizeof(cfg)); cfg.cs = m_cellSize; cfg.ch = m_cellHeight; cfg.walkableSlopeAngle = m_agentMaxSlope; cfg.walkableHeight = (int)ceilf(m_agentHeight / cfg.ch); cfg.walkableClimb = (int)floorf(m_agentMaxClimb / cfg.ch); cfg.walkableRadius = (int)ceilf(m_agentRadius / cfg.cs); cfg.maxEdgeLen = (int)(m_edgeMaxLen / m_cellSize); cfg.maxSimplificationError = m_edgeMaxError; cfg.minRegionArea = (int)rcSqr(m_regionMinSize); cfg.mergeRegionArea = (int)rcSqr(m_regionMergeSize); cfg.maxVertsPerPoly = (int)m_vertsPerPoly; cfg.tileSize = (int)m_tileSize; cfg.borderSize = cfg.walkableRadius + 3; cfg.width = cfg.tileSize + cfg.borderSize * 2; cfg.height = cfg.tileSize + cfg.borderSize * 2; cfg.detailSampleDist = m_detailSampleDist < 0.9f ? 0 : m_cellSize * m_detailSampleDist; cfg.detailSampleMaxError = m_cellHeight * m_detailSampleMaxError; } rcVcopy(cfg.bmin, bmin); rcVcopy(cfg.bmax, bmax); cfg.bmin[0] -= cfg.borderSize * cfg.cs; cfg.bmin[2] -= cfg.borderSize * cfg.cs; cfg.bmax[0] += cfg.borderSize * cfg.cs; cfg.bmax[2] += cfg.borderSize * cfg.cs; //printf("NavMeshBuilder::buildTileMesh: Building navigation:\n"); //printf("NavMeshBuilder::buildTileMesh: - %d x %d cells\n", m_cfg.width, m_cfg.height); //printf("NavMeshBuilder::buildTileMesh: - %.1fK verts, %.1fK tris\n", nverts / 1000.0f, ntris / 1000.0f); solid = rcAllocHeightfield(); if (!solid) { printf("NavMeshBuilder::buildTileMesh: Out of memory 'solid'.\n"); cleanup(); return 0; } std::unique_ptr<rcContext> m_ctx = std::make_unique<rcContext>(); if (!rcCreateHeightfield(m_ctx.get(), *solid, cfg.width, cfg.height, cfg.bmin, cfg.bmax, cfg.cs, cfg.ch)) { printf("NavMeshBuilder::buildTileMesh: Could not create solid heightfield."); cleanup(); return 0; } m_triareas.resize(chunkyMesh->maxTrisPerChunk); if (m_triareas.empty()) { std::cerr << "NavMeshBuilder::buildTileMesh: Out of memory 'm_triareas' (" << chunkyMesh->maxTrisPerChunk << ").\n"; cleanup(); return 0; } float tbmin[2], tbmax[2]; tbmin[0] = cfg.bmin[0]; tbmin[1] = cfg.bmin[2]; tbmax[0] = cfg.bmax[0]; tbmax[1] = cfg.bmax[2]; std::vector<int> cid(512);// TODO: Make grow when returning too many items. const int ncid = rcChunkyTriMesh::rcGetChunksOverlappingRect(chunkyMesh, tbmin, tbmax, cid.data(), cid.size()); if (!ncid) return 0; { std::lock_guard<std::mutex> triareas_lock(m_triareas_mutex); for (int i = 0; i < ncid; ++i) { const rcChunkyTriMeshNode& node = chunkyMesh->nodes[cid[i]]; const int* ctris = &chunkyMesh->tris[node.i * 3]; const int nctris = node.n; std::fill(m_triareas.begin(), m_triareas.begin() + nctris, 0); markWalkableTrianglesWithHeightCheck(m_ctx.get(), cfg.walkableSlopeAngle, verts, nverts, ctris, nctris, m_triareas.data(), 1.0f); std::lock_guard<std::mutex> solid_lock(m_solid_mutex); if (!rcRasterizeTriangles(m_ctx.get(), verts, nverts, ctris, m_triareas.data(), nctris, *solid, cfg.walkableClimb)) return 0; } } if (chunkyWaterMesh) { std::vector<int> cid(512); const int nwcid = rcChunkyTriMesh::rcGetChunksOverlappingRect(chunkyWaterMesh, tbmin, tbmax, cid.data(), cid.size()); std::lock_guard<std::mutex> triareas_lock(m_triareas_mutex); std::lock_guard<std::mutex> solid_lock(m_solid_mutex); for (int i = 0; i < nwcid; ++i) { const rcChunkyTriMeshNode& node = chunkyWaterMesh->nodes[cid[i]]; const int* ctris = &chunkyWaterMesh->tris[node.i * 3]; const int nctris = node.n; std::fill(m_triareas.begin(), m_triareas.begin() + nctris, SAMPLE_POLYAREA_WATER); // Rasterize water triangles if (!rcRasterizeTriangles(m_ctx.get(), waterverts, waternverts, ctris, m_triareas.data(), nctris, *solid, cfg.walkableClimb)) return 0; } // Mark water areas in the heightfield for (int y = 0; y < solid->height; ++y) { for (int x = 0; x < solid->width; ++x) { for (rcSpan* s = solid->spans[x + y * solid->width]; s; s = s->next) { if (s->area == SAMPLE_POLYAREA_WATER) { // Keep it as SAMPLE_POLYAREA_WATER instead of changing to RC_WALKABLE_AREA s->area = SAMPLE_POLYAREA_WATER; } } } } } //rcFilterLowHangingWalkableObstacles(m_ctx.get(), cfg.walkableClimb, *solid); //rcFilterLedgeSpans(m_ctx.get(), cfg.walkableHeight, cfg.walkableClimb, *solid); //rcFilterWalkableLowHeightSpans(m_ctx.get(), cfg.walkableHeight, *solid); // //// Additional step to ensure water areas are preserved //for (int y = 0; y < solid->height; ++y) { // for (int x = 0; x < solid->width; ++x) { // for (rcSpan* s = solid->spans[x + y * solid->width]; s; s = s->next) { // if (s->area == SAMPLE_POLYAREA_WATER) { // // Ensure water areas are not filtered out // s->area = SAMPLE_POLYAREA_WATER; // } // } // } //} chf = rcAllocCompactHeightfield(); if (!chf) { printf("NavMeshBuilder::buildTileMesh: buildNavigation: Out of memory 'chf'.\n"); cleanup(); return 0; } if (!rcBuildCompactHeightfield(m_ctx.get(), cfg.walkableHeight, cfg.walkableClimb, *solid, *chf)) { printf("NavMeshBuilder::buildTileMesh: Could not build compact data.\n"); cleanup(); return 0; } rcFreeHeightField(solid); solid = 0; if (!rcErodeWalkableArea(m_ctx.get(), cfg.walkableRadius, *chf)) { printf("NavMeshBuilder::buildTileMesh: Could not erode.\n"); cleanup(); return 0; } if (!rcBuildLayerRegions(m_ctx.get(), *chf, cfg.borderSize, cfg.minRegionArea)) { printf("NavMeshBuilder::buildTileMesh: Could not build layer regions.\n"); cleanup(); return 0; } cset = rcAllocContourSet(); if (!cset) { printf("NavMeshBuilder::buildTileMesh: Out of memory 'cset'.\n"); cleanup(); return 0; } if (!rcBuildContours(m_ctx.get(), *chf, cfg.maxSimplificationError, cfg.maxEdgeLen, *cset)) { printf("NavMeshBuilder::buildTileMesh: Could not create contours.\n"); cleanup(); return 0; } if (cset->nconts == 0) { return 0; } pmesh = rcAllocPolyMesh(); if (!pmesh) { printf("NavMeshBuilder::buildTileMesh: Out of memory 'pmesh'.\n"); cleanup(); return 0; } if (!rcBuildPolyMesh(m_ctx.get(), *cset, cfg.maxVertsPerPoly, *pmesh)) { printf("NavMeshBuilder::buildTileMesh: Could not triangulate contours.\n"); cleanup(); return 0; } for (int i = 0; i < pmesh->npolys; ++i) { if (pmesh->areas[i] == SAMPLE_POLYAREA_WATER) { pmesh->flags[i] = SAMPLE_POLYFLAGS_SWIM; } } dmesh = rcAllocPolyMeshDetail(); if (!dmesh) { printf("NavMeshBuilder::buildTileMesh: Out of memory 'dmesh'.\n"); cleanup(); return 0; } if (!rcBuildPolyMeshDetail(m_ctx.get(), *pmesh, *chf, cfg.detailSampleDist, cfg.detailSampleMaxError, *dmesh)) { printf("NavMeshBuilder::buildTileMesh: Could build polymesh detail.\n"); cleanup(); return 0; } rcFreeCompactHeightfield(chf); chf = 0; rcFreeContourSet(cset); cset = 0; unsigned char* navData = 0; int navDataSize = 0; if (cfg.maxVertsPerPoly <= DT_VERTS_PER_POLYGON) { if (pmesh->nverts >= 0xffff) { // The vertex indices are ushorts, and cannot point to more than 0xffff vertices. printf("NavMeshBuilder::buildTileMesh: Too many vertices per tile %d (max: %d).\n", pmesh->nverts, 0xffff); cleanup(); return 0; } // Update poly flags from areas. for (int i = 0; i < pmesh->npolys; ++i) { if (pmesh->areas[i] == RC_WALKABLE_AREA) pmesh->areas[i] = SAMPLE_POLYAREA_GROUND; if (pmesh->areas[i] == SAMPLE_POLYAREA_GROUND || pmesh->areas[i] == SAMPLE_POLYAREA_GRASS || pmesh->areas[i] == SAMPLE_POLYAREA_ROAD) { pmesh->flags[i] = SAMPLE_POLYFLAGS_WALK; } else if (pmesh->areas[i] == SAMPLE_POLYAREA_WATER) { pmesh->flags[i] = SAMPLE_POLYFLAGS_SWIM; } else if (pmesh->areas[i] == SAMPLE_POLYAREA_DOOR) { pmesh->flags[i] = SAMPLE_POLYFLAGS_WALK | SAMPLE_POLYFLAGS_DOOR; } } { std::lock_guard<std::mutex> lock(m_mutex); dtNavMeshCreateParams params; memset(&params, 0, sizeof(params)); params.verts = pmesh->verts; params.vertCount = pmesh->nverts; params.polys = pmesh->polys; params.polyAreas = pmesh->areas; params.polyFlags = pmesh->flags; params.polyCount = pmesh->npolys; params.nvp = pmesh->nvp; params.detailMeshes = dmesh->meshes; params.detailVerts = dmesh->verts; params.detailVertsCount = dmesh->nverts; params.detailTris = dmesh->tris; params.detailTriCount = dmesh->ntris; params.walkableHeight = m_agentHeight; params.walkableRadius = m_agentRadius; params.walkableClimb = m_agentMaxClimb; params.tileX = tx; params.tileY = ty; params.tileLayer = 0; rcVcopy(params.bmin, pmesh->bmin); rcVcopy(params.bmax, pmesh->bmax); params.cs = cfg.cs; params.ch = cfg.ch; params.buildBvTree = true; if (!dtCreateNavMeshData(&params, &navData, &navDataSize)) { printf("NavMeshBuilder::buildTileMesh: Could not build Detour navmesh.\n"); cleanup(); return 0; } } } //printf("NavMeshBuilder::buildTileMesh: >> Polymesh: %d vertices %d polygons\n", pmesh->nverts, pmesh->npolys); dataSize = navDataSize; cleanup(); return navData; } How to set swim flag in tilecache impl?