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); // Reapply water areas after filtering 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; } 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; } How to add swim flag to water mesh?