diff options
| author | Shauren <shauren.trinity@gmail.com> | 2025-10-24 00:13:50 +0200 |
|---|---|---|
| committer | Shauren <shauren.trinity@gmail.com> | 2025-10-24 00:13:50 +0200 |
| commit | 1eb581610bd558daebdd411a72156c32573aa8bd (patch) | |
| tree | 59e8a5f343511f92967ca1d2fa2304dd3c2910b2 /src | |
| parent | 4d693d250558daaafc7824203705c1e34623d297 (diff) | |
Tools/mmaps_generator: Move TileBuilder to its own file
Diffstat (limited to 'src')
| -rw-r--r-- | src/common/Collision/Management/MMapManager.cpp | 1 | ||||
| -rw-r--r-- | src/common/Collision/Management/VMapManager2.cpp | 7 | ||||
| -rw-r--r-- | src/common/Collision/Management/VMapManager2.h | 1 | ||||
| -rw-r--r-- | src/tools/mmaps_generator/MapBuilder.cpp | 514 | ||||
| -rw-r--r-- | src/tools/mmaps_generator/MapBuilder.h | 86 | ||||
| -rw-r--r-- | src/tools/mmaps_generator/PathCommon.h | 2 | ||||
| -rw-r--r-- | src/tools/mmaps_generator/PathGenerator.cpp | 26 | ||||
| -rw-r--r-- | src/tools/mmaps_generator/TerrainBuilder.cpp | 37 | ||||
| -rw-r--r-- | src/tools/mmaps_generator/TerrainBuilder.h | 28 | ||||
| -rw-r--r-- | src/tools/mmaps_generator/TileBuilder.cpp | 554 | ||||
| -rw-r--r-- | src/tools/mmaps_generator/TileBuilder.h | 84 |
11 files changed, 729 insertions, 611 deletions
diff --git a/src/common/Collision/Management/MMapManager.cpp b/src/common/Collision/Management/MMapManager.cpp index 8c926d79a6e..c10d64bfd36 100644 --- a/src/common/Collision/Management/MMapManager.cpp +++ b/src/common/Collision/Management/MMapManager.cpp @@ -21,6 +21,7 @@ #include "Log.h" #include "MMapDefines.h" #include "Memory.h" +#include <algorithm> namespace MMAP { diff --git a/src/common/Collision/Management/VMapManager2.cpp b/src/common/Collision/Management/VMapManager2.cpp index 93a1f7e4995..481b1cdb0ea 100644 --- a/src/common/Collision/Management/VMapManager2.cpp +++ b/src/common/Collision/Management/VMapManager2.cpp @@ -88,6 +88,13 @@ namespace VMAP thread_safe_environment = false; } + void VMapManager2::InitializeThreadUnsafe(uint32 mapId, int32 parentMapId) + { + iInstanceMapTrees[mapId] = nullptr; + if (parentMapId >= 0) + iParentMapData[mapId] = parentMapId; + } + Vector3 VMapManager2::convertPositionToInternalRep(float x, float y, float z) const { Vector3 pos; diff --git a/src/common/Collision/Management/VMapManager2.h b/src/common/Collision/Management/VMapManager2.h index 5abe003056c..8fbc9b05ed1 100644 --- a/src/common/Collision/Management/VMapManager2.h +++ b/src/common/Collision/Management/VMapManager2.h @@ -84,6 +84,7 @@ namespace VMAP ~VMapManager2(); void InitializeThreadUnsafe(std::unordered_map<uint32, std::vector<uint32>> const& mapData); + void InitializeThreadUnsafe(uint32 mapId, int32 parentMapId); LoadResult loadMap(char const* pBasePath, unsigned int mapId, int x, int y) override; diff --git a/src/tools/mmaps_generator/MapBuilder.cpp b/src/tools/mmaps_generator/MapBuilder.cpp index 988abae73b8..354cdce9e2e 100644 --- a/src/tools/mmaps_generator/MapBuilder.cpp +++ b/src/tools/mmaps_generator/MapBuilder.cpp @@ -16,21 +16,16 @@ */ #include "MapBuilder.h" -#include "IntermediateValues.h" #include "Log.h" #include "MapDefines.h" #include "MapTree.h" #include "MapUtils.h" #include "Memory.h" #include "MMapDefines.h" -#include "ModelInstance.h" #include "PathCommon.h" #include "StringConvert.h" #include "StringFormat.h" -#include <DetourNavMesh.h> -#include <DetourNavMeshBuilder.h> #include <boost/filesystem/directory.hpp> -#include <climits> namespace FileExtensions { @@ -40,36 +35,33 @@ static boost::filesystem::path vmtile = ".vmtile"; namespace MMAP { - TileBuilder::TileBuilder(MapBuilder* mapBuilder, bool skipLiquid, bool bigBaseUnit, bool debugOutput) : - m_bigBaseUnit(bigBaseUnit), - m_debugOutput(debugOutput), + MapTileBuilder::MapTileBuilder(MapBuilder* mapBuilder, Optional<float> maxWalkableAngle, Optional<float> maxWalkableAngleNotSteep, + bool skipLiquid, bool bigBaseUnit, bool debugOutput, std::vector<OffMeshData> const* offMeshConnections) : + TileBuilder(maxWalkableAngle, maxWalkableAngleNotSteep, skipLiquid, bigBaseUnit, debugOutput, offMeshConnections), m_mapBuilder(mapBuilder), - m_terrainBuilder(nullptr), - m_workerThread(&TileBuilder::WorkerThread, this), - m_rcContext(nullptr) + m_workerThread(&MapTileBuilder::WorkerThread, this) { - m_terrainBuilder = new TerrainBuilder(skipLiquid); - m_rcContext = new rcContext(false); } - TileBuilder::~TileBuilder() + MapTileBuilder::~MapTileBuilder() { WaitCompletion(); - - delete m_terrainBuilder; - delete m_rcContext; } - void TileBuilder::WaitCompletion() + void MapTileBuilder::WaitCompletion() { if (m_workerThread.joinable()) m_workerThread.join(); } + void MapTileBuilder::OnTileDone() + { + ++m_mapBuilder->m_totalTilesProcessed; + } + MapBuilder::MapBuilder(Optional<float> maxWalkableAngle, Optional<float> maxWalkableAngleNotSteep, bool skipLiquid, bool skipContinents, bool skipJunkMaps, bool skipBattlegrounds, bool debugOutput, bool bigBaseUnit, int mapid, char const* offMeshFilePath, unsigned int threads) : - m_terrainBuilder (nullptr), m_debugOutput (debugOutput), m_threads (threads), m_skipContinents (skipContinents), @@ -82,12 +74,8 @@ namespace MMAP m_mapid (mapid), m_totalTiles (0u), m_totalTilesProcessed(0u), - m_rcContext (nullptr), _cancelationToken (false) { - m_terrainBuilder = new TerrainBuilder(skipLiquid); - - m_rcContext = new rcContext(false); // At least 1 thread is needed m_threads = std::max(1u, m_threads); @@ -109,9 +97,6 @@ namespace MMAP m_tileBuilders.clear(); m_tiles.clear(); - - delete m_terrainBuilder; - delete m_rcContext; } /**************************************************************************/ @@ -238,7 +223,7 @@ namespace MMAP /**************************************************************************/ - void TileBuilder::WorkerThread() + void MapTileBuilder::WorkerThread() { while (true) { @@ -269,7 +254,8 @@ namespace MMAP for (unsigned int i = 0; i < m_threads; ++i) { - m_tileBuilders.push_back(new TileBuilder(this, m_skipLiquid, m_bigBaseUnit, m_debugOutput)); + m_tileBuilders.push_back(new MapTileBuilder(this, m_maxWalkableAngle, m_maxWalkableAngleNotSteep, + m_skipLiquid, m_bigBaseUnit, m_debugOutput, &m_offMeshConnections)); } if (mapID) @@ -376,10 +362,11 @@ namespace MMAP TerrainBuilder::cleanVertices(data.solidVerts, data.solidTris); // get bounds of current tile float bmin[3], bmax[3]; - getTileBounds(tileX, tileY, data.solidVerts.getCArray(), data.solidVerts.size() / 3, bmin, bmax); + TileBuilder::getTileBounds(tileX, tileY, data.solidVerts.getCArray(), data.solidVerts.size() / 3, bmin, bmax); // build navmesh tile - TileBuilder tileBuilder = TileBuilder(this, m_skipLiquid, m_bigBaseUnit, m_debugOutput); + MapTileBuilder tileBuilder(this, m_maxWalkableAngle, m_maxWalkableAngleNotSteep, + m_skipLiquid, m_bigBaseUnit, m_debugOutput, &m_offMeshConnections); tileBuilder.buildMoveMapTile(mapId, tileX, tileY, data, bmin, bmax, navMesh); fclose(file); } @@ -397,7 +384,8 @@ namespace MMAP // ToDo: delete the old tile as the user clearly wants to rebuild it - TileBuilder tileBuilder = TileBuilder(this, m_skipLiquid, m_bigBaseUnit, m_debugOutput); + MapTileBuilder tileBuilder(this, m_maxWalkableAngle, m_maxWalkableAngleNotSteep, + m_skipLiquid, m_bigBaseUnit, m_debugOutput, &m_offMeshConnections); tileBuilder.buildTile(mapID, tileX, tileY, navMesh); dtFreeNavMesh(navMesh); @@ -445,59 +433,6 @@ namespace MMAP } /**************************************************************************/ - void TileBuilder::buildTile(uint32 mapID, uint32 tileX, uint32 tileY, dtNavMesh* navMesh) - { - if(shouldSkipTile(mapID, tileX, tileY)) - { - ++m_mapBuilder->m_totalTilesProcessed; - return; - } - - TC_LOG_INFO("maps.mmapgen", "{}% [Map {:04}] Building tile [{:02},{:02}]", m_mapBuilder->currentPercentageDone(), mapID, tileX, tileY); - - MeshData meshData; - - // get heightmap data - m_terrainBuilder->loadMap(mapID, tileX, tileY, meshData); - - // get model data - m_terrainBuilder->loadVMap(mapID, tileY, tileX, meshData); - - // if there is no data, give up now - if (!meshData.solidVerts.size() && !meshData.liquidVerts.size()) - { - ++m_mapBuilder->m_totalTilesProcessed; - return; - } - - // remove unused vertices - TerrainBuilder::cleanVertices(meshData.solidVerts, meshData.solidTris); - TerrainBuilder::cleanVertices(meshData.liquidVerts, meshData.liquidTris); - - // gather all mesh data for final data check, and bounds calculation - G3D::Array<float> allVerts; - allVerts.append(meshData.liquidVerts); - allVerts.append(meshData.solidVerts); - - if (!allVerts.size()) - { - ++m_mapBuilder->m_totalTilesProcessed; - return; - } - - // get bounds of current tile - float bmin[3], bmax[3]; - m_mapBuilder->getTileBounds(tileX, tileY, allVerts.getCArray(), allVerts.size() / 3, bmin, bmax); - - m_terrainBuilder->loadOffMeshConnections(mapID, tileX, tileY, meshData, m_mapBuilder->m_offMeshConnections); - - // build navmesh tile - buildMoveMapTile(mapID, tileX, tileY, meshData, bmin, bmax, navMesh); - - ++m_mapBuilder->m_totalTilesProcessed; - } - - /**************************************************************************/ void MapBuilder::buildNavMesh(uint32 mapID, dtNavMesh* &navMesh) { // if map has a parent we use that to generate dtNavMeshParams - worldserver will load all missing tiles from that map @@ -542,7 +477,7 @@ namespace MMAP // use Max because '32 - tileX' is negative for values over 32 float bmin[3], bmax[3]; - getTileBounds(tileXMax, tileYMax, nullptr, 0, bmin, bmax); + TileBuilder::getTileBounds(tileXMax, tileYMax, nullptr, 0, bmin, bmax); /*** now create the navmesh ***/ @@ -580,361 +515,6 @@ namespace MMAP } /**************************************************************************/ - void TileBuilder::buildMoveMapTile(uint32 mapID, uint32 tileX, uint32 tileY, - MeshData &meshData, float bmin[3], float bmax[3], - dtNavMesh* navMesh) - { - // console output - std::string tileString = Trinity::StringFormat("[Map {:04}] [{:02},{:02}]: ", mapID, tileX, tileY); - TC_LOG_INFO("maps.mmapgen", "{} Building movemap tile...", tileString); - - IntermediateValues iv; - - float* tVerts = meshData.solidVerts.getCArray(); - int tVertCount = meshData.solidVerts.size() / 3; - int* tTris = meshData.solidTris.getCArray(); - int tTriCount = meshData.solidTris.size() / 3; - - float* lVerts = meshData.liquidVerts.getCArray(); - int lVertCount = meshData.liquidVerts.size() / 3; - int* lTris = meshData.liquidTris.getCArray(); - int lTriCount = meshData.liquidTris.size() / 3; - uint8* lTriFlags = meshData.liquidType.getCArray(); - - const TileConfig tileConfig = TileConfig(m_bigBaseUnit); - int TILES_PER_MAP = tileConfig.TILES_PER_MAP; - float BASE_UNIT_DIM = tileConfig.BASE_UNIT_DIM; - rcConfig config = m_mapBuilder->GetMapSpecificConfig(mapID, bmin, bmax, tileConfig); - - // this sets the dimensions of the heightfield - should maybe happen before border padding - rcCalcGridSize(config.bmin, config.bmax, config.cs, &config.width, &config.height); - - // allocate subregions : tiles - Tile* tiles = new Tile[TILES_PER_MAP * TILES_PER_MAP]; - - // Initialize per tile config. - rcConfig tileCfg = config; - tileCfg.width = config.tileSize + config.borderSize*2; - tileCfg.height = config.tileSize + config.borderSize*2; - - // merge per tile poly and detail meshes - rcPolyMesh** pmmerge = new rcPolyMesh*[TILES_PER_MAP * TILES_PER_MAP]; - rcPolyMeshDetail** dmmerge = new rcPolyMeshDetail*[TILES_PER_MAP * TILES_PER_MAP]; - int nmerge = 0; - // build all tiles - for (int y = 0; y < TILES_PER_MAP; ++y) - { - for (int x = 0; x < TILES_PER_MAP; ++x) - { - Tile& tile = tiles[x + y * TILES_PER_MAP]; - - // Calculate the per tile bounding box. - tileCfg.bmin[0] = config.bmin[0] + x * float(config.tileSize * config.cs); - tileCfg.bmin[2] = config.bmin[2] + y * float(config.tileSize * config.cs); - tileCfg.bmax[0] = config.bmin[0] + (x + 1) * float(config.tileSize * config.cs); - tileCfg.bmax[2] = config.bmin[2] + (y + 1) * float(config.tileSize * config.cs); - - tileCfg.bmin[0] -= tileCfg.borderSize * tileCfg.cs; - tileCfg.bmin[2] -= tileCfg.borderSize * tileCfg.cs; - tileCfg.bmax[0] += tileCfg.borderSize * tileCfg.cs; - tileCfg.bmax[2] += tileCfg.borderSize * tileCfg.cs; - - // build heightfield - tile.solid = rcAllocHeightfield(); - if (!tile.solid || !rcCreateHeightfield(m_rcContext, *tile.solid, tileCfg.width, tileCfg.height, tileCfg.bmin, tileCfg.bmax, tileCfg.cs, tileCfg.ch)) - { - TC_LOG_ERROR("maps.mmapgen", "{} Failed building heightfield!", tileString); - continue; - } - - // mark all walkable tiles, both liquids and solids - - /* we want to have triangles with slope less than walkableSlopeAngleNotSteep (<= 55) to have NAV_AREA_GROUND - * and with slope between walkableSlopeAngleNotSteep and walkableSlopeAngle (55 < .. <= 70) to have NAV_AREA_GROUND_STEEP. - * we achieve this using recast API: memset everything to NAV_AREA_GROUND_STEEP, call rcClearUnwalkableTriangles with 70 so - * any area above that will get RC_NULL_AREA (unwalkable), then call rcMarkWalkableTriangles with 55 to set NAV_AREA_GROUND - * on anything below 55 . Players and idle Creatures can use NAV_AREA_GROUND, while Creatures in combat can use NAV_AREA_GROUND_STEEP. - */ - unsigned char* triFlags = new unsigned char[tTriCount]; - memset(triFlags, NAV_AREA_GROUND_STEEP, tTriCount*sizeof(unsigned char)); - rcClearUnwalkableTriangles(m_rcContext, tileCfg.walkableSlopeAngle, tVerts, tVertCount, tTris, tTriCount, triFlags); - rcMarkWalkableTriangles(m_rcContext, tileCfg.walkableSlopeAngleNotSteep, tVerts, tVertCount, tTris, tTriCount, triFlags, NAV_AREA_GROUND); - rcRasterizeTriangles(m_rcContext, tVerts, tVertCount, tTris, triFlags, tTriCount, *tile.solid, config.walkableClimb); - delete[] triFlags; - - rcFilterLowHangingWalkableObstacles(m_rcContext, config.walkableClimb, *tile.solid); - rcFilterLedgeSpans(m_rcContext, tileCfg.walkableHeight, tileCfg.walkableClimb, *tile.solid); - rcFilterWalkableLowHeightSpans(m_rcContext, tileCfg.walkableHeight, *tile.solid); - - // add liquid triangles - rcRasterizeTriangles(m_rcContext, lVerts, lVertCount, lTris, lTriFlags, lTriCount, *tile.solid, config.walkableClimb); - - // compact heightfield spans - tile.chf = rcAllocCompactHeightfield(); - if (!tile.chf || !rcBuildCompactHeightfield(m_rcContext, tileCfg.walkableHeight, tileCfg.walkableClimb, *tile.solid, *tile.chf)) - { - TC_LOG_ERROR("maps.mmapgen", "{} Failed compacting heightfield!", tileString); - continue; - } - - // build polymesh intermediates - if (!rcErodeWalkableArea(m_rcContext, config.walkableRadius, *tile.chf)) - { - TC_LOG_ERROR("maps.mmapgen", "{} Failed eroding area!", tileString); - continue; - } - - if (!rcMedianFilterWalkableArea(m_rcContext, *tile.chf)) - { - TC_LOG_ERROR("maps.mmapgen", "{} Failed filtering area!", tileString); - continue; - } - - if (!rcBuildDistanceField(m_rcContext, *tile.chf)) - { - TC_LOG_ERROR("maps.mmapgen", "{} Failed building distance field!", tileString); - continue; - } - - if (!rcBuildRegions(m_rcContext, *tile.chf, tileCfg.borderSize, tileCfg.minRegionArea, tileCfg.mergeRegionArea)) - { - TC_LOG_ERROR("maps.mmapgen", "{} Failed building regions!", tileString); - continue; - } - - tile.cset = rcAllocContourSet(); - if (!tile.cset || !rcBuildContours(m_rcContext, *tile.chf, tileCfg.maxSimplificationError, tileCfg.maxEdgeLen, *tile.cset)) - { - TC_LOG_ERROR("maps.mmapgen", "{} Failed building contours!", tileString); - continue; - } - - // build polymesh - tile.pmesh = rcAllocPolyMesh(); - if (!tile.pmesh || !rcBuildPolyMesh(m_rcContext, *tile.cset, tileCfg.maxVertsPerPoly, *tile.pmesh)) - { - TC_LOG_ERROR("maps.mmapgen", "{} Failed building polymesh!", tileString); - continue; - } - - tile.dmesh = rcAllocPolyMeshDetail(); - if (!tile.dmesh || !rcBuildPolyMeshDetail(m_rcContext, *tile.pmesh, *tile.chf, tileCfg.detailSampleDist, tileCfg.detailSampleMaxError, *tile.dmesh)) - { - TC_LOG_ERROR("maps.mmapgen", "{} Failed building polymesh detail!", tileString); - continue; - } - - // free those up - // we may want to keep them in the future for debug - // but right now, we don't have the code to merge them - rcFreeHeightField(tile.solid); - tile.solid = nullptr; - rcFreeCompactHeightfield(tile.chf); - tile.chf = nullptr; - rcFreeContourSet(tile.cset); - tile.cset = nullptr; - - pmmerge[nmerge] = tile.pmesh; - dmmerge[nmerge] = tile.dmesh; - nmerge++; - } - } - - iv.polyMesh = rcAllocPolyMesh(); - if (!iv.polyMesh) - { - TC_LOG_ERROR("maps.mmapgen", "{} alloc iv.polyMesh FAILED!", tileString); - delete[] pmmerge; - delete[] dmmerge; - delete[] tiles; - return; - } - rcMergePolyMeshes(m_rcContext, pmmerge, nmerge, *iv.polyMesh); - - iv.polyMeshDetail = rcAllocPolyMeshDetail(); - if (!iv.polyMeshDetail) - { - TC_LOG_ERROR("maps.mmapgen", "{} alloc m_dmesh FAILED!", tileString); - delete[] pmmerge; - delete[] dmmerge; - delete[] tiles; - return; - } - rcMergePolyMeshDetails(m_rcContext, dmmerge, nmerge, *iv.polyMeshDetail); - - // free things up - delete[] pmmerge; - delete[] dmmerge; - delete[] tiles; - - // set polygons as walkable - // TODO: special flags for DYNAMIC polygons, ie surfaces that can be turned on and off - for (int i = 0; i < iv.polyMesh->npolys; ++i) - { - if (uint8 area = iv.polyMesh->areas[i] & NAV_AREA_ALL_MASK) - { - if (area >= NAV_AREA_MIN_VALUE) - iv.polyMesh->flags[i] = 1 << (NAV_AREA_MAX_VALUE - area); - else - iv.polyMesh->flags[i] = NAV_GROUND; // TODO: these will be dynamic in future - } - } - - // setup mesh parameters - dtNavMeshCreateParams params; - memset(¶ms, 0, sizeof(params)); - params.verts = iv.polyMesh->verts; - params.vertCount = iv.polyMesh->nverts; - params.polys = iv.polyMesh->polys; - params.polyAreas = iv.polyMesh->areas; - params.polyFlags = iv.polyMesh->flags; - params.polyCount = iv.polyMesh->npolys; - params.nvp = iv.polyMesh->nvp; - params.detailMeshes = iv.polyMeshDetail->meshes; - params.detailVerts = iv.polyMeshDetail->verts; - params.detailVertsCount = iv.polyMeshDetail->nverts; - params.detailTris = iv.polyMeshDetail->tris; - params.detailTriCount = iv.polyMeshDetail->ntris; - - params.offMeshConVerts = meshData.offMeshConnections.getCArray(); - params.offMeshConCount = meshData.offMeshConnections.size()/6; - params.offMeshConRad = meshData.offMeshConnectionRads.getCArray(); - params.offMeshConDir = meshData.offMeshConnectionDirs.getCArray(); - params.offMeshConAreas = meshData.offMeshConnectionsAreas.getCArray(); - params.offMeshConFlags = meshData.offMeshConnectionsFlags.getCArray(); - - params.walkableHeight = BASE_UNIT_DIM*config.walkableHeight; // agent height - params.walkableRadius = BASE_UNIT_DIM*config.walkableRadius; // agent radius - params.walkableClimb = BASE_UNIT_DIM*config.walkableClimb; // keep less that walkableHeight (aka agent height)! - params.tileX = (((bmin[0] + bmax[0]) / 2) - navMesh->getParams()->orig[0]) / GRID_SIZE; - params.tileY = (((bmin[2] + bmax[2]) / 2) - navMesh->getParams()->orig[2]) / GRID_SIZE; - rcVcopy(params.bmin, bmin); - rcVcopy(params.bmax, bmax); - params.cs = config.cs; - params.ch = config.ch; - params.tileLayer = 0; - params.buildBvTree = true; - - // will hold final navmesh - unsigned char* navData = nullptr; - int navDataSize = 0; - - do - { - // these values are checked within dtCreateNavMeshData - handle them here - // so we have a clear error message - if (params.nvp > DT_VERTS_PER_POLYGON) - { - TC_LOG_ERROR("maps.mmapgen", "{} Invalid verts-per-polygon value!", tileString); - break; - } - if (params.vertCount >= 0xffff) - { - TC_LOG_ERROR("maps.mmapgen", "{} Too many vertices!", tileString); - break; - } - if (!params.vertCount || !params.verts) - { - // occurs mostly when adjacent tiles have models - // loaded but those models don't span into this tile - - // message is an annoyance - //TC_LOG_ERROR("maps.mmapgen", "{} No vertices to build tile!", tileString); - break; - } - if (!params.polyCount || !params.polys) - { - // we have flat tiles with no actual geometry - don't build those, its useless - // keep in mind that we do output those into debug info - TC_LOG_ERROR("maps.mmapgen", "{} No polygons to build on tile!", tileString); - break; - } - if (!params.detailMeshes || !params.detailVerts || !params.detailTris) - { - TC_LOG_ERROR("maps.mmapgen", "{} No detail mesh to build tile!", tileString); - break; - } - - TC_LOG_DEBUG("maps.mmapgen", "{} Building navmesh tile...", tileString); - if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize)) - { - TC_LOG_ERROR("maps.mmapgen", "{} Failed building navmesh tile!", tileString); - break; - } - - dtTileRef tileRef = 0; - TC_LOG_DEBUG("maps.mmapgen", "{} Adding tile to navmesh...", tileString); - // DT_TILE_FREE_DATA tells detour to unallocate memory when the tile - // is removed via removeTile() - dtStatus dtResult = navMesh->addTile(navData, navDataSize, DT_TILE_FREE_DATA, 0, &tileRef); - if (!tileRef || !dtStatusSucceed(dtResult)) - { - TC_LOG_ERROR("maps.mmapgen", "{} Failed adding tile to navmesh!", tileString); - break; - } - - // file output - std::string fileName = Trinity::StringFormat("mmaps/{:04}{:02}{:02}.mmtile", mapID, tileY, tileX); - FILE* file = fopen(fileName.c_str(), "wb"); - if (!file) - { - TC_LOG_ERROR("maps.mmapgen", "{}: [Map {:04}] Failed to open {} for writing!", strerror(errno), mapID, fileName); - navMesh->removeTile(tileRef, nullptr, nullptr); - break; - } - - TC_LOG_DEBUG("maps.mmapgen", "{} Writing to file...", tileString); - - // write header - MmapTileHeader header; - header.usesLiquids = m_terrainBuilder->usesLiquids(); - header.size = uint32(navDataSize); - fwrite(&header, sizeof(MmapTileHeader), 1, file); - - // write data - fwrite(navData, sizeof(unsigned char), navDataSize, file); - fclose(file); - - // now that tile is written to disk, we can unload it - navMesh->removeTile(tileRef, nullptr, nullptr); - } - while (false); - - if (m_debugOutput) - { - // restore padding so that the debug visualization is correct - for (int i = 0; i < iv.polyMesh->nverts; ++i) - { - unsigned short* v = &iv.polyMesh->verts[i*3]; - v[0] += (unsigned short)config.borderSize; - v[2] += (unsigned short)config.borderSize; - } - - iv.generateObjFile(mapID, tileX, tileY, meshData); - iv.writeIV(mapID, tileX, tileY); - } - } - - /**************************************************************************/ - void MapBuilder::getTileBounds(uint32 tileX, uint32 tileY, float* verts, int vertCount, float* bmin, float* bmax) const - { - // this is for elevation - if (verts && vertCount) - rcCalcBounds(verts, vertCount, bmin, bmax); - else - { - bmin[1] = FLT_MIN; - bmax[1] = FLT_MAX; - } - - // this is for width and depth - bmax[0] = (32 - int(tileX)) * GRID_SIZE; - bmax[2] = (32 - int(tileY)) * GRID_SIZE; - bmin[0] = bmax[0] - GRID_SIZE; - bmin[2] = bmax[2] - GRID_SIZE; - } - - /**************************************************************************/ bool MapBuilder::shouldSkipMap(uint32 mapID) const { if (m_mapid >= 0) @@ -1020,7 +600,7 @@ namespace MMAP } /**************************************************************************/ - bool TileBuilder::shouldSkipTile(uint32 mapID, uint32 tileX, uint32 tileY) const + bool MapTileBuilder::shouldSkipTile(uint32 mapID, uint32 tileX, uint32 tileY) const { std::string fileName = Trinity::StringFormat("mmaps/{:04}{:02}{:02}.mmtile", mapID, tileY, tileX); FILE* file = fopen(fileName.c_str(), "rb"); @@ -1039,58 +619,12 @@ namespace MMAP if (header.mmapVersion != MMAP_VERSION) return false; - if (m_debugOutput) - return false; - - return true; + return TileBuilder::shouldSkipTile(mapID, tileX, tileY); } - rcConfig MapBuilder::GetMapSpecificConfig(uint32 mapID, float bmin[3], float bmax[3], const TileConfig &tileConfig) const + std::string MapTileBuilder::GetProgressText() const { - rcConfig config; - memset(&config, 0, sizeof(rcConfig)); - - rcVcopy(config.bmin, bmin); - rcVcopy(config.bmax, bmax); - - config.maxVertsPerPoly = DT_VERTS_PER_POLYGON; - config.cs = tileConfig.BASE_UNIT_DIM; - config.ch = tileConfig.BASE_UNIT_DIM; - // Keeping these 2 slope angles the same reduces a lot the number of polys. - // 55 should be the minimum, maybe 70 is ok (keep in mind blink uses mmaps), 85 is too much for players - config.walkableSlopeAngle = m_maxWalkableAngle ? *m_maxWalkableAngle : 55; - config.walkableSlopeAngleNotSteep = m_maxWalkableAngleNotSteep ? *m_maxWalkableAngleNotSteep : 55; - config.tileSize = tileConfig.VERTEX_PER_TILE; - config.walkableRadius = m_bigBaseUnit ? 1 : 2; - config.borderSize = config.walkableRadius + 3; - config.maxEdgeLen = tileConfig.VERTEX_PER_TILE + 1; // anything bigger than tileSize - config.walkableHeight = m_bigBaseUnit ? 3 : 6; - // a value >= 3|6 allows npcs to walk over some fences - // a value >= 4|8 allows npcs to walk over all fences - config.walkableClimb = m_bigBaseUnit ? 3 : 6; - config.minRegionArea = rcSqr(60); - config.mergeRegionArea = rcSqr(50); - config.maxSimplificationError = 1.8f; // eliminates most jagged edges (tiny polygons) - config.detailSampleDist = config.cs * 16; - config.detailSampleMaxError = config.ch * 1; - - switch (mapID) - { - // Blade's Edge Arena - case 562: - // This allows to walk on the ropes to the pillars - config.walkableRadius = 0; - break; - // Blackfathom Deeps - case 48: - // Reduce the chance to have underground levels - config.ch *= 2; - break; - default: - break; - } - - return config; + return Trinity::StringFormat("{}%", m_mapBuilder->currentPercentageDone()); } /**************************************************************************/ diff --git a/src/tools/mmaps_generator/MapBuilder.h b/src/tools/mmaps_generator/MapBuilder.h index 388dc53a072..34c3a113274 100644 --- a/src/tools/mmaps_generator/MapBuilder.h +++ b/src/tools/mmaps_generator/MapBuilder.h @@ -22,8 +22,8 @@ #include "Optional.h" #include "ProducerConsumerQueue.h" #include "TerrainBuilder.h" +#include "TileBuilder.h" #include <DetourNavMesh.h> -#include <Recast.h> #include <atomic> #include <span> #include <thread> @@ -33,45 +33,6 @@ namespace MMAP { typedef std::unordered_map<uint32, Trinity::Containers::FlatSet<uint32>> TileList; - struct Tile - { - Tile() : chf(nullptr), solid(nullptr), cset(nullptr), pmesh(nullptr), dmesh(nullptr) {} - ~Tile() - { - rcFreeCompactHeightfield(chf); - rcFreeContourSet(cset); - rcFreeHeightField(solid); - rcFreePolyMesh(pmesh); - rcFreePolyMeshDetail(dmesh); - } - rcCompactHeightfield* chf; - rcHeightfield* solid; - rcContourSet* cset; - rcPolyMesh* pmesh; - rcPolyMeshDetail* dmesh; - }; - - struct TileConfig - { - TileConfig(bool bigBaseUnit) - { - // these are WORLD UNIT based metrics - // this are basic unit dimentions - // value have to divide GRID_SIZE(533.3333f) ( aka: 0.5333, 0.2666, 0.3333, 0.1333, etc ) - BASE_UNIT_DIM = bigBaseUnit ? 0.5333333f : 0.2666666f; - - // All are in UNIT metrics! - VERTEX_PER_MAP = int(GRID_SIZE / BASE_UNIT_DIM + 0.5f); - VERTEX_PER_TILE = bigBaseUnit ? 40 : 80; // must divide VERTEX_PER_MAP - TILES_PER_MAP = VERTEX_PER_MAP / VERTEX_PER_TILE; - } - - float BASE_UNIT_DIM; - int VERTEX_PER_MAP; - int VERTEX_PER_TILE; - int TILES_PER_MAP; - }; - struct TileInfo { TileInfo() : m_mapId(uint32(-1)), m_tileX(), m_tileY(), m_navMeshParams() {} @@ -84,46 +45,37 @@ namespace MMAP // ToDo: move this to its own file. For now it will stay here to keep the changes to a minimum, especially in the cpp file class MapBuilder; - class TileBuilder + + class MapTileBuilder : public TileBuilder { public: - TileBuilder(MapBuilder* mapBuilder, + MapTileBuilder(MapBuilder* mapBuilder, + Optional<float> maxWalkableAngle, + Optional<float> maxWalkableAngleNotSteep, bool skipLiquid, bool bigBaseUnit, - bool debugOutput); - - TileBuilder(TileBuilder&&) = default; - ~TileBuilder(); + bool debugOutput, + std::vector<OffMeshData> const* offMeshConnections); + ~MapTileBuilder(); void WorkerThread(); void WaitCompletion(); - void buildTile(uint32 mapID, uint32 tileX, uint32 tileY, dtNavMesh* navMesh); - // move map building - void buildMoveMapTile(uint32 mapID, - uint32 tileX, - uint32 tileY, - MeshData& meshData, - float bmin[3], - float bmax[3], - dtNavMesh* navMesh); + bool shouldSkipTile(uint32 mapID, uint32 tileX, uint32 tileY) const override; - bool shouldSkipTile(uint32 mapID, uint32 tileX, uint32 tileY) const; + std::string GetProgressText() const override; - private: - bool m_bigBaseUnit; - bool m_debugOutput; + void OnTileDone() override; + private: MapBuilder* m_mapBuilder; - TerrainBuilder* m_terrainBuilder; std::thread m_workerThread; - // build performance - not really used for now - rcContext* m_rcContext; }; class MapBuilder { friend class TileBuilder; + friend class MapTileBuilder; public: MapBuilder(Optional<float> maxWalkableAngle, @@ -157,24 +109,17 @@ namespace MMAP void buildNavMesh(uint32 mapID, dtNavMesh* &navMesh); - void getTileBounds(uint32 tileX, uint32 tileY, - float* verts, int vertCount, - float* bmin, float* bmax) const; - bool shouldSkipMap(uint32 mapID) const; bool isTransportMap(uint32 mapID) const; bool isDevMap(uint32 mapID) const; bool isBattlegroundMap(uint32 mapID) const; bool isContinentMap(uint32 mapID) const; - rcConfig GetMapSpecificConfig(uint32 mapID, float bmin[3], float bmax[3], const TileConfig &tileConfig) const; - uint32 percentageDone(uint32 totalTiles, uint32 totalTilesDone) const; uint32 currentPercentageDone() const; void ParseOffMeshConnectionsFile(char const* offMeshFilePath); - TerrainBuilder* m_terrainBuilder; TileList m_tiles; bool m_debugOutput; @@ -195,9 +140,6 @@ namespace MMAP uint32 m_totalTiles; std::atomic<uint32> m_totalTilesProcessed; - // build performance - not really used for now - rcContext* m_rcContext; - std::vector<TileBuilder*> m_tileBuilders; ProducerConsumerQueue<TileInfo> _queue; std::atomic<bool> _cancelationToken; diff --git a/src/tools/mmaps_generator/PathCommon.h b/src/tools/mmaps_generator/PathCommon.h index 17d354a0997..b7c05fe1541 100644 --- a/src/tools/mmaps_generator/PathCommon.h +++ b/src/tools/mmaps_generator/PathCommon.h @@ -141,7 +141,7 @@ namespace MMAP namespace VMapFactory { - std::unique_ptr<VMAP::VMapManager2> CreateVMapManager(); + std::unique_ptr<VMAP::VMapManager2> CreateVMapManager(uint32 mapId); } } diff --git a/src/tools/mmaps_generator/PathGenerator.cpp b/src/tools/mmaps_generator/PathGenerator.cpp index b08c2fc76ca..1e98b19a852 100644 --- a/src/tools/mmaps_generator/PathGenerator.cpp +++ b/src/tools/mmaps_generator/PathGenerator.cpp @@ -38,7 +38,6 @@ constexpr char Readme[] = namespace { std::unordered_map<uint32, uint8> _liquidTypes; - std::unordered_map<uint32, std::vector<uint32>> _mapDataForVmapInitialization; } namespace MMAP @@ -47,10 +46,21 @@ namespace MMAP namespace VMapFactory { - std::unique_ptr<VMAP::VMapManager2> CreateVMapManager() + std::unique_ptr<VMAP::VMapManager2> CreateVMapManager(uint32 mapId) { std::unique_ptr<VMAP::VMapManager2> vmgr = std::make_unique<VMAP::VMapManager2>(); - vmgr->InitializeThreadUnsafe(_mapDataForVmapInitialization); + + do + { + int32 parentMapId = sMapStore[mapId].ParentMapID; + + vmgr->InitializeThreadUnsafe(mapId, parentMapId); + if (parentMapId < 0) + break; + + mapId = parentMapId; + } while (true); + vmgr->GetLiquidFlagsPtr = [](uint32 liquidId) -> uint32 { auto itr = _liquidTypes.find(liquidId); @@ -362,10 +372,9 @@ std::unordered_map<uint32, uint8> LoadLiquid(std::string const& locale, bool sil return liquidData; } -std::unordered_map<uint32, std::vector<uint32>> LoadMap(std::string const& locale, bool silent, int32 errorExitCode) +void LoadMap(std::string const& locale, bool silent, int32 errorExitCode) { DB2FileLoader mapDb2; - std::unordered_map<uint32, std::vector<uint32>> mapData; DB2FileSystemSource mapSource((boost::filesystem::path("dbc") / locale / "Map.db2").string()); try { @@ -376,12 +385,9 @@ std::unordered_map<uint32, std::vector<uint32>> LoadMap(std::string const& local if (!record) continue; - mapData.emplace(std::piecewise_construct, std::forward_as_tuple(record.GetId()), std::forward_as_tuple()); int16 parentMapId = int16(record.GetUInt16("ParentMapID")); if (parentMapId < 0) parentMapId = int16(record.GetUInt16("CosmeticParentMapID")); - if (parentMapId != -1) - mapData[parentMapId].push_back(record.GetId()); MMAP::MapEntry& map = MMAP::sMapStore[record.GetId()]; map.MapType = record.GetUInt8("MapType"); @@ -397,8 +403,6 @@ std::unordered_map<uint32, std::vector<uint32>> LoadMap(std::string const& local exit(finish(e.what(), errorExitCode)); } - - return mapData; } int main(int argc, char** argv) @@ -451,7 +455,7 @@ int main(int argc, char** argv) _liquidTypes = LoadLiquid(dbcLocales[0], silent, -5); - _mapDataForVmapInitialization = LoadMap(dbcLocales[0], silent, -4); + LoadMap(dbcLocales[0], silent, -4); MMAP::MapBuilder builder(maxAngle, maxAngleNotSteep, skipLiquid, skipContinents, skipJunkMaps, skipBattlegrounds, debugOutput, bigBaseUnit, mapnum, offMeshInputPath, threads); diff --git a/src/tools/mmaps_generator/TerrainBuilder.cpp b/src/tools/mmaps_generator/TerrainBuilder.cpp index d3d68670d4f..a2a542ed095 100644 --- a/src/tools/mmaps_generator/TerrainBuilder.cpp +++ b/src/tools/mmaps_generator/TerrainBuilder.cpp @@ -29,10 +29,9 @@ namespace MMAP { TerrainBuilder::TerrainBuilder(bool skipLiquid) : m_skipLiquid (skipLiquid){ } - TerrainBuilder::~TerrainBuilder() { } /**************************************************************************/ - void TerrainBuilder::getLoopVars(Spot portion, int &loopStart, int &loopEnd, int &loopInc) + void TerrainBuilder::getLoopVars(Spot portion, int& loopStart, int& loopEnd, int& loopInc) { switch (portion) { @@ -65,31 +64,31 @@ namespace MMAP } /**************************************************************************/ - void TerrainBuilder::loadMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData) + void TerrainBuilder::loadMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData, VMAP::VMapManager2* vmapManager) { - if (loadMap(mapID, tileX, tileY, meshData, ENTIRE)) + if (loadMap(mapID, tileX, tileY, meshData, vmapManager, ENTIRE)) { - loadMap(mapID, tileX+1, tileY, meshData, LEFT); - loadMap(mapID, tileX-1, tileY, meshData, RIGHT); - loadMap(mapID, tileX, tileY+1, meshData, TOP); - loadMap(mapID, tileX, tileY-1, meshData, BOTTOM); + loadMap(mapID, tileX+1, tileY, meshData, vmapManager, LEFT); + loadMap(mapID, tileX-1, tileY, meshData, vmapManager, RIGHT); + loadMap(mapID, tileX, tileY+1, meshData, vmapManager, TOP); + loadMap(mapID, tileX, tileY-1, meshData, vmapManager, BOTTOM); } } /**************************************************************************/ - bool TerrainBuilder::loadMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData, Spot portion) + bool TerrainBuilder::loadMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData, VMAP::VMapManager2* vmapManager, Spot portion) { std::string mapFileName = Trinity::StringFormat("maps/{:04}_{:02}_{:02}.map", mapID, tileY, tileX); FILE* mapFile = fopen(mapFileName.c_str(), "rb"); if (!mapFile) { - int32 parentMapId = sMapStore[mapID].ParentMapID; + int32 parentMapId = vmapManager->getParentMapId(mapID); while (!mapFile && parentMapId != -1) { mapFileName = Trinity::StringFormat("maps/{:04}_{:02}_{:02}.map", parentMapId, tileY, tileX); mapFile = fopen(mapFileName.c_str(), "rb"); - parentMapId = sMapStore[parentMapId].ParentMapID; + parentMapId = vmapManager->getParentMapId(mapID); } } @@ -124,12 +123,9 @@ namespace MMAP } // data used later - uint8 holes[16][16][8]; - memset(holes, 0, sizeof(holes)); - uint16 liquid_entry[16][16]; - memset(liquid_entry, 0, sizeof(liquid_entry)); - map_liquidHeaderTypeFlags liquid_flags[16][16]; - memset(liquid_flags, 0, sizeof(liquid_flags)); + uint8 holes[16][16][8] = { }; + uint16 liquid_entry[16][16] = { }; + map_liquidHeaderTypeFlags liquid_flags[16][16] = { }; G3D::Array<int> ltriangles; G3D::Array<int> ttriangles; @@ -559,7 +555,7 @@ namespace MMAP } /**************************************************************************/ - bool TerrainBuilder::isHole(int square, uint8 const holes[16][16][8]) + bool TerrainBuilder::isHole(int square, uint8 const (&holes)[16][16][8]) { int row = square / 128; int col = square % 128; @@ -583,9 +579,8 @@ namespace MMAP } /**************************************************************************/ - bool TerrainBuilder::loadVMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData) + bool TerrainBuilder::loadVMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData, VMAP::VMapManager2* vmapManager) { - std::unique_ptr<VMAP::VMapManager2> vmapManager = VMapFactory::CreateVMapManager(); VMAP::LoadResult result = vmapManager->loadMap("vmaps", mapID, tileX, tileY); bool retval = false; @@ -792,7 +787,7 @@ namespace MMAP } /**************************************************************************/ - void TerrainBuilder::cleanVertices(G3D::Array<float> &verts, G3D::Array<int> &tris) + void TerrainBuilder::cleanVertices(G3D::Array<float>& verts, G3D::Array<int>& tris) { std::map<int, int> vertMap; diff --git a/src/tools/mmaps_generator/TerrainBuilder.h b/src/tools/mmaps_generator/TerrainBuilder.h index 98c59bbb572..fff061d4618 100644 --- a/src/tools/mmaps_generator/TerrainBuilder.h +++ b/src/tools/mmaps_generator/TerrainBuilder.h @@ -18,12 +18,16 @@ #ifndef _MMAP_TERRAIN_BUILDER_H #define _MMAP_TERRAIN_BUILDER_H -#include "PathCommon.h" #include "WorldModel.h" #include <G3D/Array.h> #include <G3D/Vector3.h> +namespace VMAP +{ +class VMapManager2; +} + enum class map_liquidHeaderTypeFlags : uint8; namespace MMAP @@ -91,11 +95,10 @@ namespace MMAP class TerrainBuilder { public: - TerrainBuilder(bool skipLiquid); - ~TerrainBuilder(); + explicit TerrainBuilder(bool skipLiquid); - void loadMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData); - bool loadVMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData); + void loadMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData, VMAP::VMapManager2* vmapManager); + bool loadVMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData, VMAP::VMapManager2* vmapManager); void loadOffMeshConnections(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData, std::vector<OffMeshData> const& offMeshConnections); bool usesLiquids() const { return !m_skipLiquid; } @@ -106,20 +109,17 @@ namespace MMAP static void copyVertices(std::vector<G3D::Vector3> const& source, G3D::Array<float>& dest); static void copyIndices(std::vector<VMAP::MeshTriangle> const& source, G3D::Array<int>& dest, int offset, bool flip); static void copyIndices(G3D::Array<int> const& source, G3D::Array<int>& dest, int offset); - static void cleanVertices(G3D::Array<float> &verts, G3D::Array<int> &tris); + static void cleanVertices(G3D::Array<float>& verts, G3D::Array<int>& tris); private: /// Loads a portion of a map's terrain - bool loadMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData, Spot portion); + bool loadMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData, VMAP::VMapManager2* vmapManager, Spot portion); /// Sets loop variables for selecting only certain parts of a map's terrain - void getLoopVars(Spot portion, int &loopStart, int &loopEnd, int &loopInc); + void getLoopVars(Spot portion, int& loopStart, int& loopEnd, int& loopInc); /// Controls whether liquids are loaded bool m_skipLiquid; - /// Load the map terrain from file - bool loadHeightMap(uint32 mapID, uint32 tileX, uint32 tileY, G3D::Array<float> &vertices, G3D::Array<int> &triangles, Spot portion); - /// Get the vector coordinate for a specific position void getHeightCoord(int index, Grid grid, float xOffset, float yOffset, float* coord, float* v); @@ -127,17 +127,13 @@ namespace MMAP void getHeightTriangle(int square, Spot triangle, int* indices, bool liquid = false); /// Determines if the specific position's triangles should be rendered - bool isHole(int square, uint8 const holes[16][16][8]); + bool isHole(int square, uint8 const (&holes)[16][16][8]); /// Get the liquid vector coordinate for a specific position void getLiquidCoord(int index, int index2, float xOffset, float yOffset, float* coord, float* v); /// Get the liquid type for a specific position map_liquidHeaderTypeFlags getLiquidType(int square, map_liquidHeaderTypeFlags const (&liquid_type)[16][16]); - - // hide parameterless and copy constructor - TerrainBuilder() = delete; - TerrainBuilder(TerrainBuilder const& tb) = delete; }; } diff --git a/src/tools/mmaps_generator/TileBuilder.cpp b/src/tools/mmaps_generator/TileBuilder.cpp new file mode 100644 index 00000000000..7b0a7bef00f --- /dev/null +++ b/src/tools/mmaps_generator/TileBuilder.cpp @@ -0,0 +1,554 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "TileBuilder.h" +#include "IntermediateValues.h" +#include "Log.h" +#include "MMapDefines.h" +#include "PathCommon.h" +#include "StringFormat.h" +#include "VMapManager2.h" +#include <DetourNavMeshBuilder.h> + +namespace +{ + struct Tile + { + Tile() : chf(nullptr), solid(nullptr), cset(nullptr), pmesh(nullptr), dmesh(nullptr) {} + ~Tile() + { + rcFreeCompactHeightfield(chf); + rcFreeContourSet(cset); + rcFreeHeightField(solid); + rcFreePolyMesh(pmesh); + rcFreePolyMeshDetail(dmesh); + } + rcCompactHeightfield* chf; + rcHeightfield* solid; + rcContourSet* cset; + rcPolyMesh* pmesh; + rcPolyMeshDetail* dmesh; + }; +} + +namespace MMAP +{ + struct TileConfig + { + TileConfig(bool bigBaseUnit) + { + // these are WORLD UNIT based metrics + // this are basic unit dimentions + // value have to divide GRID_SIZE(533.3333f) ( aka: 0.5333, 0.2666, 0.3333, 0.1333, etc ) + BASE_UNIT_DIM = bigBaseUnit ? 0.5333333f : 0.2666666f; + + // All are in UNIT metrics! + VERTEX_PER_MAP = int(GRID_SIZE / BASE_UNIT_DIM + 0.5f); + VERTEX_PER_TILE = bigBaseUnit ? 40 : 80; // must divide VERTEX_PER_MAP + TILES_PER_MAP = VERTEX_PER_MAP / VERTEX_PER_TILE; + } + + float BASE_UNIT_DIM; + int VERTEX_PER_MAP; + int VERTEX_PER_TILE; + int TILES_PER_MAP; + }; + + TileBuilder::TileBuilder(Optional<float> maxWalkableAngle, Optional<float> maxWalkableAngleNotSteep, + bool skipLiquid, bool bigBaseUnit, bool debugOutput, std::vector<OffMeshData> const* offMeshConnections) : + m_maxWalkableAngle(maxWalkableAngle), + m_maxWalkableAngleNotSteep(maxWalkableAngleNotSteep), + m_bigBaseUnit(bigBaseUnit), + m_debugOutput(debugOutput), + m_terrainBuilder(skipLiquid), + m_rcContext(false), + m_offMeshConnections(offMeshConnections) + { + } + + TileBuilder::~TileBuilder() = default; + + /**************************************************************************/ + void TileBuilder::buildTile(uint32 mapID, uint32 tileX, uint32 tileY, dtNavMesh* navMesh) + { + if (shouldSkipTile(mapID, tileX, tileY)) + { + OnTileDone(); + return; + } + + TC_LOG_INFO("maps.mmapgen", "{} [Map {:04}] Building tile [{:02},{:02}]", GetProgressText(), mapID, tileX, tileY); + + MeshData meshData; + + std::unique_ptr<VMAP::VMapManager2> vmapManager = VMapFactory::CreateVMapManager(mapID); + + // get heightmap data + m_terrainBuilder.loadMap(mapID, tileX, tileY, meshData, vmapManager.get()); + + // get model data + m_terrainBuilder.loadVMap(mapID, tileY, tileX, meshData, vmapManager.get()); + + // if there is no data, give up now + if (!meshData.solidVerts.size() && !meshData.liquidVerts.size()) + { + OnTileDone(); + return; + } + + // remove unused vertices + TerrainBuilder::cleanVertices(meshData.solidVerts, meshData.solidTris); + TerrainBuilder::cleanVertices(meshData.liquidVerts, meshData.liquidTris); + + // gather all mesh data for final data check, and bounds calculation + G3D::Array<float> allVerts; + allVerts.append(meshData.liquidVerts); + allVerts.append(meshData.solidVerts); + + if (!allVerts.size()) + { + OnTileDone(); + return; + } + + // get bounds of current tile + float bmin[3], bmax[3]; + getTileBounds(tileX, tileY, allVerts.getCArray(), allVerts.size() / 3, bmin, bmax); + + if (m_offMeshConnections) + m_terrainBuilder.loadOffMeshConnections(mapID, tileX, tileY, meshData, *m_offMeshConnections); + + // build navmesh tile + buildMoveMapTile(mapID, tileX, tileY, meshData, bmin, bmax, navMesh); + + OnTileDone(); + } + + /**************************************************************************/ + void TileBuilder::buildMoveMapTile(uint32 mapID, uint32 tileX, uint32 tileY, + MeshData& meshData, float (&bmin)[3], float (&bmax)[3], + dtNavMesh* navMesh) + { + // console output + std::string tileString = Trinity::StringFormat("[Map {:04}] [{:02},{:02}]:", mapID, tileX, tileY); + TC_LOG_INFO("maps.mmapgen", "{} Building movemap tile...", tileString); + + IntermediateValues iv; + + float* tVerts = meshData.solidVerts.getCArray(); + int tVertCount = meshData.solidVerts.size() / 3; + int* tTris = meshData.solidTris.getCArray(); + int tTriCount = meshData.solidTris.size() / 3; + + float* lVerts = meshData.liquidVerts.getCArray(); + int lVertCount = meshData.liquidVerts.size() / 3; + int* lTris = meshData.liquidTris.getCArray(); + int lTriCount = meshData.liquidTris.size() / 3; + uint8* lTriFlags = meshData.liquidType.getCArray(); + + const TileConfig tileConfig = TileConfig(m_bigBaseUnit); + int TILES_PER_MAP = tileConfig.TILES_PER_MAP; + float BASE_UNIT_DIM = tileConfig.BASE_UNIT_DIM; + rcConfig config = GetMapSpecificConfig(mapID, bmin, bmax, tileConfig); + + // this sets the dimensions of the heightfield - should maybe happen before border padding + rcCalcGridSize(config.bmin, config.bmax, config.cs, &config.width, &config.height); + + // allocate subregions : tiles + Tile* tiles = new Tile[TILES_PER_MAP * TILES_PER_MAP]; + + // Initialize per tile config. + rcConfig tileCfg = config; + tileCfg.width = config.tileSize + config.borderSize * 2; + tileCfg.height = config.tileSize + config.borderSize * 2; + + // merge per tile poly and detail meshes + rcPolyMesh** pmmerge = new rcPolyMesh * [TILES_PER_MAP * TILES_PER_MAP]; + rcPolyMeshDetail** dmmerge = new rcPolyMeshDetail * [TILES_PER_MAP * TILES_PER_MAP]; + int nmerge = 0; + // build all tiles + for (int y = 0; y < TILES_PER_MAP; ++y) + { + for (int x = 0; x < TILES_PER_MAP; ++x) + { + Tile& tile = tiles[x + y * TILES_PER_MAP]; + + // Calculate the per tile bounding box. + tileCfg.bmin[0] = config.bmin[0] + x * float(config.tileSize * config.cs); + tileCfg.bmin[2] = config.bmin[2] + y * float(config.tileSize * config.cs); + tileCfg.bmax[0] = config.bmin[0] + (x + 1) * float(config.tileSize * config.cs); + tileCfg.bmax[2] = config.bmin[2] + (y + 1) * float(config.tileSize * config.cs); + + tileCfg.bmin[0] -= tileCfg.borderSize * tileCfg.cs; + tileCfg.bmin[2] -= tileCfg.borderSize * tileCfg.cs; + tileCfg.bmax[0] += tileCfg.borderSize * tileCfg.cs; + tileCfg.bmax[2] += tileCfg.borderSize * tileCfg.cs; + + // build heightfield + tile.solid = rcAllocHeightfield(); + if (!tile.solid || !rcCreateHeightfield(&m_rcContext, *tile.solid, tileCfg.width, tileCfg.height, tileCfg.bmin, tileCfg.bmax, tileCfg.cs, tileCfg.ch)) + { + TC_LOG_ERROR("maps.mmapgen", "{} Failed building heightfield!", tileString); + continue; + } + + // mark all walkable tiles, both liquids and solids + + /* we want to have triangles with slope less than walkableSlopeAngleNotSteep (<= 55) to have NAV_AREA_GROUND + * and with slope between walkableSlopeAngleNotSteep and walkableSlopeAngle (55 < .. <= 70) to have NAV_AREA_GROUND_STEEP. + * we achieve this using recast API: memset everything to NAV_AREA_GROUND_STEEP, call rcClearUnwalkableTriangles with 70 so + * any area above that will get RC_NULL_AREA (unwalkable), then call rcMarkWalkableTriangles with 55 to set NAV_AREA_GROUND + * on anything below 55 . Players and idle Creatures can use NAV_AREA_GROUND, while Creatures in combat can use NAV_AREA_GROUND_STEEP. + */ + unsigned char* triFlags = new unsigned char[tTriCount]; + memset(triFlags, NAV_AREA_GROUND_STEEP, tTriCount * sizeof(unsigned char)); + rcClearUnwalkableTriangles(&m_rcContext, tileCfg.walkableSlopeAngle, tVerts, tVertCount, tTris, tTriCount, triFlags); + rcMarkWalkableTriangles(&m_rcContext, tileCfg.walkableSlopeAngleNotSteep, tVerts, tVertCount, tTris, tTriCount, triFlags, NAV_AREA_GROUND); + rcRasterizeTriangles(&m_rcContext, tVerts, tVertCount, tTris, triFlags, tTriCount, *tile.solid, config.walkableClimb); + delete[] triFlags; + + rcFilterLowHangingWalkableObstacles(&m_rcContext, config.walkableClimb, *tile.solid); + rcFilterLedgeSpans(&m_rcContext, tileCfg.walkableHeight, tileCfg.walkableClimb, *tile.solid); + rcFilterWalkableLowHeightSpans(&m_rcContext, tileCfg.walkableHeight, *tile.solid); + + // add liquid triangles + rcRasterizeTriangles(&m_rcContext, lVerts, lVertCount, lTris, lTriFlags, lTriCount, *tile.solid, config.walkableClimb); + + // compact heightfield spans + tile.chf = rcAllocCompactHeightfield(); + if (!tile.chf || !rcBuildCompactHeightfield(&m_rcContext, tileCfg.walkableHeight, tileCfg.walkableClimb, *tile.solid, *tile.chf)) + { + TC_LOG_ERROR("maps.mmapgen", "{} Failed compacting heightfield!", tileString); + continue; + } + + // build polymesh intermediates + if (!rcErodeWalkableArea(&m_rcContext, config.walkableRadius, *tile.chf)) + { + TC_LOG_ERROR("maps.mmapgen", "{} Failed eroding area!", tileString); + continue; + } + + if (!rcMedianFilterWalkableArea(&m_rcContext, *tile.chf)) + { + TC_LOG_ERROR("maps.mmapgen", "{} Failed filtering area!", tileString); + continue; + } + + if (!rcBuildDistanceField(&m_rcContext, *tile.chf)) + { + TC_LOG_ERROR("maps.mmapgen", "{} Failed building distance field!", tileString); + continue; + } + + if (!rcBuildRegions(&m_rcContext, *tile.chf, tileCfg.borderSize, tileCfg.minRegionArea, tileCfg.mergeRegionArea)) + { + TC_LOG_ERROR("maps.mmapgen", "{} Failed building regions!", tileString); + continue; + } + + tile.cset = rcAllocContourSet(); + if (!tile.cset || !rcBuildContours(&m_rcContext, *tile.chf, tileCfg.maxSimplificationError, tileCfg.maxEdgeLen, *tile.cset)) + { + TC_LOG_ERROR("maps.mmapgen", "{} Failed building contours!", tileString); + continue; + } + + // build polymesh + tile.pmesh = rcAllocPolyMesh(); + if (!tile.pmesh || !rcBuildPolyMesh(&m_rcContext, *tile.cset, tileCfg.maxVertsPerPoly, *tile.pmesh)) + { + TC_LOG_ERROR("maps.mmapgen", "{} Failed building polymesh!", tileString); + continue; + } + + tile.dmesh = rcAllocPolyMeshDetail(); + if (!tile.dmesh || !rcBuildPolyMeshDetail(&m_rcContext, *tile.pmesh, *tile.chf, tileCfg.detailSampleDist, tileCfg.detailSampleMaxError, *tile.dmesh)) + { + TC_LOG_ERROR("maps.mmapgen", "{} Failed building polymesh detail!", tileString); + continue; + } + + // free those up + // we may want to keep them in the future for debug + // but right now, we don't have the code to merge them + rcFreeHeightField(tile.solid); + tile.solid = nullptr; + rcFreeCompactHeightfield(tile.chf); + tile.chf = nullptr; + rcFreeContourSet(tile.cset); + tile.cset = nullptr; + + pmmerge[nmerge] = tile.pmesh; + dmmerge[nmerge] = tile.dmesh; + nmerge++; + } + } + + iv.polyMesh = rcAllocPolyMesh(); + if (!iv.polyMesh) + { + TC_LOG_ERROR("maps.mmapgen", "{} alloc iv.polyMesh FAILED!", tileString); + delete[] pmmerge; + delete[] dmmerge; + delete[] tiles; + return; + } + rcMergePolyMeshes(&m_rcContext, pmmerge, nmerge, *iv.polyMesh); + + iv.polyMeshDetail = rcAllocPolyMeshDetail(); + if (!iv.polyMeshDetail) + { + TC_LOG_ERROR("maps.mmapgen", "{} alloc m_dmesh FAILED!", tileString); + delete[] pmmerge; + delete[] dmmerge; + delete[] tiles; + return; + } + rcMergePolyMeshDetails(&m_rcContext, dmmerge, nmerge, *iv.polyMeshDetail); + + // free things up + delete[] pmmerge; + delete[] dmmerge; + delete[] tiles; + + // set polygons as walkable + // TODO: special flags for DYNAMIC polygons, ie surfaces that can be turned on and off + for (int i = 0; i < iv.polyMesh->npolys; ++i) + { + if (uint8 area = iv.polyMesh->areas[i] & NAV_AREA_ALL_MASK) + { + if (area >= NAV_AREA_MIN_VALUE) + iv.polyMesh->flags[i] = 1 << (NAV_AREA_MAX_VALUE - area); + else + iv.polyMesh->flags[i] = NAV_GROUND; // TODO: these will be dynamic in future + } + } + + // setup mesh parameters + dtNavMeshCreateParams params = {}; + params.verts = iv.polyMesh->verts; + params.vertCount = iv.polyMesh->nverts; + params.polys = iv.polyMesh->polys; + params.polyAreas = iv.polyMesh->areas; + params.polyFlags = iv.polyMesh->flags; + params.polyCount = iv.polyMesh->npolys; + params.nvp = iv.polyMesh->nvp; + params.detailMeshes = iv.polyMeshDetail->meshes; + params.detailVerts = iv.polyMeshDetail->verts; + params.detailVertsCount = iv.polyMeshDetail->nverts; + params.detailTris = iv.polyMeshDetail->tris; + params.detailTriCount = iv.polyMeshDetail->ntris; + + params.offMeshConVerts = meshData.offMeshConnections.getCArray(); + params.offMeshConCount = meshData.offMeshConnections.size() / 6; + params.offMeshConRad = meshData.offMeshConnectionRads.getCArray(); + params.offMeshConDir = meshData.offMeshConnectionDirs.getCArray(); + params.offMeshConAreas = meshData.offMeshConnectionsAreas.getCArray(); + params.offMeshConFlags = meshData.offMeshConnectionsFlags.getCArray(); + + params.walkableHeight = BASE_UNIT_DIM * config.walkableHeight; // agent height + params.walkableRadius = BASE_UNIT_DIM * config.walkableRadius; // agent radius + params.walkableClimb = BASE_UNIT_DIM * config.walkableClimb; // keep less that walkableHeight (aka agent height)! + params.tileX = (((bmin[0] + bmax[0]) / 2) - navMesh->getParams()->orig[0]) / GRID_SIZE; + params.tileY = (((bmin[2] + bmax[2]) / 2) - navMesh->getParams()->orig[2]) / GRID_SIZE; + rcVcopy(params.bmin, bmin); + rcVcopy(params.bmax, bmax); + params.cs = config.cs; + params.ch = config.ch; + params.tileLayer = 0; + params.buildBvTree = true; + + // will hold final navmesh + unsigned char* navData = nullptr; + int navDataSize = 0; + + do + { + // these values are checked within dtCreateNavMeshData - handle them here + // so we have a clear error message + if (params.nvp > DT_VERTS_PER_POLYGON) + { + TC_LOG_ERROR("maps.mmapgen", "{} Invalid verts-per-polygon value!", tileString); + break; + } + if (params.vertCount >= 0xffff) + { + TC_LOG_ERROR("maps.mmapgen", "{} Too many vertices!", tileString); + break; + } + if (!params.vertCount || !params.verts) + { + // occurs mostly when adjacent tiles have models + // loaded but those models don't span into this tile + + // message is an annoyance + //TC_LOG_ERROR("maps.mmapgen", "{} No vertices to build tile!", tileString); + break; + } + if (!params.polyCount || !params.polys) + { + // we have flat tiles with no actual geometry - don't build those, its useless + // keep in mind that we do output those into debug info + TC_LOG_ERROR("maps.mmapgen", "{} No polygons to build on tile!", tileString); + break; + } + if (!params.detailMeshes || !params.detailVerts || !params.detailTris) + { + TC_LOG_ERROR("maps.mmapgen", "{} No detail mesh to build tile!", tileString); + break; + } + + TC_LOG_DEBUG("maps.mmapgen", "{} Building navmesh tile...", tileString); + if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize)) + { + TC_LOG_ERROR("maps.mmapgen", "{} Failed building navmesh tile!", tileString); + break; + } + + dtTileRef tileRef = 0; + TC_LOG_DEBUG("maps.mmapgen", "{} Adding tile to navmesh...", tileString); + // DT_TILE_FREE_DATA tells detour to unallocate memory when the tile + // is removed via removeTile() + dtStatus dtResult = navMesh->addTile(navData, navDataSize, DT_TILE_FREE_DATA, 0, &tileRef); + if (!tileRef || !dtStatusSucceed(dtResult)) + { + TC_LOG_ERROR("maps.mmapgen", "{} Failed adding tile to navmesh!", tileString); + break; + } + + // file output + std::string fileName = Trinity::StringFormat("mmaps/{:04}{:02}{:02}.mmtile", mapID, tileY, tileX); + FILE* file = fopen(fileName.c_str(), "wb"); + if (!file) + { + TC_LOG_ERROR("maps.mmapgen", "{}: [Map {:04}] Failed to open {} for writing!", strerror(errno), mapID, fileName); + navMesh->removeTile(tileRef, nullptr, nullptr); + break; + } + + TC_LOG_DEBUG("maps.mmapgen", "{} Writing to file...", tileString); + + // write header + MmapTileHeader header; + header.usesLiquids = m_terrainBuilder.usesLiquids(); + header.size = uint32(navDataSize); + fwrite(&header, sizeof(MmapTileHeader), 1, file); + + // write data + fwrite(navData, sizeof(unsigned char), navDataSize, file); + fclose(file); + + // now that tile is written to disk, we can unload it + navMesh->removeTile(tileRef, nullptr, nullptr); + } while (false); + + if (m_debugOutput) + { + // restore padding so that the debug visualization is correct + for (int i = 0; i < iv.polyMesh->nverts; ++i) + { + unsigned short* v = &iv.polyMesh->verts[i * 3]; + v[0] += (unsigned short)config.borderSize; + v[2] += (unsigned short)config.borderSize; + } + + iv.generateObjFile(mapID, tileX, tileY, meshData); + iv.writeIV(mapID, tileX, tileY); + } + } + + /**************************************************************************/ + void TileBuilder::getTileBounds(uint32 tileX, uint32 tileY, float* verts, int vertCount, float* bmin, float* bmax) + { + // this is for elevation + if (verts && vertCount) + rcCalcBounds(verts, vertCount, bmin, bmax); + else + { + bmin[1] = FLT_MIN; + bmax[1] = FLT_MAX; + } + + // this is for width and depth + bmax[0] = (32 - int(tileX)) * GRID_SIZE; + bmax[2] = (32 - int(tileY)) * GRID_SIZE; + bmin[0] = bmax[0] - GRID_SIZE; + bmin[2] = bmax[2] - GRID_SIZE; + } + + /**************************************************************************/ + bool TileBuilder::shouldSkipTile(uint32 /*mapID*/, uint32 /*tileX*/, uint32 /*tileY*/) const + { + if (m_debugOutput) + return false; + + return true; + } + + rcConfig TileBuilder::GetMapSpecificConfig(uint32 mapID, float const (&bmin)[3], float const (&bmax)[3], TileConfig const& tileConfig) const + { + rcConfig config { }; + + rcVcopy(config.bmin, bmin); + rcVcopy(config.bmax, bmax); + + config.maxVertsPerPoly = DT_VERTS_PER_POLYGON; + config.cs = tileConfig.BASE_UNIT_DIM; + config.ch = tileConfig.BASE_UNIT_DIM; + // Keeping these 2 slope angles the same reduces a lot the number of polys. + // 55 should be the minimum, maybe 70 is ok (keep in mind blink uses mmaps), 85 is too much for players + config.walkableSlopeAngle = m_maxWalkableAngle.value_or(55.0f); + config.walkableSlopeAngleNotSteep = m_maxWalkableAngleNotSteep.value_or(55.0f); + config.tileSize = tileConfig.VERTEX_PER_TILE; + config.walkableRadius = m_bigBaseUnit ? 1 : 2; + config.borderSize = config.walkableRadius + 3; + config.maxEdgeLen = tileConfig.VERTEX_PER_TILE + 1; // anything bigger than tileSize + config.walkableHeight = m_bigBaseUnit ? 3 : 6; + // a value >= 3|6 allows npcs to walk over some fences + // a value >= 4|8 allows npcs to walk over all fences + config.walkableClimb = m_bigBaseUnit ? 3 : 6; + config.minRegionArea = rcSqr(60); + config.mergeRegionArea = rcSqr(50); + config.maxSimplificationError = 1.8f; // eliminates most jagged edges (tiny polygons) + config.detailSampleDist = config.cs * 16; + config.detailSampleMaxError = config.ch * 1; + + switch (mapID) + { + // Blade's Edge Arena + case 562: + // This allows to walk on the ropes to the pillars + config.walkableRadius = 0; + break; + // Blackfathom Deeps + case 48: + // Reduce the chance to have underground levels + config.ch *= 2; + break; + default: + break; + } + + return config; + } + + std::string TileBuilder::GetProgressText() const + { + return ""; + } +} diff --git a/src/tools/mmaps_generator/TileBuilder.h b/src/tools/mmaps_generator/TileBuilder.h new file mode 100644 index 00000000000..4ebbe38a037 --- /dev/null +++ b/src/tools/mmaps_generator/TileBuilder.h @@ -0,0 +1,84 @@ +/* + * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRINITYCORE_TILE_BUILDER_H +#define TRINITYCORE_TILE_BUILDER_H + +#include "Define.h" +#include "StringFormat.h" +#include "TerrainBuilder.h" +#include <DetourNavMesh.h> +#include <Recast.h> + +namespace MMAP +{ +struct TileConfig; + +class TileBuilder +{ +public: + TileBuilder(Optional<float> maxWalkableAngle, + Optional<float> maxWalkableAngleNotSteep, + bool skipLiquid, + bool bigBaseUnit, + bool debugOutput, + std::vector<OffMeshData> const* offMeshConnections); + + TileBuilder(TileBuilder const&) = delete; + TileBuilder(TileBuilder&&) = delete; + + TileBuilder& operator=(TileBuilder const&) = delete; + TileBuilder& operator=(TileBuilder&&) = delete; + + virtual ~TileBuilder(); + + void buildTile(uint32 mapID, uint32 tileX, uint32 tileY, dtNavMesh* navMesh); + // move map building + void buildMoveMapTile(uint32 mapID, + uint32 tileX, + uint32 tileY, + MeshData& meshData, + float (&bmin)[3], + float (&bmax)[3], + dtNavMesh* navMesh); + + virtual bool shouldSkipTile(uint32 mapID, uint32 tileX, uint32 tileY) const; + + static void getTileBounds(uint32 tileX, uint32 tileY, + float* verts, int vertCount, + float* bmin, float* bmax); + + rcConfig GetMapSpecificConfig(uint32 mapID, float const (&bmin)[3], float const (&bmax)[3], TileConfig const& tileConfig) const; + + virtual std::string GetProgressText() const; + + virtual void OnTileDone() { } + +private: + Optional<float> m_maxWalkableAngle; + Optional<float> m_maxWalkableAngleNotSteep; + bool m_bigBaseUnit; + bool m_debugOutput; + + TerrainBuilder m_terrainBuilder; + // build performance - not really used for now + rcContext m_rcContext; + std::vector<OffMeshData> const* m_offMeshConnections; +}; +} + +#endif // TRINITYCORE_TILE_BUILDER_H |
