Legends-of-Azeroth-Pandaria-5.4.8
1207 строк · 43.5 Кб
1/*
2* This file is part of the Pandaria 5.4.8 Project. See THANKS file for Copyright information
3*
4* This program is free software; you can redistribute it and/or modify it
5* under the terms of the GNU General Public License as published by the
6* Free Software Foundation; either version 2 of the License, or (at your
7* option) any later version.
8*
9* This program is distributed in the hope that it will be useful, but WITHOUT
10* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12* more details.
13*
14* You should have received a copy of the GNU General Public License along
15* with this program. If not, see <http://www.gnu.org/licenses/>.
16*/
17
18#include "PathCommon.h"19#include "MapBuilder.h"20#include "MapDefines.h"21#include "MapTree.h"22#include "ModelInstance.h"23#include "DetourNavMeshBuilder.h"24#include "DetourNavMesh.h"25#include "DetourCommon.h"26#include "DisableMgr.h"27#include "SharedDefines.h"28
29bool DisableMgr::IsDisabledFor(DisableType type, uint32 entry, Unit const* unit, uint8 flags) { return false; }30
31#define MMAP_MAGIC 0x4d4d4150 // 'MMAP'32#define MMAP_VERSION 733
34namespace MMAP35{
36TileBuilder::TileBuilder(MapBuilder* mapBuilder, bool skipLiquid, bool bigBaseUnit, bool debugOutput) :37m_bigBaseUnit(bigBaseUnit),38m_debugOutput(debugOutput),39m_mapBuilder(mapBuilder),40m_terrainBuilder(nullptr),41m_workerThread(&TileBuilder::WorkerThread, this),42m_rcContext(nullptr)43{44m_terrainBuilder = new TerrainBuilder(skipLiquid);45m_rcContext = new rcContext(false);46}47
48TileBuilder::~TileBuilder()49{50WaitCompletion();51
52delete m_terrainBuilder;53delete m_rcContext;54}55
56void TileBuilder::WaitCompletion()57{58if (m_workerThread.joinable())59m_workerThread.join();60}61
62MapBuilder::MapBuilder(Optional<float> maxWalkableAngle, Optional<float> maxWalkableAngleNotSteep, bool skipLiquid,63bool skipContinents, bool skipJunkMaps, bool skipBattlegrounds, bool skipArenas, bool skipDungeons, bool skipTransports,64bool debugOutput, bool bigBaseUnit, int mapid, const char* offMeshFilePath, unsigned int threads) :65m_terrainBuilder (nullptr),66m_debugOutput (debugOutput),67m_offMeshFilePath (offMeshFilePath),68m_threads (threads),69m_skipContinents (skipContinents),70m_skipJunkMaps (skipJunkMaps),71m_skipBattlegrounds (skipBattlegrounds),72m_skipArenas (skipArenas),73m_skipDungeons (skipDungeons),74m_skipTransports (skipTransports),75m_skipLiquid (skipLiquid),76m_maxWalkableAngle (maxWalkableAngle),77m_maxWalkableAngleNotSteep (maxWalkableAngleNotSteep),78m_bigBaseUnit (bigBaseUnit),79m_mapid (mapid),80m_totalTiles (0u),81m_totalTilesProcessed(0u),82m_rcContext (nullptr),83_cancelationToken (false)84{85m_terrainBuilder = new TerrainBuilder(skipLiquid);86
87m_rcContext = new rcContext(false);88
89// At least 1 thread is needed90m_threads = std::max(1u, m_threads);91
92discoverTiles();93}94
95/**************************************************************************/96MapBuilder::~MapBuilder()97{98_cancelationToken = true;99
100_queue.Cancel();101
102for (TileList::iterator it = m_tiles.begin(); it != m_tiles.end(); ++it)103{104(*it).m_tiles->clear();105delete (*it).m_tiles;106}107
108delete m_terrainBuilder;109delete m_rcContext;110}111
112/**************************************************************************/113void MapBuilder::discoverTiles()114{115std::vector<std::string> files;116uint32 mapID, tileX, tileY, tileID, count = 0;117char filter[13];118
119printf("Discovering maps... ");120getDirContents(files, "maps");121for (uint32 i = 0; i < files.size(); ++i)122{123mapID = uint32(atoi(files[i].substr(0,4).c_str()));124if (std::find(m_tiles.begin(), m_tiles.end(), mapID) == m_tiles.end())125{126m_tiles.emplace_back(MapTiles(mapID, new std::set<uint32>));127count++;128}129}130
131files.clear();132getDirContents(files, "vmaps", "*.vmtree");133for (uint32 i = 0; i < files.size(); ++i)134{135mapID = uint32(atoi(files[i].substr(0,4).c_str()));136if (std::find(m_tiles.begin(), m_tiles.end(), mapID) == m_tiles.end())137{138m_tiles.emplace_back(MapTiles(mapID, new std::set<uint32>));139count++;140}141}142// for (auto&& map : transportMaps)143// {144// mapID = map.first;145// if (std::find(m_tiles.begin(), m_tiles.end(), mapID) == m_tiles.end())146// {147// m_tiles.emplace_back(MapTiles(mapID, new std::set<uint32>));148// count++;149// }150// }151printf("found %u.\n", count);152
153count = 0;154printf("Discovering tiles... ");155for (TileList::iterator itr = m_tiles.begin(); itr != m_tiles.end(); ++itr)156{157std::set<uint32>* tiles = (*itr).m_tiles;158mapID = (*itr).m_mapId;159
160sprintf(filter, "%04u*.vmtile", mapID);161files.clear();162getDirContents(files, "vmaps", filter);163for (uint32 i = 0; i < files.size(); ++i)164{165tileX = uint32(atoi(files[i].substr(8,2).c_str()));166tileY = uint32(atoi(files[i].substr(5,2).c_str()));167tileID = StaticMapTree::packTileID(tileY, tileX);168
169tiles->insert(tileID);170count++;171}172
173sprintf(filter, "%04u*", mapID);174files.clear();175getDirContents(files, "maps", filter);176for (uint32 i = 0; i < files.size(); ++i)177{178tileY = uint32(atoi(files[i].substr(5,2).c_str()));179tileX = uint32(atoi(files[i].substr(8,2).c_str()));180tileID = StaticMapTree::packTileID(tileX, tileY);181
182if (tiles->insert(tileID).second)183count++;184}185
186// make sure we process maps which don't have tiles187if (tiles->empty())188{189// convert coord bounds to grid bounds190uint32 minX, minY, maxX, maxY;191getGridBounds(mapID, minX, minY, maxX, maxY);192
193// add all tiles within bounds to tile list.194for (uint32 i = minX; i <= maxX; ++i)195for (uint32 j = minY; j <= maxY; ++j)196if (tiles->insert(StaticMapTree::packTileID(i, j)).second)197count++;198}199
200// // For transport maps we're only interested in cells covering coordinates -1 to 1, which happen to be 31 and 32 when offset201// if (isTransportMap(mapID))202// for (uint32 x = 31; x <= 32; ++x)203// for (uint32 y = 31; y <= 32; ++y)204// if (tiles->insert(StaticMapTree::packTileID(x, y)).second)205// count++;206}207printf("found %u.\n\n", count);208
209// Calculate tiles to process in total210for (TileList::iterator it = m_tiles.begin(); it != m_tiles.end(); ++it)211{212if (!shouldSkipMap(it->m_mapId))213m_totalTiles += it->m_tiles->size();214}215
216}217
218/**************************************************************************/219std::set<uint32>* MapBuilder::getTileList(uint32 mapID)220{221TileList::iterator itr = std::find(m_tiles.begin(), m_tiles.end(), mapID);222if (itr != m_tiles.end())223return (*itr).m_tiles;224
225std::set<uint32>* tiles = new std::set<uint32>();226m_tiles.emplace_back(MapTiles(mapID, tiles));227return tiles;228}229
230/**************************************************************************/231
232void TileBuilder::WorkerThread()233{234while (1)235{236TileInfo tileInfo;237
238m_mapBuilder->_queue.WaitAndPop(tileInfo);239
240if (m_mapBuilder->_cancelationToken)241return;242
243dtNavMesh* navMesh = dtAllocNavMesh();244if (!navMesh->init(&tileInfo.m_navMeshParams))245{246printf("[Map %03i] Failed creating navmesh for tile %i,%i !\n", tileInfo.m_mapId, tileInfo.m_tileX, tileInfo.m_tileY);247dtFreeNavMesh(navMesh);248return;249}250
251buildTile(tileInfo.m_mapId, tileInfo.m_tileX, tileInfo.m_tileY, navMesh);252
253dtFreeNavMesh(navMesh);254}255}256
257void MapBuilder::buildMaps(Optional<uint32> mapID)258{259printf("Using %u threads to generate mmaps\n", m_threads);260
261for (unsigned int i = 0; i < m_threads; ++i)262{263m_tileBuilders.push_back(new TileBuilder(this, m_skipLiquid, m_bigBaseUnit, m_debugOutput));264}265
266if (mapID)267{268buildMap(*mapID);269}270else271{272// Build all maps if no map id has been specified273for (TileList::iterator it = m_tiles.begin(); it != m_tiles.end(); ++it)274{275if (!shouldSkipMap(it->m_mapId))276buildMap(it->m_mapId);277}278}279
280while (!_queue.Empty())281{282std::this_thread::sleep_for(std::chrono::milliseconds(1000));283}284
285_cancelationToken = true;286
287_queue.Cancel();288
289for (auto& builder : m_tileBuilders)290delete builder;291
292m_tileBuilders.clear();293}294
295/**************************************************************************/296void MapBuilder::getGridBounds(uint32 mapID, uint32 &minX, uint32 &minY, uint32 &maxX, uint32 &maxY)297{298// min and max are initialized to invalid values so the caller iterating the [min, max] range299// will never enter the loop unless valid min/max values are found300maxX = 0;301maxY = 0;302minX = std::numeric_limits<uint32>::max();303minY = std::numeric_limits<uint32>::max();304
305float bmin[3] = { 0, 0, 0 };306float bmax[3] = { 0, 0, 0 };307float lmin[3] = { 0, 0, 0 };308float lmax[3] = { 0, 0, 0 };309MeshData meshData;310
311// make sure we process maps which don't have tiles312// initialize the static tree, which loads WDT models313if (!m_terrainBuilder->loadVMap(mapID, 64, 64, meshData))314return;315
316// get the coord bounds of the model data317if (meshData.solidVerts.size() + meshData.liquidVerts.size() == 0)318return;319
320// get the coord bounds of the model data321if (meshData.solidVerts.size() && meshData.liquidVerts.size())322{323rcCalcBounds(meshData.solidVerts.getCArray(), meshData.solidVerts.size() / 3, bmin, bmax);324rcCalcBounds(meshData.liquidVerts.getCArray(), meshData.liquidVerts.size() / 3, lmin, lmax);325rcVmin(bmin, lmin);326rcVmax(bmax, lmax);327}328else if (meshData.solidVerts.size())329rcCalcBounds(meshData.solidVerts.getCArray(), meshData.solidVerts.size() / 3, bmin, bmax);330else331rcCalcBounds(meshData.liquidVerts.getCArray(), meshData.liquidVerts.size() / 3, lmin, lmax);332
333// convert coord bounds to grid bounds334maxX = 32 - bmin[0] / GRID_SIZE;335maxY = 32 - bmin[2] / GRID_SIZE;336minX = 32 - bmax[0] / GRID_SIZE;337minY = 32 - bmax[2] / GRID_SIZE;338}339
340void MapBuilder::buildMeshFromFile(char* name)341{342FILE* file = fopen(name, "rb");343if (!file)344return;345
346printf("Building mesh from file\n");347int tileX, tileY, mapId;348if (fread(&mapId, sizeof(int), 1, file) != 1)349{350fclose(file);351return;352}353if (fread(&tileX, sizeof(int), 1, file) != 1)354{355fclose(file);356return;357}358if (fread(&tileY, sizeof(int), 1, file) != 1)359{360fclose(file);361return;362}363
364dtNavMesh* navMesh = nullptr;365buildNavMesh(mapId, navMesh);366if (!navMesh)367{368printf("Failed creating navmesh! \n");369fclose(file);370return;371}372
373uint32 verticesCount, indicesCount;374if (fread(&verticesCount, sizeof(uint32), 1, file) != 1)375{376fclose(file);377return;378}379
380if (fread(&indicesCount, sizeof(uint32), 1, file) != 1)381{382fclose(file);383return;384}385
386float* verts = new float[verticesCount];387int* inds = new int[indicesCount];388
389if (fread(verts, sizeof(float), verticesCount, file) != verticesCount)390{391fclose(file);392delete[] verts;393delete[] inds;394return;395}396
397if (fread(inds, sizeof(int), indicesCount, file) != indicesCount)398{399fclose(file);400delete[] verts;401delete[] inds;402return;403}404
405MeshData data;406
407for (uint32 i = 0; i < verticesCount; ++i)408data.solidVerts.append(verts[i]);409delete[] verts;410
411for (uint32 i = 0; i < indicesCount; ++i)412data.solidTris.append(inds[i]);413delete[] inds;414
415TerrainBuilder::cleanVertices(data.solidVerts, data.solidTris);416// get bounds of current tile417float bmin[3], bmax[3];418getTileBounds(tileX, tileY, data.solidVerts.getCArray(), data.solidVerts.size() / 3, bmin, bmax);419
420// build navmesh tile421// build navmesh tile422TileBuilder tileBuilder = TileBuilder(this, m_skipLiquid, m_bigBaseUnit, m_debugOutput);423tileBuilder.buildMoveMapTile(mapId, tileX, tileY, data, bmin, bmax, navMesh);424//buildMoveMapTile(mapId, tileX, tileY, data, bmin, bmax, navMesh);425fclose(file);426}427
428/**************************************************************************/429void MapBuilder::buildSingleTile(uint32 mapID, uint32 tileX, uint32 tileY)430{431dtNavMesh* navMesh = nullptr;432buildNavMesh(mapID, navMesh);433if (!navMesh)434{435printf("Failed creating navmesh! \n");436return;437}438
439TileBuilder tileBuilder = TileBuilder(this, m_skipLiquid, m_bigBaseUnit, m_debugOutput);440tileBuilder.buildTile(mapID, tileX, tileY, navMesh);441//buildTile(mapID, tileX, tileY, navMesh);442dtFreeNavMesh(navMesh);443
444_cancelationToken = true;445
446_queue.Cancel();447}448
449/**************************************************************************/450void MapBuilder::buildMap(uint32 mapID)451{452std::set<uint32>* tiles = getTileList(mapID);453
454if (!tiles->empty())455{456// build navMesh457dtNavMesh* navMesh = nullptr;458buildNavMesh(mapID, navMesh);459if (!navMesh)460{461printf("[Map %04i] Failed creating navmesh!\n", mapID);462return;463}464
465// now start building mmtiles for each tile466printf("[Map %04i] We have %u tiles. \n", mapID, (unsigned int)tiles->size());467for (std::set<uint32>::iterator it = tiles->begin(); it != tiles->end(); ++it)468{469uint32 tileX, tileY;470
471// unpack tile coords472StaticMapTree::unpackTileID((*it), tileX, tileY);473
474TileInfo tileInfo;475tileInfo.m_mapId = mapID;476tileInfo.m_tileX = tileX;477tileInfo.m_tileY = tileY;478memcpy(&tileInfo.m_navMeshParams, navMesh->getParams(), sizeof(dtNavMeshParams));479_queue.Push(tileInfo);480}481
482dtFreeNavMesh(navMesh);483}484
485printf("[Map %04i] Complete!\n", mapID);486}487
488/**************************************************************************/489void TileBuilder::buildTile(uint32 mapID, uint32 tileX, uint32 tileY, dtNavMesh* navMesh)490{491if(shouldSkipTile(mapID, tileX, tileY))492{493++m_mapBuilder->m_totalTilesProcessed;494return;495}496
497printf("%u%% [Map %04i] Building tile [%02u,%02u]\n", m_mapBuilder->currentPercentageDone(), mapID, tileX, tileY);498
499MeshData meshData;500
501// get heightmap data502m_terrainBuilder->loadMap(mapID, tileX, tileY, meshData);503
504// get model data505m_terrainBuilder->loadVMap(mapID, tileY, tileX, meshData);506
507// if there is no data, give up now508if (!meshData.solidVerts.size() && !meshData.liquidVerts.size())509{510++m_mapBuilder->m_totalTilesProcessed;511return;512}513
514// remove unused vertices515TerrainBuilder::cleanVertices(meshData.solidVerts, meshData.solidTris);516TerrainBuilder::cleanVertices(meshData.liquidVerts, meshData.liquidTris);517
518// gather all mesh data for final data check, and bounds calculation519G3D::Array<float> allVerts;520allVerts.append(meshData.liquidVerts);521allVerts.append(meshData.solidVerts);522
523if (!allVerts.size())524{525++m_mapBuilder->m_totalTilesProcessed;526return;527}528
529// get bounds of current tile530float bmin[3], bmax[3];531m_mapBuilder->getTileBounds(tileX, tileY, allVerts.getCArray(), allVerts.size() / 3, bmin, bmax);532
533m_terrainBuilder->loadOffMeshConnections(mapID, tileX, tileY, meshData, m_mapBuilder->m_offMeshFilePath);534
535// build navmesh tile536buildMoveMapTile(mapID, tileX, tileY, meshData, bmin, bmax, navMesh);537
538++m_mapBuilder->m_totalTilesProcessed;539}540
541/**************************************************************************/542void MapBuilder::buildNavMesh(uint32 mapID, dtNavMesh* &navMesh)543{544std::set<uint32>* tiles = getTileList(mapID);545
546// old code for non-statically assigned bitmask sizes:547///*** calculate number of bits needed to store tiles & polys ***/548//int tileBits = dtIlog2(dtNextPow2(tiles->size()));549//if (tileBits < 1) tileBits = 1; // need at least one bit!550//int polyBits = sizeof(dtPolyRef)*8 - SALT_MIN_BITS - tileBits;551
552int polyBits = DT_POLY_BITS;553
554int maxTiles = tiles->size();555int maxPolysPerTile = 1 << polyBits;556
557/*** calculate bounds of map ***/558
559uint32 tileXMin = 64, tileYMin = 64, tileXMax = 0, tileYMax = 0, tileX, tileY;560for (std::set<uint32>::iterator it = tiles->begin(); it != tiles->end(); ++it)561{562StaticMapTree::unpackTileID(*it, tileX, tileY);563
564if (tileX > tileXMax)565tileXMax = tileX;566else if (tileX < tileXMin)567tileXMin = tileX;568
569if (tileY > tileYMax)570tileYMax = tileY;571else if (tileY < tileYMin)572tileYMin = tileY;573}574
575// use Max because '32 - tileX' is negative for values over 32576float bmin[3], bmax[3];577getTileBounds(tileXMax, tileYMax, nullptr, 0, bmin, bmax);578
579/*** now create the navmesh ***/580
581// navmesh creation params582dtNavMeshParams navMeshParams;583memset(&navMeshParams, 0, sizeof(dtNavMeshParams));584navMeshParams.tileWidth = GRID_SIZE;585navMeshParams.tileHeight = GRID_SIZE;586rcVcopy(navMeshParams.orig, bmin);587navMeshParams.maxTiles = maxTiles;588navMeshParams.maxPolys = maxPolysPerTile;589
590navMesh = dtAllocNavMesh();591printf("[Map %04i] Creating navMesh...\n", mapID);592if (!navMesh->init(&navMeshParams))593{594printf("[Map %04i] Failed creating navmesh! \n", mapID);595return;596}597
598char fileName[25];599sprintf(fileName, "mmaps/%04u.mmap", mapID);600
601FILE* file = fopen(fileName, "wb");602if (!file)603{604dtFreeNavMesh(navMesh);605char message[1024];606sprintf(message, "[Map %04i] Failed to open %s for writing!\n", mapID, fileName);607perror(message);608return;609}610
611// now that we know navMesh params are valid, we can write them to file612fwrite(&navMeshParams, sizeof(dtNavMeshParams), 1, file);613fclose(file);614}615
616/**************************************************************************/617void TileBuilder::buildMoveMapTile(uint32 mapID, uint32 tileX, uint32 tileY,618MeshData &meshData, float bmin[3], float bmax[3],619dtNavMesh* navMesh)620{621// console output622char tileString[21];623sprintf(tileString, "[Map %04i] [%02i,%02i]: ", mapID, tileX, tileY);624printf("%s Building movemap tiles...\n", tileString);625
626IntermediateValues iv;627
628float* tVerts = meshData.solidVerts.getCArray();629int tVertCount = meshData.solidVerts.size() / 3;630int* tTris = meshData.solidTris.getCArray();631int tTriCount = meshData.solidTris.size() / 3;632
633float* lVerts = meshData.liquidVerts.getCArray();634int lVertCount = meshData.liquidVerts.size() / 3;635int* lTris = meshData.liquidTris.getCArray();636int lTriCount = meshData.liquidTris.size() / 3;637uint8* lTriFlags = meshData.liquidType.getCArray();638
639const TileConfig tileConfig = TileConfig(m_bigBaseUnit);640int TILES_PER_MAP = tileConfig.TILES_PER_MAP;641float BASE_UNIT_DIM = tileConfig.BASE_UNIT_DIM;642rcConfig config = m_mapBuilder->GetMapSpecificConfig(mapID, bmin, bmax, tileConfig);643
644// this sets the dimensions of the heightfield - should maybe happen before border padding645rcCalcGridSize(config.bmin, config.bmax, config.cs, &config.width, &config.height);646
647// allocate subregions : tiles648Tile* tiles = new Tile[TILES_PER_MAP * TILES_PER_MAP];649
650// Initialize per tile config.651rcConfig tileCfg = config;652tileCfg.width = config.tileSize + config.borderSize*2;653tileCfg.height = config.tileSize + config.borderSize*2;654
655// merge per tile poly and detail meshes656rcPolyMesh** pmmerge = new rcPolyMesh*[TILES_PER_MAP * TILES_PER_MAP];657rcPolyMeshDetail** dmmerge = new rcPolyMeshDetail*[TILES_PER_MAP * TILES_PER_MAP];658int nmerge = 0;659// build all tiles660for (int y = 0; y < TILES_PER_MAP; ++y)661{662for (int x = 0; x < TILES_PER_MAP; ++x)663{664Tile& tile = tiles[x + y * TILES_PER_MAP];665
666// Calculate the per tile bounding box.667tileCfg.bmin[0] = config.bmin[0] + x * float(config.tileSize * config.cs);668tileCfg.bmin[2] = config.bmin[2] + y * float(config.tileSize * config.cs);669tileCfg.bmax[0] = config.bmin[0] + (x + 1) * float(config.tileSize * config.cs);670tileCfg.bmax[2] = config.bmin[2] + (y + 1) * float(config.tileSize * config.cs);671
672tileCfg.bmin[0] -= tileCfg.borderSize * tileCfg.cs;673tileCfg.bmin[2] -= tileCfg.borderSize * tileCfg.cs;674tileCfg.bmax[0] += tileCfg.borderSize * tileCfg.cs;675tileCfg.bmax[2] += tileCfg.borderSize * tileCfg.cs;676
677// build heightfield678tile.solid = rcAllocHeightfield();679if (!tile.solid || !rcCreateHeightfield(m_rcContext, *tile.solid, tileCfg.width, tileCfg.height, tileCfg.bmin, tileCfg.bmax, tileCfg.cs, tileCfg.ch))680{681printf("%s Failed building heightfield! \n", tileString);682continue;683}684
685// mark all walkable tiles, both liquids and solids686
687/* we want to have triangles with slope less than walkableSlopeAngleNotSteep (<= 55) to have NAV_AREA_GROUND688* and with slope between walkableSlopeAngleNotSteep and walkableSlopeAngle (55 < .. <= 70) to have NAV_AREA_GROUND_STEEP.
689* we achieve this using recast API: memset everything to NAV_AREA_GROUND_STEEP, call rcClearUnwalkableTriangles with 70 so
690* any area above that will get RC_NULL_AREA (unwalkable), then call rcMarkWalkableTriangles with 55 to set NAV_AREA_GROUND
691* on anything below 55 . Players and idle Creatures can use NAV_AREA_GROUND, while Creatures in combat can use NAV_AREA_GROUND_STEEP.
692*/
693unsigned char* triFlags = new unsigned char[tTriCount];694memset(triFlags, NAV_AREA_GROUND_STEEP, tTriCount*sizeof(unsigned char));695rcClearUnwalkableTriangles(m_rcContext, tileCfg.walkableSlopeAngle, tVerts, tVertCount, tTris, tTriCount, triFlags);696rcMarkWalkableTriangles(m_rcContext, tileCfg.walkableSlopeAngleNotSteep, tVerts, tVertCount, tTris, tTriCount, triFlags, NAV_AREA_GROUND);697rcRasterizeTriangles(m_rcContext, tVerts, tVertCount, tTris, triFlags, tTriCount, *tile.solid, config.walkableClimb);698delete[] triFlags;699
700rcFilterLowHangingWalkableObstacles(m_rcContext, config.walkableClimb, *tile.solid);701rcFilterLedgeSpans(m_rcContext, tileCfg.walkableHeight, tileCfg.walkableClimb, *tile.solid);702rcFilterWalkableLowHeightSpans(m_rcContext, tileCfg.walkableHeight, *tile.solid);703
704// add liquid triangles705rcRasterizeTriangles(m_rcContext, lVerts, lVertCount, lTris, lTriFlags, lTriCount, *tile.solid, config.walkableClimb);706
707// compact heightfield spans708tile.chf = rcAllocCompactHeightfield();709if (!tile.chf || !rcBuildCompactHeightfield(m_rcContext, tileCfg.walkableHeight, tileCfg.walkableClimb, *tile.solid, *tile.chf))710{711printf("%s Failed compacting heightfield! \n", tileString);712continue;713}714
715// build polymesh intermediates716if (!rcErodeWalkableArea(m_rcContext, config.walkableRadius, *tile.chf))717{718printf("%s Failed eroding area! \n", tileString);719continue;720}721
722if (!rcMedianFilterWalkableArea(m_rcContext, *tile.chf))723{724printf("%s Failed filtering area! \n", tileString);725continue;726}727
728if (!rcBuildDistanceField(m_rcContext, *tile.chf))729{730printf("%s Failed building distance field! \n", tileString);731continue;732}733
734if (!rcBuildRegions(m_rcContext, *tile.chf, tileCfg.borderSize, tileCfg.minRegionArea, tileCfg.mergeRegionArea))735{736printf("%s Failed building regions! \n", tileString);737continue;738}739
740tile.cset = rcAllocContourSet();741if (!tile.cset || !rcBuildContours(m_rcContext, *tile.chf, tileCfg.maxSimplificationError, tileCfg.maxEdgeLen, *tile.cset))742{743printf("%s Failed building contours! \n", tileString);744continue;745}746
747// build polymesh748tile.pmesh = rcAllocPolyMesh();749if (!tile.pmesh || !rcBuildPolyMesh(m_rcContext, *tile.cset, tileCfg.maxVertsPerPoly, *tile.pmesh))750{751printf("%s Failed building polymesh! \n", tileString);752continue;753}754
755tile.dmesh = rcAllocPolyMeshDetail();756if (!tile.dmesh || !rcBuildPolyMeshDetail(m_rcContext, *tile.pmesh, *tile.chf, tileCfg.detailSampleDist, tileCfg.detailSampleMaxError, *tile.dmesh))757{758printf("%s Failed building polymesh detail! \n", tileString);759continue;760}761
762// free those up763// we may want to keep them in the future for debug764// but right now, we don't have the code to merge them765rcFreeHeightField(tile.solid);766tile.solid = nullptr;767rcFreeCompactHeightfield(tile.chf);768tile.chf = nullptr;769rcFreeContourSet(tile.cset);770tile.cset = nullptr;771
772pmmerge[nmerge] = tile.pmesh;773dmmerge[nmerge] = tile.dmesh;774nmerge++;775}776}777
778iv.polyMesh = rcAllocPolyMesh();779if (!iv.polyMesh)780{781printf("%s alloc iv.polyMesh FIALED!\n", tileString);782delete[] pmmerge;783delete[] dmmerge;784delete[] tiles;785return;786}787rcMergePolyMeshes(m_rcContext, pmmerge, nmerge, *iv.polyMesh);788
789iv.polyMeshDetail = rcAllocPolyMeshDetail();790if (!iv.polyMeshDetail)791{792printf("%s alloc m_dmesh FIALED!\n", tileString);793delete[] pmmerge;794delete[] dmmerge;795delete[] tiles;796return;797}798rcMergePolyMeshDetails(m_rcContext, dmmerge, nmerge, *iv.polyMeshDetail);799
800// free things up801delete[] pmmerge;802delete[] dmmerge;803delete[] tiles;804
805// set polygons as walkable806// TODO: special flags for DYNAMIC polygons, ie surfaces that can be turned on and off807for (int i = 0; i < iv.polyMesh->npolys; ++i)808{809if (uint8 area = iv.polyMesh->areas[i] & NAV_AREA_ALL_MASK)810{811if (area >= NAV_AREA_MIN_VALUE)812iv.polyMesh->flags[i] = 1 << (NAV_AREA_MAX_VALUE - area);813else814iv.polyMesh->flags[i] = NAV_GROUND; // TODO: these will be dynamic in future815}816}817
818// setup mesh parameters819dtNavMeshCreateParams params;820memset(¶ms, 0, sizeof(params));821params.verts = iv.polyMesh->verts;822params.vertCount = iv.polyMesh->nverts;823params.polys = iv.polyMesh->polys;824params.polyAreas = iv.polyMesh->areas;825params.polyFlags = iv.polyMesh->flags;826params.polyCount = iv.polyMesh->npolys;827params.nvp = iv.polyMesh->nvp;828params.detailMeshes = iv.polyMeshDetail->meshes;829params.detailVerts = iv.polyMeshDetail->verts;830params.detailVertsCount = iv.polyMeshDetail->nverts;831params.detailTris = iv.polyMeshDetail->tris;832params.detailTriCount = iv.polyMeshDetail->ntris;833
834params.offMeshConVerts = meshData.offMeshConnections.getCArray();835params.offMeshConCount = meshData.offMeshConnections.size()/6;836params.offMeshConRad = meshData.offMeshConnectionRads.getCArray();837params.offMeshConDir = meshData.offMeshConnectionDirs.getCArray();838params.offMeshConAreas = meshData.offMeshConnectionsAreas.getCArray();839params.offMeshConFlags = meshData.offMeshConnectionsFlags.getCArray();840
841params.walkableHeight = BASE_UNIT_DIM*config.walkableHeight; // agent height842params.walkableRadius = BASE_UNIT_DIM*config.walkableRadius; // agent radius843params.walkableClimb = BASE_UNIT_DIM*config.walkableClimb; // keep less that walkableHeight (aka agent height)!844params.tileX = (((bmin[0] + bmax[0]) / 2) - navMesh->getParams()->orig[0]) / GRID_SIZE;845params.tileY = (((bmin[2] + bmax[2]) / 2) - navMesh->getParams()->orig[2]) / GRID_SIZE;846rcVcopy(params.bmin, bmin);847rcVcopy(params.bmax, bmax);848params.cs = config.cs;849params.ch = config.ch;850params.tileLayer = 0;851params.buildBvTree = true;852
853// will hold final navmesh854unsigned char* navData = nullptr;855int navDataSize = 0;856
857do858{859// these values are checked within dtCreateNavMeshData - handle them here860// so we have a clear error message861if (params.nvp > DT_VERTS_PER_POLYGON)862{863printf("%s Invalid verts-per-polygon value! \n", tileString);864continue;865}866if (params.vertCount >= 0xffff)867{868printf("%s Too many vertices! \n", tileString);869continue;870}871if (!params.vertCount || !params.verts)872{873// occurs mostly when adjacent tiles have models874// loaded but those models don't span into this tile875
876// message is an annoyance877//printf("%sNo vertices to build tile! \n", tileString);878continue;879}880if (!params.polyCount || !params.polys)881{882// we have flat tiles with no actual geometry - don't build those, its useless883// drop tiles with only exact count - some tiles may have geometry while having less tiles884printf("%s No polygons to build on tile! \n", tileString);885continue;886}887if (!params.detailMeshes || !params.detailVerts || !params.detailTris)888{889printf("%s No detail mesh to build tile! \n", tileString);890continue;891}892
893printf("%s Building navmesh tile...\n", tileString);894if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize))895{896printf("%s Failed building navmesh tile! \n", tileString);897continue;898}899
900dtTileRef tileRef = 0;901printf("%s Adding tile to navmesh...\n", tileString);902// DT_TILE_FREE_DATA tells detour to unallocate memory when the tile903// is removed via removeTile()904dtStatus dtResult = navMesh->addTile(navData, navDataSize, DT_TILE_FREE_DATA, 0, &tileRef);905if (!tileRef || dtResult != DT_SUCCESS)906{907printf("%s Failed adding tile to navmesh! \n", tileString);908continue;909}910
911// file output912char fileName[255];913sprintf(fileName, "mmaps/%04u_%02i_%02i.mmtile", mapID, tileY, tileX);914FILE* file = fopen(fileName, "wb");915if (!file)916{917char message[1024];918sprintf(message, "[Map %04i] Failed to open %s for writing!\n", mapID, fileName);919perror(message);920navMesh->removeTile(tileRef, nullptr, nullptr);921continue;922}923
924printf("%s Writing to file...\n", tileString);925
926// write header927MmapTileHeader header;928header.usesLiquids = m_terrainBuilder->usesLiquids();929header.size = uint32(navDataSize);930fwrite(&header, sizeof(MmapTileHeader), 1, file);931
932// write data933fwrite(navData, sizeof(unsigned char), navDataSize, file);934fclose(file);935
936// now that tile is written to disk, we can unload it937navMesh->removeTile(tileRef, nullptr, nullptr);938}939while (0);940
941if (m_debugOutput)942{943// restore padding so that the debug visualization is correct944for (int i = 0; i < iv.polyMesh->nverts; ++i)945{946unsigned short* v = &iv.polyMesh->verts[i*3];947v[0] += (unsigned short)config.borderSize;948v[2] += (unsigned short)config.borderSize;949}950
951iv.generateObjFile(mapID, tileX, tileY, meshData);952iv.writeIV(mapID, tileX, tileY);953}954}955
956/**************************************************************************/957void MapBuilder::getTileBounds(uint32 tileX, uint32 tileY, float* verts, int vertCount, float* bmin, float* bmax)958{959// this is for elevation960if (verts && vertCount)961rcCalcBounds(verts, vertCount, bmin, bmax);962else963{964bmin[1] = FLT_MIN;965bmax[1] = FLT_MAX;966}967
968// this is for width and depth969bmax[0] = (32 - int(tileX)) * GRID_SIZE;970bmax[2] = (32 - int(tileY)) * GRID_SIZE;971bmin[0] = bmax[0] - GRID_SIZE;972bmin[2] = bmax[2] - GRID_SIZE;973}974
975/**************************************************************************/976bool MapBuilder::shouldSkipMap(uint32 mapID) const977{978if (m_mapid >= 0)979return static_cast<uint32>(m_mapid) != mapID;980
981if (m_skipContinents)982if (isContinentMap(mapID))983return true;984
985if (m_skipJunkMaps)986switch (mapID)987{988case 13: // test.wdt989case 25: // ScottTest.wdt990case 29: // Test.wdt991case 42: // Colin.wdt992case 169: // EmeraldDream.wdt (unused, and very large)993case 451: // development.wdt994case 573: // ExteriorTest.wdt995case 597: // CraigTest.wdt996case 605: // development_nonweighted.wdt997case 606: // QA_DVD.wdt998case 651: // ElevatorSpawnTest.wdt999case 1060: // LevelDesignLand-DevOnly.wdt1000return true;1001default:1002if (isTransportMap(mapID))1003return true;1004break;1005}1006
1007if (m_skipBattlegrounds)1008switch (mapID)1009{1010case 30: // AV1011case 37: // ?1012case 489: // WSG1013case 529: // AB1014case 566: // EotS1015case 607: // SotA1016case 628: // IoC1017case 726: // Twin Peaks1018case 727: // Silvershard Mines1019case 761: // The Battle for Gilneas1020case 968: // Rated Eye of the Storm1021case 998: // Temple of Kotmogu1022case 1010: // CTF31023case 1105: // Deepwind Gorge1024return true;1025default:1026break;1027}1028
1029if (m_skipArenas)1030switch (mapID)1031{1032case 559: // Nagrand Arena1033case 562: // Blade's Edge Arena1034case 572: // Ruins of Lordaeron1035case 617: // Dalaran Sewers1036case 618: // The Ring of Valor1037return true;1038default:1039break;1040}1041
1042return false;1043}1044/**************************************************************************/1045bool MapBuilder::isTransportMap(uint32 mapID) const1046{1047switch (mapID)1048{1049// transport maps1050case 582:1051case 584:1052case 586:1053case 587:1054case 588:1055case 589:1056case 590:1057case 591:1058case 592:1059case 593:1060case 594:1061case 596:1062case 610:1063case 612:1064case 613:1065case 614:1066case 620:1067case 621:1068case 622:1069case 623:1070case 641:1071case 642:1072case 647:1073case 672:1074case 673:1075case 712:1076case 713:1077case 718:1078case 738:1079case 739:1080case 740:1081case 741:1082case 742:1083case 743:1084case 747:1085case 748:1086case 749:1087case 750:1088case 762:1089case 763:1090case 765:1091case 766:1092case 767:1093return true;1094default:1095return false;1096}1097}1098
1099bool MapBuilder::isContinentMap(uint32 mapID) const1100{1101switch (mapID)1102{1103case 0:1104case 1:1105case 530:1106case 571:1107return true;1108default:1109return false;1110}1111}1112
1113/**************************************************************************/1114// bool MapBuilder::isTransportMap(uint32 mapID) const1115// {1116// return transportMaps.find(mapID) != transportMaps.end();1117// }1118
1119
1120
1121/**************************************************************************/1122bool TileBuilder::shouldSkipTile(uint32 mapID, uint32 tileX, uint32 tileY) const1123{1124char fileName[255];1125sprintf(fileName, "mmaps/%04u_%02i_%02i.mmtile", mapID, tileY, tileX);1126FILE* file = fopen(fileName, "rb");1127if (!file)1128return false;1129
1130MmapTileHeader header;1131int count = fread(&header, sizeof(MmapTileHeader), 1, file);1132fclose(file);1133if (count != 1)1134return false;1135
1136if (header.mmapMagic != MMAP_MAGIC || header.dtVersion != uint32(DT_NAVMESH_VERSION))1137return false;1138
1139if (header.mmapVersion != MMAP_VERSION)1140return false;1141
1142return true;1143}1144
1145rcConfig MapBuilder::GetMapSpecificConfig(uint32 mapID, float bmin[3], float bmax[3], const TileConfig &tileConfig) const1146{1147rcConfig config;1148memset(&config, 0, sizeof(rcConfig));1149
1150rcVcopy(config.bmin, bmin);1151rcVcopy(config.bmax, bmax);1152
1153config.maxVertsPerPoly = DT_VERTS_PER_POLYGON;1154config.cs = tileConfig.BASE_UNIT_DIM;1155config.ch = tileConfig.BASE_UNIT_DIM;1156// Keeping these 2 slope angles the same reduces a lot the number of polys.1157// 55 should be the minimum, maybe 70 is ok (keep in mind blink uses mmaps), 85 is too much for players1158config.walkableSlopeAngle = m_maxWalkableAngle ? *m_maxWalkableAngle : 55;1159config.walkableSlopeAngleNotSteep = m_maxWalkableAngleNotSteep ? *m_maxWalkableAngleNotSteep : 55;1160config.tileSize = tileConfig.VERTEX_PER_TILE;1161config.walkableRadius = m_bigBaseUnit ? 1 : 2;1162config.borderSize = config.walkableRadius + 3;1163config.maxEdgeLen = tileConfig.VERTEX_PER_TILE + 1; // anything bigger than tileSize1164config.walkableHeight = m_bigBaseUnit ? 3 : 6;1165// a value >= 3|6 allows npcs to walk over some fences1166// a value >= 4|8 allows npcs to walk over all fences1167config.walkableClimb = m_bigBaseUnit ? 3 : 6;1168config.minRegionArea = rcSqr(60);1169config.mergeRegionArea = rcSqr(50);1170config.maxSimplificationError = 1.8f; // eliminates most jagged edges (tiny polygons)1171config.detailSampleDist = config.cs * 16;1172config.detailSampleMaxError = config.ch * 1;1173
1174switch (mapID)1175{1176// Blade's Edge Arena1177case 562:1178// This allows to walk on the ropes to the pillars1179config.walkableRadius = 0;1180break;1181// Blackfathom Deeps1182case 48:1183// Reduce the chance to have underground levels1184config.ch *= 2;1185break;1186default:1187break;1188}1189
1190return config;1191}1192
1193/**************************************************************************/1194uint32 MapBuilder::percentageDone(uint32 totalTiles, uint32 totalTilesBuilt) const1195{1196if (totalTiles)1197return totalTilesBuilt * 100 / totalTiles;1198
1199return 0;1200}1201
1202uint32 MapBuilder::currentPercentageDone() const1203{1204return percentageDone(m_totalTiles, m_totalTilesProcessed);1205}1206
1207}
1208