/* * This file is part of the AzerothCore 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 Affero General Public License as published by the * Free Software Foundation; either version 3 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 Affero 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 . */ #include "MapBuilder.h" #include "MapDefines.h" #include "MapTree.h" #include "ModelInstance.h" #include "PathCommon.h" #include #include #include namespace MMAP { MapBuilder::MapBuilder(float maxWalkableAngle, bool skipLiquid, bool skipContinents, bool skipJunkMaps, bool skipBattlegrounds, bool debugOutput, bool bigBaseUnit, const char* offMeshFilePath) : m_debugOutput (debugOutput), m_offMeshFilePath (offMeshFilePath), m_skipContinents (skipContinents), m_skipJunkMaps (skipJunkMaps), m_skipBattlegrounds (skipBattlegrounds), m_maxWalkableAngle (maxWalkableAngle), m_bigBaseUnit (bigBaseUnit), _cancelationToken (false) { m_terrainBuilder = new TerrainBuilder(skipLiquid); m_rcContext = new rcContext(false); // percentageDone - Initializing m_totalTiles = 0; m_totalTilesBuilt = 0; discoverTiles(); } /**************************************************************************/ MapBuilder::~MapBuilder() { for (auto & m_tile : m_tiles) { m_tile.m_tiles->clear(); delete m_tile.m_tiles; } delete m_terrainBuilder; delete m_rcContext; } /**************************************************************************/ void MapBuilder::discoverTiles() { std::vector files; uint32 mapID, tileX, tileY, tileID, count = 0, fsize = 0; char filter[12]; printf("Discovering maps... "); getDirContents(files, "maps"); for (auto & file : files) { mapID = uint32(atoi(file.substr(0, file.size() - 8).c_str())); if (std::find(m_tiles.begin(), m_tiles.end(), mapID) == m_tiles.end()) { m_tiles.emplace_back(MapTiles(mapID, new std::set)); count++; } } files.clear(); getDirContents(files, "vmaps", "*.vmtree"); for (auto & file : files) { mapID = uint32(atoi(file.substr(0, file.size() - 7).c_str())); if (std::find(m_tiles.begin(), m_tiles.end(), mapID) == m_tiles.end()) { m_tiles.emplace_back(MapTiles(mapID, new std::set)); count++; } } printf("found %u.\n", count); count = 0; printf("Discovering tiles... "); for (auto & m_tile : m_tiles) { std::set* tiles = m_tile.m_tiles; mapID = m_tile.m_mapId; sprintf(filter, "%03u*.vmtile", mapID); files.clear(); getDirContents(files, "vmaps", filter); for (auto & file : files) { fsize = file.size(); tileY = uint32(atoi(file.substr(fsize - 12, 2).c_str())); tileX = uint32(atoi(file.substr(fsize - 9, 2).c_str())); tileID = StaticMapTree::packTileID(tileY, tileX); tiles->insert(tileID); count++; } sprintf(filter, "%03u*", mapID); files.clear(); getDirContents(files, "maps", filter); for (auto & file : files) { fsize = file.size(); tileY = uint32(atoi(file.substr(fsize - 8, 2).c_str())); tileX = uint32(atoi(file.substr(fsize - 6, 2).c_str())); tileID = StaticMapTree::packTileID(tileX, tileY); if (tiles->insert(tileID).second) count++; } // make sure we process maps which don't have tiles if (tiles->empty()) { // convert coord bounds to grid bounds uint32 minX, minY, maxX, maxY; getGridBounds(mapID, minX, minY, maxX, maxY); // add all tiles within bounds to tile list. for (uint32 i = minX; i <= maxX; ++i) for (uint32 j = minY; j <= maxY; ++j) if (tiles->insert(StaticMapTree::packTileID(i, j)).second) count++; } } printf("found %u.\n\n", count); // Calculate tiles to process in total for (auto & m_tile : m_tiles) { if (!shouldSkipMap(m_tile.m_mapId)) m_totalTiles += m_tile.m_tiles->size(); } } /**************************************************************************/ std::set* MapBuilder::getTileList(uint32 mapID) { TileList::iterator itr = std::find(m_tiles.begin(), m_tiles.end(), mapID); if (itr != m_tiles.end()) return (*itr).m_tiles; std::set* tiles = new std::set(); m_tiles.emplace_back(MapTiles(mapID, tiles)); return tiles; } /**************************************************************************/ void MapBuilder::buildAllMaps(unsigned int threads) { printf("Using %u threads to extract mmaps\n", threads); for (unsigned int i = 0; i < threads; ++i) { _workerThreads.emplace_back(&MapBuilder::WorkerThread, this); } m_tiles.sort([](MapTiles a, MapTiles b) { return a.m_tiles->size() > b.m_tiles->size(); }); for (auto & m_tile : m_tiles) { uint32 mapId = m_tile.m_mapId; if (!shouldSkipMap(mapId)) { if (threads > 0) _queue.Push(mapId); else buildMap(mapId); } } while (!_queue.Empty()) { std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } _cancelationToken = true; _queue.Cancel(); for (auto& thread : _workerThreads) { thread.join(); } } /**************************************************************************/ void MapBuilder::getGridBounds(uint32 mapID, uint32& minX, uint32& minY, uint32& maxX, uint32& maxY) const { // min and max are initialized to invalid values so the caller iterating the [min, max] range // will never enter the loop unless valid min/max values are found maxX = 0; maxY = 0; minX = std::numeric_limits::max(); minY = std::numeric_limits::max(); float bmin[3] = { 0, 0, 0 }; float bmax[3] = { 0, 0, 0 }; float lmin[3] = { 0, 0, 0 }; float lmax[3] = { 0, 0, 0 }; MeshData meshData; // make sure we process maps which don't have tiles // initialize the static tree, which loads WDT models if (!m_terrainBuilder->loadVMap(mapID, 64, 64, meshData)) return; // get the coord bounds of the model data if (meshData.solidVerts.size() + meshData.liquidVerts.size() == 0) return; // get the coord bounds of the model data if (meshData.solidVerts.size() && meshData.liquidVerts.size()) { rcCalcBounds(meshData.solidVerts.getCArray(), meshData.solidVerts.size() / 3, bmin, bmax); rcCalcBounds(meshData.liquidVerts.getCArray(), meshData.liquidVerts.size() / 3, lmin, lmax); rcVmin(bmin, lmin); rcVmax(bmax, lmax); } else if (meshData.solidVerts.size()) rcCalcBounds(meshData.solidVerts.getCArray(), meshData.solidVerts.size() / 3, bmin, bmax); else rcCalcBounds(meshData.liquidVerts.getCArray(), meshData.liquidVerts.size() / 3, lmin, lmax); // convert coord bounds to grid bounds maxX = 32 - bmin[0] / GRID_SIZE; maxY = 32 - bmin[2] / GRID_SIZE; minX = 32 - bmax[0] / GRID_SIZE; minY = 32 - bmax[2] / GRID_SIZE; } void MapBuilder::buildMeshFromFile(char* name) { FILE* file = fopen(name, "rb"); if (!file) return; printf("Building mesh from file\n"); int tileX, tileY, mapId; if (fread(&mapId, sizeof(int), 1, file) != 1) { fclose(file); return; } if (fread(&tileX, sizeof(int), 1, file) != 1) { fclose(file); return; } if (fread(&tileY, sizeof(int), 1, file) != 1) { fclose(file); return; } dtNavMesh* navMesh = nullptr; buildNavMesh(mapId, navMesh); if (!navMesh) { printf("Failed creating navmesh! \n"); fclose(file); return; } uint32 verticesCount, indicesCount; if (fread(&verticesCount, sizeof(uint32), 1, file) != 1) { fclose(file); return; } if (fread(&indicesCount, sizeof(uint32), 1, file) != 1) { fclose(file); return; } float* verts = new float[verticesCount]; int* inds = new int[indicesCount]; if (fread(verts, sizeof(float), verticesCount, file) != verticesCount) { fclose(file); delete[] verts; delete[] inds; return; } if (fread(inds, sizeof(int), indicesCount, file) != indicesCount) { fclose(file); delete[] verts; delete[] inds; return; } MeshData data; for (uint32 i = 0; i < verticesCount; ++i) data.solidVerts.append(verts[i]); delete[] verts; for (uint32 i = 0; i < indicesCount; ++i) data.solidTris.append(inds[i]); delete[] inds; 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); // build navmesh tile buildMoveMapTile(mapId, tileX, tileY, data, bmin, bmax, navMesh); fclose(file); } /**************************************************************************/ void MapBuilder::buildSingleTile(uint32 mapID, uint32 tileX, uint32 tileY) { dtNavMesh* navMesh = nullptr; buildNavMesh(mapID, navMesh); if (!navMesh) { printf("Failed creating navmesh! \n"); return; } buildTile(mapID, tileX, tileY, navMesh); dtFreeNavMesh(navMesh); } void MapBuilder::WorkerThread() { while (true) { uint32 mapId = 0; _queue.WaitAndPop(mapId); if (_cancelationToken) return; buildMap(mapId); } } /**************************************************************************/ void MapBuilder::buildMap(uint32 mapID) { std::set* tiles = getTileList(mapID); // make sure we process maps which don't have tiles if (!tiles->size()) { // convert coord bounds to grid bounds uint32 minX, minY, maxX, maxY; getGridBounds(mapID, minX, minY, maxX, maxY); // add all tiles within bounds to tile list. for (uint32 i = minX; i <= maxX; ++i) for (uint32 j = minY; j <= maxY; ++j) tiles->insert(StaticMapTree::packTileID(i, j)); } if (!tiles->empty()) { // build navMesh dtNavMesh* navMesh = nullptr; buildNavMesh(mapID, navMesh); if (!navMesh) { printf("[Map %03i] Failed creating navmesh!\n", mapID); return; } // now start building mmtiles for each tile printf("[Map %03i] We have %u tiles. \n", mapID, (unsigned int)tiles->size()); for (unsigned int tile : *tiles) { // percentageDone - increment tiles built m_totalTilesBuilt++; uint32 tileX, tileY; // unpack tile coords StaticMapTree::unpackTileID(tile, tileX, tileY); if (shouldSkipTile(mapID, tileX, tileY)) continue; buildTile(mapID, tileX, tileY, navMesh); } dtFreeNavMesh(navMesh); } printf("[Map %03i] Complete!\n", mapID); } /**************************************************************************/ void MapBuilder::buildTile(uint32 mapID, uint32 tileX, uint32 tileY, dtNavMesh* navMesh) { // percentageDone - added, now it will show addional reference percentage done of the overall process printf("%u%% [Map %03i] Building tile [%02u,%02u]\n", percentageDone(m_totalTiles, m_totalTilesBuilt), 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()) 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 allVerts; allVerts.append(meshData.liquidVerts); allVerts.append(meshData.solidVerts); if (!allVerts.size()) return; // get bounds of current tile float bmin[3], bmax[3]; getTileBounds(tileX, tileY, allVerts.getCArray(), allVerts.size() / 3, bmin, bmax); m_terrainBuilder->loadOffMeshConnections(mapID, tileX, tileY, meshData, m_offMeshFilePath); // build navmesh tile buildMoveMapTile(mapID, tileX, tileY, meshData, bmin, bmax, navMesh); } /**************************************************************************/ void MapBuilder::buildNavMesh(uint32 mapID, dtNavMesh*& navMesh) { std::set* tiles = getTileList(mapID); // old code for non-statically assigned bitmask sizes: ///*** calculate number of bits needed to store tiles & polys ***/ //int tileBits = dtIlog2(dtNextPow2(tiles->size())); //if (tileBits < 1) tileBits = 1; // need at least one bit! //int polyBits = sizeof(dtPolyRef)*8 - SALT_MIN_BITS - tileBits; int polyBits = DT_POLY_BITS; int maxTiles = tiles->size(); int maxPolysPerTile = 1 << polyBits; /*** calculate bounds of map ***/ uint32 tileXMin = 64, tileYMin = 64, tileXMax = 0, tileYMax = 0, tileX, tileY; for (unsigned int tile : *tiles) { StaticMapTree::unpackTileID(tile, tileX, tileY); if (tileX > tileXMax) tileXMax = tileX; else if (tileX < tileXMin) tileXMin = tileX; if (tileY > tileYMax) tileYMax = tileY; else if (tileY < tileYMin) tileYMin = tileY; } // use Max because '32 - tileX' is negative for values over 32 float bmin[3], bmax[3]; getTileBounds(tileXMax, tileYMax, nullptr, 0, bmin, bmax); /*** now create the navmesh ***/ // navmesh creation params dtNavMeshParams navMeshParams; memset(&navMeshParams, 0, sizeof(dtNavMeshParams)); navMeshParams.tileWidth = GRID_SIZE; navMeshParams.tileHeight = GRID_SIZE; rcVcopy(navMeshParams.orig, bmin); navMeshParams.maxTiles = maxTiles; navMeshParams.maxPolys = maxPolysPerTile; navMesh = dtAllocNavMesh(); printf("[Map %03i] Creating navMesh...\n", mapID); if (!navMesh->init(&navMeshParams)) { printf("[Map %03i] Failed creating navmesh! \n", mapID); return; } char fileName[25]; sprintf(fileName, "mmaps/%03u.mmap", mapID); FILE* file = fopen(fileName, "wb"); if (!file) { dtFreeNavMesh(navMesh); char message[1024]; sprintf(message, "[Map %03i] Failed to open %s for writing!\n", mapID, fileName); perror(message); return; } // now that we know navMesh params are valid, we can write them to file fwrite(&navMeshParams, sizeof(dtNavMeshParams), 1, file); fclose(file); } /**************************************************************************/ void MapBuilder::buildMoveMapTile(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData, float bmin[3], float bmax[3], dtNavMesh* navMesh) { // console output char tileString[20]; sprintf(tileString, "[Map %03i] [%02i,%02i]: ", mapID, tileX, tileY); printf("%s Building movemap tiles...\n", 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(); // 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 ) const static float BASE_UNIT_DIM = m_bigBaseUnit ? 0.5333333f : 0.2666666f; // All are in UNIT metrics! const static int VERTEX_PER_MAP = int(GRID_SIZE / BASE_UNIT_DIM + 0.5f); const static int VERTEX_PER_TILE = m_bigBaseUnit ? 40 : 80; // must divide VERTEX_PER_MAP const static int TILES_PER_MAP = VERTEX_PER_MAP / VERTEX_PER_TILE; rcConfig config; memset(&config, 0, sizeof(rcConfig)); rcVcopy(config.bmin, bmin); rcVcopy(config.bmax, bmax); config.maxVertsPerPoly = DT_VERTS_PER_POLYGON; config.cs = BASE_UNIT_DIM; config.ch = BASE_UNIT_DIM; config.walkableSlopeAngle = m_maxWalkableAngle; config.tileSize = VERTEX_PER_TILE; config.walkableRadius = m_bigBaseUnit ? 1 : 2; config.borderSize = config.walkableRadius + 3; config.maxEdgeLen = VERTEX_PER_TILE + 1; // anything bigger than tileSize // a value >= 3|6 allows npcs to walk over some fences // a value >= 4|8 allows npcs to walk over all fences // we prefer 3 and 6 for both Height/Climb as it's closer to be like retail config.walkableHeight = m_bigBaseUnit ? 3 : 6; 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; // 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] + float(x * config.tileSize - config.borderSize) * config.cs; tileCfg.bmin[2] = config.bmin[2] + float(y * config.tileSize - config.borderSize) * config.cs; tileCfg.bmax[0] = config.bmin[0] + float((x + 1) * config.tileSize + config.borderSize) * config.cs; tileCfg.bmax[2] = config.bmin[2] + float((y + 1) * config.tileSize + config.borderSize) * config.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)) { printf("%s Failed building heightfield! \n", tileString); continue; } // mark all walkable tiles, both liquids and solids unsigned char* triFlags = new unsigned char[tTriCount]; memset(triFlags, NAV_GROUND, tTriCount * sizeof(unsigned char)); rcClearUnwalkableTriangles(m_rcContext, tileCfg.walkableSlopeAngle, tVerts, tVertCount, tTris, tTriCount, triFlags); 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); 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)) { printf("%s Failed compacting heightfield! \n", tileString); continue; } // build polymesh intermediates if (!rcErodeWalkableArea(m_rcContext, config.walkableRadius, *tile.chf)) { printf("%s Failed eroding area! \n", tileString); continue; } if (!rcBuildDistanceField(m_rcContext, *tile.chf)) { printf("%s Failed building distance field! \n", tileString); continue; } if (!rcBuildRegions(m_rcContext, *tile.chf, tileCfg.borderSize, tileCfg.minRegionArea, tileCfg.mergeRegionArea)) { printf("%s Failed building regions! \n", tileString); continue; } tile.cset = rcAllocContourSet(); if (!tile.cset || !rcBuildContours(m_rcContext, *tile.chf, tileCfg.maxSimplificationError, tileCfg.maxEdgeLen, *tile.cset)) { printf("%s Failed building contours! \n", tileString); continue; } // build polymesh tile.pmesh = rcAllocPolyMesh(); if (!tile.pmesh || !rcBuildPolyMesh(m_rcContext, *tile.cset, tileCfg.maxVertsPerPoly, *tile.pmesh)) { printf("%s Failed building polymesh! \n", tileString); continue; } tile.dmesh = rcAllocPolyMeshDetail(); if (!tile.dmesh || !rcBuildPolyMeshDetail(m_rcContext, *tile.pmesh, *tile.chf, tileCfg.detailSampleDist, tileCfg.detailSampleMaxError, *tile.dmesh)) { printf("%s Failed building polymesh detail! \n", 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) { printf("%s alloc iv.polyMesh FAILED!\n", tileString); delete[] pmmerge; delete[] dmmerge; delete[] tiles; return; } rcMergePolyMeshes(m_rcContext, pmmerge, nmerge, *iv.polyMesh); iv.polyMeshDetail = rcAllocPolyMeshDetail(); if (!iv.polyMeshDetail) { printf("%s alloc m_dmesh FAILED!\n", 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 (iv.polyMesh->areas[i] & RC_WALKABLE_AREA) iv.polyMesh->flags[i] = iv.polyMesh->areas[i]; // 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) { printf("%s Invalid verts-per-polygon value! \n", tileString); break; } if (params.vertCount >= 0xffff) { printf("%s Too many vertices! \n", 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 printf("%sNo vertices to build tile! \n", tileString); break; } if (!params.polyCount || !params.polys || TILES_PER_MAP * TILES_PER_MAP == params.polyCount) { // 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 // drop tiles with only exact count - some tiles may have geometry while having less tiles printf("%s No polygons to build on tile! \n", tileString); break; } if (!params.detailMeshes || !params.detailVerts || !params.detailTris) { printf("%s No detail mesh to build tile! \n", tileString); break; } printf("%s Building navmesh tile...\n", tileString); if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize)) { printf("%s Failed building navmesh tile! \n", tileString); break; } dtTileRef tileRef = 0; printf("%s Adding tile to navmesh...\n", 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 || dtResult != DT_SUCCESS) { printf("%s Failed adding tile to navmesh! \n", tileString); break; } // file output char fileName[255]; sprintf(fileName, "mmaps/%03u%02i%02i.mmtile", mapID, tileY, tileX); FILE* file = fopen(fileName, "wb"); if (!file) { char message[1024]; sprintf(message, "[Map %03i] Failed to open %s for writing!\n", mapID, fileName); perror(message); navMesh->removeTile(tileRef, nullptr, nullptr); break; } printf("%s Writing to file...\n", 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) { // 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) { if (m_skipContinents) switch (mapID) { case 0: case 1: case 530: case 571: return true; default: break; } if (m_skipJunkMaps) switch (mapID) { case 13: // test.wdt case 25: // ScottTest.wdt case 29: // Test.wdt case 42: // Colin.wdt case 169: // EmeraldDream.wdt (unused, and very large) case 451: // development.wdt case 573: // ExteriorTest.wdt case 597: // CraigTest.wdt case 605: // development_nonweighted.wdt case 606: // QA_DVD.wdt return true; default: if (isTransportMap(mapID)) return true; break; } if (m_skipBattlegrounds) switch (mapID) { case 30: // AV case 37: // ? case 489: // WSG case 529: // AB case 566: // EotS case 607: // SotA case 628: // IoC return true; default: break; } return false; } /**************************************************************************/ bool MapBuilder::isTransportMap(uint32 mapID) { switch (mapID) { // transport maps case 582: case 584: case 586: case 587: case 588: case 589: case 590: case 591: case 592: case 593: case 594: case 596: case 610: case 612: case 613: case 614: case 620: case 621: case 622: case 623: case 641: case 642: case 647: case 672: case 673: case 712: case 713: case 718: return true; default: return false; } } /**************************************************************************/ bool MapBuilder::shouldSkipTile(uint32 mapID, uint32 tileX, uint32 tileY) { char fileName[255]; sprintf(fileName, "mmaps/%03u%02i%02i.mmtile", mapID, tileY, tileX); FILE* file = fopen(fileName, "rb"); if (!file) return false; MmapTileHeader header; int count = fread(&header, sizeof(MmapTileHeader), 1, file); fclose(file); if (count != 1) return false; if (header.mmapMagic != MMAP_MAGIC || header.dtVersion != uint32(DT_NAVMESH_VERSION)) return false; if (header.mmapVersion != MMAP_VERSION) return false; return true; } /**************************************************************************/ uint32 MapBuilder::percentageDone(uint32 totalTiles, uint32 totalTilesBuilt) { if (totalTiles) return totalTilesBuilt * 100 / totalTiles; return 0; } }