diff --git a/server/gameserver/navmeshbuilder.cc b/server/gameserver/navmeshbuilder.cc index d5bfb9f..db3c1d9 100644 --- a/server/gameserver/navmeshbuilder.cc +++ b/server/gameserver/navmeshbuilder.cc @@ -49,6 +49,39 @@ struct TileCacheData int dataSize; }; +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; @@ -106,18 +139,14 @@ struct FastLZCompressor : public dtTileCacheCompressor virtual dtStatus compress(const unsigned char* buffer, const int bufferSize, unsigned char* compressed, const int /*maxCompressedSize*/, int* compressedSize) { -#if 0 *compressedSize = fastlz_compress((const void *const)buffer, bufferSize, compressed); -#endif return DT_SUCCESS; } virtual dtStatus decompress(const unsigned char* compressed, const int compressedSize, unsigned char* buffer, const int maxBufferSize, int* bufferSize) { -#if 0 *bufferSize = fastlz_decompress(compressed, compressedSize, buffer, maxBufferSize); -#endif return *bufferSize < 0 ? DT_FAILURE : DT_SUCCESS; } }; @@ -164,72 +193,45 @@ void NavMeshBuilder::UnInit() } -dtNavMesh* NavMeshBuilder::Build(MapInstance* map_instance) +struct BuilderParams { float kCellSize = 64; - - // Init cache 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(bmin, bmax, kCellSize, &gw, &gh); + 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; - rcConfig cfg; - { - 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); - } - - dtTileCacheParams tcparams; - { - // 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; - } + InitRcConfig(builder_params); + InitTileCacheParams(builder_params); dtStatus status; dtTileCache* tile_cache = dtAllocTileCache(); - #if 0 - dtStatus status = tile_cache->init(&tcparams, m_talloc, m_tcomp, m_tmproc);; - #endif + 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, bmin); - params.tileWidth = kTileSize * kCellSize; - params.tileHeight = kTileSize * kCellSize; + rcVcopy(params.orig, builder_params.bmin); + params.tileWidth = kTileSize * builder_params.kCellSize; + params.tileHeight = kTileSize * builder_params.kCellSize; #if 0 params.maxTiles = kMaxTiles; params.maxPolys = kMaxPolysPerTile; @@ -253,11 +255,7 @@ dtNavMesh* NavMeshBuilder::Build(MapInstance* map_instance) { TileCacheData tiles[MAX_LAYERS]; memset(tiles, 0, sizeof(tiles)); - #if 1 - int ntiles = 0; - #else - int ntiles = rasterizeTileLayers(x, y, cfg, tiles, MAX_LAYERS); - #endif + int ntiles = RasterizeTileLayers(x, y, builder_params.cfg, tiles, MAX_LAYERS); for (int i = 0; i < ntiles; ++i) { @@ -280,9 +278,11 @@ dtNavMesh* NavMeshBuilder::Build(MapInstance* map_instance) } // Build initial meshes - for (int y = 0; y < th; ++y) - for (int x = 0; x < tw; ++x) + for (int y = 0; y < th; ++y) { + for (int x = 0; x < tw; ++x) { tile_cache->buildNavMeshTilesAt(x,y, navmesh); + } + } #if 0 m_cacheBuildTimeMs = m_ctx->getAccumulatedTime(RC_TIMER_TOTAL)/1000.0f; @@ -291,13 +291,12 @@ dtNavMesh* NavMeshBuilder::Build(MapInstance* map_instance) 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; - } - printf("navmeshMemUsage = %.1f kB", navmeshMemUsage/1024.0f); + for (int i = 0; i < nav->getMaxTiles(); ++i) { + const dtMeshTile* tile = nav->getTile(i); + if (tile->header) { + navmeshMemUsage += tile->dataSize; + } + } return nullptr; } @@ -402,3 +401,261 @@ void NavMeshBuilder::OutputObjFile(MapInstance* map_instance) 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 + + FastLZCompressor comp; + RasterizationContext rc; + + struct rcChunkyTriMesh; + #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 0 + if (!rcCreateHeightfield(m_ctx, *rc.solid, tcfg.width, tcfg.height, tcfg.bmin, tcfg.bmax, tcfg.cs, tcfg.ch)) { + return 0; + } + #endif + + // 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. + #if 0 + rc.triareas = new unsigned char[chunkyMesh->maxTrisPerChunk]; + #endif + if (!rc.triareas) { + #if 0 + m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'm_triareas' (%d).", chunkyMesh->maxTrisPerChunk); + #endif + 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. + #if 1 + const int ncid = 0; + #else + const int ncid = rcGetChunksOverlappingRect(chunkyMesh, tbmin, tbmax, cid, 512); + #endif + if (!ncid) { + return 0; // empty + } + + for (int i = 0; i < ncid; ++i) { + #if 0 + 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, tcfg.walkableSlopeAngle, + verts, nverts, tris, ntris, rc.triareas, + SAMPLE_AREAMOD_GROUND); + + if (!rcRasterizeTriangles(m_ctx, verts, nverts, tris, rc.triareas, ntris, *rc.solid, tcfg.walkableClimb)) + return 0; + #endif + } + + // 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) { + #if 0 + m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'chf'."); + #endif + return 0; + } + #if 0 + if (!rcBuildCompactHeightfield(m_ctx, tcfg.walkableHeight, tcfg.walkableClimb, *rc.solid, *rc.chf)) { + #if 0 + m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build compact data."); + #endif + return 0; + } + #endif + + // Erode the walkable area by agent radius. + #if 0 + if (!rcErodeWalkableArea(m_ctx, tcfg.walkableRadius, *rc.chf)) { + #if 0 + m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not erode."); + #endif + return 0; + } + #endif + + #if 0 + // (Optional) Mark areas. + const ConvexVolume* vols = m_geom->getConvexVolumes(); + for (int i = 0; i < m_geom->getConvexVolumeCount(); ++i) { + rcMarkConvexPolyArea(m_ctx, vols[i].verts, vols[i].nverts, + vols[i].hmin, vols[i].hmax, + vols[i].areaMod, *rc.chf); + } + #endif + + rc.lset = rcAllocHeightfieldLayerSet(); + if (!rc.lset) { + #if 0 + m_ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'lset'."); + #endif + return 0; + } + #if 0 + if (!rcBuildHeightfieldLayers(m_ctx, *rc.chf, tcfg.borderSize, tcfg.walkableHeight, *rc.lset)) { + #if 0 + m_ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build heighfield layers."); + #endif + return 0; + } + #endif + + 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; +} diff --git a/server/gameserver/navmeshbuilder.h b/server/gameserver/navmeshbuilder.h index c3e2c80..ae024fb 100644 --- a/server/gameserver/navmeshbuilder.h +++ b/server/gameserver/navmeshbuilder.h @@ -1,5 +1,9 @@ #pragma once +struct rcConfig; +struct dtTileCacheParams; +struct BuilderParams; +struct TileCacheData; class dtNavMesh; class MapInstance; class NavMeshBuilder : public a8::Singleton @@ -14,4 +18,12 @@ public: dtNavMesh* Build(MapInstance* map_instance); void OutputObjFile(MapInstance* map_instance); + + private: + void InitRcConfig(BuilderParams& builder_params); + void InitTileCacheParams(BuilderParams& builder_params); + int RasterizeTileLayers(const int tx, const int ty, + const rcConfig& cfg, + TileCacheData* tiles, + const int maxTiles); };