TrinityCore
Loading...
Searching...
No Matches
MapBuilder.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS 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 "MapBuilder.h"
19#include "IntermediateValues.h"
20#include "MapDefines.h"
21#include "MapTree.h"
22#include "ModelInstance.h"
23#include "PathCommon.h"
24#include "StringFormat.h"
25#include "VMapFactory.h"
26#include "VMapManager2.h"
27#include <DetourCommon.h>
28#include <DetourNavMesh.h>
29#include <DetourNavMeshBuilder.h>
30#include <climits>
31
32namespace MMAP
33{
34 TileBuilder::TileBuilder(MapBuilder* mapBuilder, bool skipLiquid, bool bigBaseUnit, bool debugOutput) :
35 m_bigBaseUnit(bigBaseUnit),
36 m_debugOutput(debugOutput),
37 m_mapBuilder(mapBuilder),
38 m_terrainBuilder(nullptr),
39 m_workerThread(&TileBuilder::WorkerThread, this),
40 m_rcContext(nullptr)
41 {
42 m_terrainBuilder = new TerrainBuilder(skipLiquid);
43 m_rcContext = new rcContext(false);
44 }
45
47 {
49
50 delete m_terrainBuilder;
51 delete m_rcContext;
52 }
53
55 {
56 if (m_workerThread.joinable())
57 m_workerThread.join();
58 }
59
60 MapBuilder::MapBuilder(Optional<float> maxWalkableAngle, Optional<float> maxWalkableAngleNotSteep, bool skipLiquid,
61 bool skipContinents, bool skipJunkMaps, bool skipBattlegrounds,
62 bool debugOutput, bool bigBaseUnit, int mapid, char const* offMeshFilePath, unsigned int threads) :
63 m_terrainBuilder (nullptr),
64 m_debugOutput (debugOutput),
65 m_offMeshFilePath (offMeshFilePath),
66 m_threads (threads),
67 m_skipContinents (skipContinents),
68 m_skipJunkMaps (skipJunkMaps),
69 m_skipBattlegrounds (skipBattlegrounds),
70 m_skipLiquid (skipLiquid),
71 m_maxWalkableAngle (maxWalkableAngle),
72 m_maxWalkableAngleNotSteep (maxWalkableAngleNotSteep),
73 m_bigBaseUnit (bigBaseUnit),
74 m_mapid (mapid),
75 m_totalTiles (0u),
76 m_totalTilesProcessed(0u),
77 m_rcContext (nullptr),
78 _cancelationToken (false)
79 {
80 m_terrainBuilder = new TerrainBuilder(skipLiquid);
81
82 m_rcContext = new rcContext(false);
83
84 // At least 1 thread is needed
85 m_threads = std::max(1u, m_threads);
86
88 }
89
90 /**************************************************************************/
92 {
93 _cancelationToken = true;
94
95 _queue.Cancel();
96
97 for (auto& builder : m_tileBuilders)
98 delete builder;
99
100 m_tileBuilders.clear();
101
102 for (TileList::iterator it = m_tiles.begin(); it != m_tiles.end(); ++it)
103 {
104 (*it).m_tiles->clear();
105 delete (*it).m_tiles;
106 }
107
108 delete m_terrainBuilder;
109 delete m_rcContext;
110 }
111
112 /**************************************************************************/
114 {
115 std::vector<std::string> files;
116 uint32 mapID, tileX, tileY, tileID, count = 0;
117
118 printf("Discovering maps... ");
119 getDirContents(files, "maps");
120 for (uint32 i = 0; i < files.size(); ++i)
121 {
122 mapID = uint32(atoi(files[i].substr(0,3).c_str()));
123 if (std::find(m_tiles.begin(), m_tiles.end(), mapID) == m_tiles.end())
124 {
125 m_tiles.emplace_back(MapTiles(mapID, new std::set<uint32>));
126 count++;
127 }
128 }
129
130 files.clear();
131 getDirContents(files, "vmaps", "*.vmtree");
132 for (uint32 i = 0; i < files.size(); ++i)
133 {
134 mapID = uint32(atoi(files[i].substr(0,3).c_str()));
135 if (std::find(m_tiles.begin(), m_tiles.end(), mapID) == m_tiles.end())
136 {
137 m_tiles.emplace_back(MapTiles(mapID, new std::set<uint32>));
138 count++;
139 }
140 }
141 printf("found %u.\n", count);
142
143 count = 0;
144 printf("Discovering tiles... ");
145 for (TileList::iterator itr = m_tiles.begin(); itr != m_tiles.end(); ++itr)
146 {
147 std::set<uint32>* tiles = (*itr).m_tiles;
148 mapID = (*itr).m_mapId;
149
150 files.clear();
151 getDirContents(files, "vmaps", Trinity::StringFormat("{:03}*.vmtile", mapID));
152 for (uint32 i = 0; i < files.size(); ++i)
153 {
154 tileX = uint32(atoi(files[i].substr(7,2).c_str()));
155 tileY = uint32(atoi(files[i].substr(4,2).c_str()));
156 tileID = StaticMapTree::packTileID(tileY, tileX);
157
158 tiles->insert(tileID);
159 count++;
160 }
161
162 files.clear();
163 getDirContents(files, "maps", Trinity::StringFormat("{:03}*", mapID));
164 for (uint32 i = 0; i < files.size(); ++i)
165 {
166 tileY = uint32(atoi(files[i].substr(3,2).c_str()));
167 tileX = uint32(atoi(files[i].substr(5,2).c_str()));
168 tileID = StaticMapTree::packTileID(tileX, tileY);
169
170 if (tiles->insert(tileID).second)
171 count++;
172 }
173
174 // make sure we process maps which don't have tiles
175 if (tiles->empty())
176 {
177 // convert coord bounds to grid bounds
178 uint32 minX, minY, maxX, maxY;
179 getGridBounds(mapID, minX, minY, maxX, maxY);
180
181 // add all tiles within bounds to tile list.
182 for (uint32 i = minX; i <= maxX; ++i)
183 for (uint32 j = minY; j <= maxY; ++j)
184 if (tiles->insert(StaticMapTree::packTileID(i, j)).second)
185 count++;
186 }
187 }
188 printf("found %u.\n\n", count);
189
190 // Calculate tiles to process in total
191 for (TileList::iterator it = m_tiles.begin(); it != m_tiles.end(); ++it)
192 {
193 if (!shouldSkipMap(it->m_mapId))
194 m_totalTiles += it->m_tiles->size();
195 }
196 }
197
198 /**************************************************************************/
199 std::set<uint32>* MapBuilder::getTileList(uint32 mapID)
200 {
201 TileList::iterator itr = std::find(m_tiles.begin(), m_tiles.end(), mapID);
202 if (itr != m_tiles.end())
203 return (*itr).m_tiles;
204
205 std::set<uint32>* tiles = new std::set<uint32>();
206 m_tiles.emplace_back(MapTiles(mapID, tiles));
207 return tiles;
208 }
209
210 /**************************************************************************/
211
213 {
214 while (true)
215 {
216 TileInfo tileInfo;
217
218 m_mapBuilder->_queue.WaitAndPop(tileInfo);
219
221 return;
222
223 dtNavMesh* navMesh = dtAllocNavMesh();
224 if (!navMesh->init(&tileInfo.m_navMeshParams))
225 {
226 printf("[Map %03i] Failed creating navmesh for tile %i,%i !\n", tileInfo.m_mapId, tileInfo.m_tileX, tileInfo.m_tileY);
227 dtFreeNavMesh(navMesh);
228 return;
229 }
230
231 buildTile(tileInfo.m_mapId, tileInfo.m_tileX, tileInfo.m_tileY, navMesh);
232
233 dtFreeNavMesh(navMesh);
234 }
235 }
236
238 {
239 printf("Using %u threads to generate mmaps\n", m_threads);
240
241 for (unsigned int i = 0; i < m_threads; ++i)
242 {
244 }
245
246 if (mapID)
247 {
248 buildMap(*mapID);
249 }
250 else
251 {
252 // Build all maps if no map id has been specified
253 for (TileList::iterator it = m_tiles.begin(); it != m_tiles.end(); ++it)
254 {
255 if (!shouldSkipMap(it->m_mapId))
256 buildMap(it->m_mapId);
257 }
258 }
259
260 while (!_queue.Empty())
261 {
262 std::this_thread::sleep_for(std::chrono::milliseconds(1000));
263 }
264
265 _cancelationToken = true;
266
267 _queue.Cancel();
268
269 for (auto& builder : m_tileBuilders)
270 delete builder;
271
272 m_tileBuilders.clear();
273 }
274
275 /**************************************************************************/
276 void MapBuilder::getGridBounds(uint32 mapID, uint32 &minX, uint32 &minY, uint32 &maxX, uint32 &maxY) const
277 {
278 // min and max are initialized to invalid values so the caller iterating the [min, max] range
279 // will never enter the loop unless valid min/max values are found
280 maxX = 0;
281 maxY = 0;
282 minX = std::numeric_limits<uint32>::max();
283 minY = std::numeric_limits<uint32>::max();
284
285 float bmin[3] = { 0, 0, 0 };
286 float bmax[3] = { 0, 0, 0 };
287 float lmin[3] = { 0, 0, 0 };
288 float lmax[3] = { 0, 0, 0 };
289 MeshData meshData;
290
291 // make sure we process maps which don't have tiles
292 // initialize the static tree, which loads WDT models
293 if (!m_terrainBuilder->loadVMap(mapID, 64, 64, meshData))
294 return;
295
296 // get the coord bounds of the model data
297 if (meshData.solidVerts.size() + meshData.liquidVerts.size() == 0)
298 return;
299
300 // get the coord bounds of the model data
301 if (meshData.solidVerts.size() && meshData.liquidVerts.size())
302 {
303 rcCalcBounds(meshData.solidVerts.getCArray(), meshData.solidVerts.size() / 3, bmin, bmax);
304 rcCalcBounds(meshData.liquidVerts.getCArray(), meshData.liquidVerts.size() / 3, lmin, lmax);
305 rcVmin(bmin, lmin);
306 rcVmax(bmax, lmax);
307 }
308 else if (meshData.solidVerts.size())
309 rcCalcBounds(meshData.solidVerts.getCArray(), meshData.solidVerts.size() / 3, bmin, bmax);
310 else
311 rcCalcBounds(meshData.liquidVerts.getCArray(), meshData.liquidVerts.size() / 3, lmin, lmax);
312
313 // convert coord bounds to grid bounds
314 maxX = 32 - bmin[0] / GRID_SIZE;
315 maxY = 32 - bmin[2] / GRID_SIZE;
316 minX = 32 - bmax[0] / GRID_SIZE;
317 minY = 32 - bmax[2] / GRID_SIZE;
318 }
319
321 {
322 FILE* file = fopen(name, "rb");
323 if (!file)
324 return;
325
326 printf("Building mesh from file\n");
327 int tileX, tileY, mapId;
328 if (fread(&mapId, sizeof(int), 1, file) != 1)
329 {
330 fclose(file);
331 return;
332 }
333 if (fread(&tileX, sizeof(int), 1, file) != 1)
334 {
335 fclose(file);
336 return;
337 }
338 if (fread(&tileY, sizeof(int), 1, file) != 1)
339 {
340 fclose(file);
341 return;
342 }
343
344 dtNavMesh* navMesh = nullptr;
345 buildNavMesh(mapId, navMesh);
346 if (!navMesh)
347 {
348 printf("Failed creating navmesh! \n");
349 fclose(file);
350 return;
351 }
352
353 uint32 verticesCount, indicesCount;
354 if (fread(&verticesCount, sizeof(uint32), 1, file) != 1)
355 {
356 fclose(file);
357 return;
358 }
359
360 if (fread(&indicesCount, sizeof(uint32), 1, file) != 1)
361 {
362 fclose(file);
363 return;
364 }
365
366 float* verts = new float[verticesCount];
367 int* inds = new int[indicesCount];
368
369 if (fread(verts, sizeof(float), verticesCount, file) != verticesCount)
370 {
371 fclose(file);
372 delete[] verts;
373 delete[] inds;
374 return;
375 }
376
377 if (fread(inds, sizeof(int), indicesCount, file) != indicesCount)
378 {
379 fclose(file);
380 delete[] verts;
381 delete[] inds;
382 return;
383 }
384
385 MeshData data;
386
387 for (uint32 i = 0; i < verticesCount; ++i)
388 data.solidVerts.append(verts[i]);
389 delete[] verts;
390
391 for (uint32 i = 0; i < indicesCount; ++i)
392 data.solidTris.append(inds[i]);
393 delete[] inds;
394
396 // get bounds of current tile
397 float bmin[3], bmax[3];
398 getTileBounds(tileX, tileY, data.solidVerts.getCArray(), data.solidVerts.size() / 3, bmin, bmax);
399
400 // build navmesh tile
402 tileBuilder.buildMoveMapTile(mapId, tileX, tileY, data, bmin, bmax, navMesh);
403 fclose(file);
404 }
405
406 /**************************************************************************/
408 {
409 dtNavMesh* navMesh = nullptr;
410 buildNavMesh(mapID, navMesh);
411 if (!navMesh)
412 {
413 printf("Failed creating navmesh! \n");
414 return;
415 }
416
417 // ToDo: delete the old tile as the user clearly wants to rebuild it
418
420 tileBuilder.buildTile(mapID, tileX, tileY, navMesh);
421 dtFreeNavMesh(navMesh);
422
423 _cancelationToken = true;
424
425 _queue.Cancel();
426 }
427
428 /**************************************************************************/
430 {
431 std::set<uint32>* tiles = getTileList(mapID);
432
433 if (!tiles->empty())
434 {
435 // build navMesh
436 dtNavMesh* navMesh = nullptr;
437 buildNavMesh(mapID, navMesh);
438 if (!navMesh)
439 {
440 printf("[Map %03i] Failed creating navmesh!\n", mapID);
441 m_totalTilesProcessed += tiles->size();
442 return;
443 }
444
445 // now start building mmtiles for each tile
446 printf("[Map %03i] We have %u tiles. \n", mapID, (unsigned int)tiles->size());
447 for (std::set<uint32>::iterator it = tiles->begin(); it != tiles->end(); ++it)
448 {
449 uint32 tileX, tileY;
450
451 // unpack tile coords
452 StaticMapTree::unpackTileID((*it), tileX, tileY);
453
454 TileInfo tileInfo;
455 tileInfo.m_mapId = mapID;
456 tileInfo.m_tileX = tileX;
457 tileInfo.m_tileY = tileY;
458 memcpy(&tileInfo.m_navMeshParams, navMesh->getParams(), sizeof(dtNavMeshParams));
459 _queue.Push(tileInfo);
460 }
461
462 dtFreeNavMesh(navMesh);
463 }
464 }
465
466 /**************************************************************************/
467 void TileBuilder::buildTile(uint32 mapID, uint32 tileX, uint32 tileY, dtNavMesh* navMesh)
468 {
469 if(shouldSkipTile(mapID, tileX, tileY))
470 {
472 return;
473 }
474
475 printf("%u%% [Map %03i] Building tile [%02u,%02u]\n", m_mapBuilder->currentPercentageDone(), mapID, tileX, tileY);
476
477 MeshData meshData;
478
479 // get heightmap data
480 m_terrainBuilder->loadMap(mapID, tileX, tileY, meshData);
481
482 // get model data
483 m_terrainBuilder->loadVMap(mapID, tileY, tileX, meshData);
484
485 // if there is no data, give up now
486 if (!meshData.solidVerts.size() && !meshData.liquidVerts.size())
487 {
489 return;
490 }
491
492 // remove unused vertices
495
496 // gather all mesh data for final data check, and bounds calculation
497 G3D::Array<float> allVerts;
498 allVerts.append(meshData.liquidVerts);
499 allVerts.append(meshData.solidVerts);
500
501 if (!allVerts.size())
502 {
504 return;
505 }
506
507 // get bounds of current tile
508 float bmin[3], bmax[3];
509 m_mapBuilder->getTileBounds(tileX, tileY, allVerts.getCArray(), allVerts.size() / 3, bmin, bmax);
510
512
513 // build navmesh tile
514 buildMoveMapTile(mapID, tileX, tileY, meshData, bmin, bmax, navMesh);
515
517 }
518
519 /**************************************************************************/
520 void MapBuilder::buildNavMesh(uint32 mapID, dtNavMesh* &navMesh)
521 {
522 std::set<uint32>* tiles = getTileList(mapID);
523
524 // old code for non-statically assigned bitmask sizes:
526 //int tileBits = dtIlog2(dtNextPow2(tiles->size()));
527 //if (tileBits < 1) tileBits = 1; // need at least one bit!
528 //int polyBits = sizeof(dtPolyRef)*8 - SALT_MIN_BITS - tileBits;
529
530 int polyBits = DT_POLY_BITS;
531
532 int maxTiles = tiles->size();
533 int maxPolysPerTile = 1 << polyBits;
534
535 /*** calculate bounds of map ***/
536
537 uint32 tileXMin = 64, tileYMin = 64, tileXMax = 0, tileYMax = 0, tileX, tileY;
538 for (std::set<uint32>::iterator it = tiles->begin(); it != tiles->end(); ++it)
539 {
540 StaticMapTree::unpackTileID(*it, tileX, tileY);
541
542 if (tileX > tileXMax)
543 tileXMax = tileX;
544 else if (tileX < tileXMin)
545 tileXMin = tileX;
546
547 if (tileY > tileYMax)
548 tileYMax = tileY;
549 else if (tileY < tileYMin)
550 tileYMin = tileY;
551 }
552
553 // use Max because '32 - tileX' is negative for values over 32
554 float bmin[3], bmax[3];
555 getTileBounds(tileXMax, tileYMax, nullptr, 0, bmin, bmax);
556
557 /*** now create the navmesh ***/
558
559 // navmesh creation params
560 dtNavMeshParams navMeshParams;
561 memset(&navMeshParams, 0, sizeof(dtNavMeshParams));
562 navMeshParams.tileWidth = GRID_SIZE;
563 navMeshParams.tileHeight = GRID_SIZE;
564 rcVcopy(navMeshParams.orig, bmin);
565 navMeshParams.maxTiles = maxTiles;
566 navMeshParams.maxPolys = maxPolysPerTile;
567
568 navMesh = dtAllocNavMesh();
569 printf("[Map %03i] Creating navMesh...\n", mapID);
570 if (!navMesh->init(&navMeshParams))
571 {
572 printf("[Map %03i] Failed creating navmesh! \n", mapID);
573 return;
574 }
575
576 std::string fileName = Trinity::StringFormat("mmaps/{:03}.mmap", mapID);
577
578 FILE* file = fopen(fileName.c_str(), "wb");
579 if (!file)
580 {
581 dtFreeNavMesh(navMesh);
582 perror(Trinity::StringFormat("[Map {:03}] Failed to open {} for writing!\n", mapID, fileName).c_str());
583 return;
584 }
585
586 // now that we know navMesh params are valid, we can write them to file
587 fwrite(&navMeshParams, sizeof(dtNavMeshParams), 1, file);
588 fclose(file);
589 }
590
591 /**************************************************************************/
593 MeshData &meshData, float bmin[3], float bmax[3],
594 dtNavMesh* navMesh)
595 {
596 // console output
597 std::string tileString = Trinity::StringFormat("[Map {:03}] [{:02},{:02}]]: ", mapID, tileX, tileY);
598 printf("%s Building movemap tiles...\n", tileString.c_str());
599
601
602 float* tVerts = meshData.solidVerts.getCArray();
603 int tVertCount = meshData.solidVerts.size() / 3;
604 int* tTris = meshData.solidTris.getCArray();
605 int tTriCount = meshData.solidTris.size() / 3;
606
607 float* lVerts = meshData.liquidVerts.getCArray();
608 int lVertCount = meshData.liquidVerts.size() / 3;
609 int* lTris = meshData.liquidTris.getCArray();
610 int lTriCount = meshData.liquidTris.size() / 3;
611 uint8* lTriFlags = meshData.liquidType.getCArray();
612
613 const TileConfig tileConfig = TileConfig(m_bigBaseUnit);
614 int TILES_PER_MAP = tileConfig.TILES_PER_MAP;
615 float BASE_UNIT_DIM = tileConfig.BASE_UNIT_DIM;
616 rcConfig config = m_mapBuilder->GetMapSpecificConfig(mapID, bmin, bmax, tileConfig);
617
618 // this sets the dimensions of the heightfield - should maybe happen before border padding
619 rcCalcGridSize(config.bmin, config.bmax, config.cs, &config.width, &config.height);
620
621 // allocate subregions : tiles
622 Tile* tiles = new Tile[TILES_PER_MAP * TILES_PER_MAP];
623
624 // Initialize per tile config.
625 rcConfig tileCfg = config;
626 tileCfg.width = config.tileSize + config.borderSize*2;
627 tileCfg.height = config.tileSize + config.borderSize*2;
628
629 // merge per tile poly and detail meshes
630 rcPolyMesh** pmmerge = new rcPolyMesh*[TILES_PER_MAP * TILES_PER_MAP];
631 rcPolyMeshDetail** dmmerge = new rcPolyMeshDetail*[TILES_PER_MAP * TILES_PER_MAP];
632 int nmerge = 0;
633 // build all tiles
634 for (int y = 0; y < TILES_PER_MAP; ++y)
635 {
636 for (int x = 0; x < TILES_PER_MAP; ++x)
637 {
638 Tile& tile = tiles[x + y * TILES_PER_MAP];
639
640 // Calculate the per tile bounding box.
641 tileCfg.bmin[0] = config.bmin[0] + x * float(config.tileSize * config.cs);
642 tileCfg.bmin[2] = config.bmin[2] + y * float(config.tileSize * config.cs);
643 tileCfg.bmax[0] = config.bmin[0] + (x + 1) * float(config.tileSize * config.cs);
644 tileCfg.bmax[2] = config.bmin[2] + (y + 1) * float(config.tileSize * config.cs);
645
646 tileCfg.bmin[0] -= tileCfg.borderSize * tileCfg.cs;
647 tileCfg.bmin[2] -= tileCfg.borderSize * tileCfg.cs;
648 tileCfg.bmax[0] += tileCfg.borderSize * tileCfg.cs;
649 tileCfg.bmax[2] += tileCfg.borderSize * tileCfg.cs;
650
651 // build heightfield
652 tile.solid = rcAllocHeightfield();
653 if (!tile.solid || !rcCreateHeightfield(m_rcContext, *tile.solid, tileCfg.width, tileCfg.height, tileCfg.bmin, tileCfg.bmax, tileCfg.cs, tileCfg.ch))
654 {
655 printf("%s Failed building heightfield! \n", tileString.c_str());
656 continue;
657 }
658
659 // mark all walkable tiles, both liquids and solids
660
661 /* we want to have triangles with slope less than walkableSlopeAngleNotSteep (<= 55) to have NAV_AREA_GROUND
662 * and with slope between walkableSlopeAngleNotSteep and walkableSlopeAngle (55 < .. <= 70) to have NAV_AREA_GROUND_STEEP.
663 * we achieve this using recast API: memset everything to NAV_AREA_GROUND_STEEP, call rcClearUnwalkableTriangles with 70 so
664 * any area above that will get RC_NULL_AREA (unwalkable), then call rcMarkWalkableTriangles with 55 to set NAV_AREA_GROUND
665 * on anything below 55 . Players and idle Creatures can use NAV_AREA_GROUND, while Creatures in combat can use NAV_AREA_GROUND_STEEP.
666 */
667 unsigned char* triFlags = new unsigned char[tTriCount];
668 memset(triFlags, NAV_AREA_GROUND_STEEP, tTriCount*sizeof(unsigned char));
669 rcClearUnwalkableTriangles(m_rcContext, tileCfg.walkableSlopeAngle, tVerts, tVertCount, tTris, tTriCount, triFlags);
670 rcMarkWalkableTriangles(m_rcContext, tileCfg.walkableSlopeAngleNotSteep, tVerts, tVertCount, tTris, tTriCount, triFlags, NAV_AREA_GROUND);
671 rcRasterizeTriangles(m_rcContext, tVerts, tVertCount, tTris, triFlags, tTriCount, *tile.solid, config.walkableClimb);
672 delete[] triFlags;
673
674 rcFilterLowHangingWalkableObstacles(m_rcContext, config.walkableClimb, *tile.solid);
675 rcFilterLedgeSpans(m_rcContext, tileCfg.walkableHeight, tileCfg.walkableClimb, *tile.solid);
676 rcFilterWalkableLowHeightSpans(m_rcContext, tileCfg.walkableHeight, *tile.solid);
677
678 // add liquid triangles
679 rcRasterizeTriangles(m_rcContext, lVerts, lVertCount, lTris, lTriFlags, lTriCount, *tile.solid, config.walkableClimb);
680
681 // compact heightfield spans
682 tile.chf = rcAllocCompactHeightfield();
683 if (!tile.chf || !rcBuildCompactHeightfield(m_rcContext, tileCfg.walkableHeight, tileCfg.walkableClimb, *tile.solid, *tile.chf))
684 {
685 printf("%s Failed compacting heightfield! \n", tileString.c_str());
686 continue;
687 }
688
689 // build polymesh intermediates
690 if (!rcErodeWalkableArea(m_rcContext, config.walkableRadius, *tile.chf))
691 {
692 printf("%s Failed eroding area! \n", tileString.c_str());
693 continue;
694 }
695
696 if (!rcMedianFilterWalkableArea(m_rcContext, *tile.chf))
697 {
698 printf("%s Failed filtering area! \n", tileString.c_str());
699 continue;
700 }
701
702 if (!rcBuildDistanceField(m_rcContext, *tile.chf))
703 {
704 printf("%s Failed building distance field! \n", tileString.c_str());
705 continue;
706 }
707
708 if (!rcBuildRegions(m_rcContext, *tile.chf, tileCfg.borderSize, tileCfg.minRegionArea, tileCfg.mergeRegionArea))
709 {
710 printf("%s Failed building regions! \n", tileString.c_str());
711 continue;
712 }
713
714 tile.cset = rcAllocContourSet();
715 if (!tile.cset || !rcBuildContours(m_rcContext, *tile.chf, tileCfg.maxSimplificationError, tileCfg.maxEdgeLen, *tile.cset))
716 {
717 printf("%s Failed building contours! \n", tileString.c_str());
718 continue;
719 }
720
721 // build polymesh
722 tile.pmesh = rcAllocPolyMesh();
723 if (!tile.pmesh || !rcBuildPolyMesh(m_rcContext, *tile.cset, tileCfg.maxVertsPerPoly, *tile.pmesh))
724 {
725 printf("%s Failed building polymesh! \n", tileString.c_str());
726 continue;
727 }
728
729 tile.dmesh = rcAllocPolyMeshDetail();
730 if (!tile.dmesh || !rcBuildPolyMeshDetail(m_rcContext, *tile.pmesh, *tile.chf, tileCfg.detailSampleDist, tileCfg.detailSampleMaxError, *tile.dmesh))
731 {
732 printf("%s Failed building polymesh detail! \n", tileString.c_str());
733 continue;
734 }
735
736 // free those up
737 // we may want to keep them in the future for debug
738 // but right now, we don't have the code to merge them
739 rcFreeHeightField(tile.solid);
740 tile.solid = nullptr;
741 rcFreeCompactHeightfield(tile.chf);
742 tile.chf = nullptr;
743 rcFreeContourSet(tile.cset);
744 tile.cset = nullptr;
745
746 pmmerge[nmerge] = tile.pmesh;
747 dmmerge[nmerge] = tile.dmesh;
748 nmerge++;
749 }
750 }
751
752 iv.polyMesh = rcAllocPolyMesh();
753 if (!iv.polyMesh)
754 {
755 printf("%s alloc iv.polyMesh FAILED!\n", tileString.c_str());
756 delete[] pmmerge;
757 delete[] dmmerge;
758 delete[] tiles;
759 return;
760 }
761 rcMergePolyMeshes(m_rcContext, pmmerge, nmerge, *iv.polyMesh);
762
763 iv.polyMeshDetail = rcAllocPolyMeshDetail();
764 if (!iv.polyMeshDetail)
765 {
766 printf("%s alloc m_dmesh FAILED!\n", tileString.c_str());
767 delete[] pmmerge;
768 delete[] dmmerge;
769 delete[] tiles;
770 return;
771 }
772 rcMergePolyMeshDetails(m_rcContext, dmmerge, nmerge, *iv.polyMeshDetail);
773
774 // free things up
775 delete[] pmmerge;
776 delete[] dmmerge;
777 delete[] tiles;
778
779 // set polygons as walkable
780 // TODO: special flags for DYNAMIC polygons, ie surfaces that can be turned on and off
781 for (int i = 0; i < iv.polyMesh->npolys; ++i)
782 {
783 if (uint8 area = iv.polyMesh->areas[i] & NAV_AREA_ALL_MASK)
784 {
785 if (area >= NAV_AREA_MIN_VALUE)
786 iv.polyMesh->flags[i] = 1 << (NAV_AREA_MAX_VALUE - area);
787 else
788 iv.polyMesh->flags[i] = NAV_GROUND; // TODO: these will be dynamic in future
789 }
790 }
791
792 // setup mesh parameters
793 dtNavMeshCreateParams params;
794 memset(&params, 0, sizeof(params));
795 params.verts = iv.polyMesh->verts;
796 params.vertCount = iv.polyMesh->nverts;
797 params.polys = iv.polyMesh->polys;
798 params.polyAreas = iv.polyMesh->areas;
799 params.polyFlags = iv.polyMesh->flags;
800 params.polyCount = iv.polyMesh->npolys;
801 params.nvp = iv.polyMesh->nvp;
802 params.detailMeshes = iv.polyMeshDetail->meshes;
803 params.detailVerts = iv.polyMeshDetail->verts;
804 params.detailVertsCount = iv.polyMeshDetail->nverts;
805 params.detailTris = iv.polyMeshDetail->tris;
806 params.detailTriCount = iv.polyMeshDetail->ntris;
807
808 params.offMeshConVerts = meshData.offMeshConnections.getCArray();
809 params.offMeshConCount = meshData.offMeshConnections.size()/6;
810 params.offMeshConRad = meshData.offMeshConnectionRads.getCArray();
811 params.offMeshConDir = meshData.offMeshConnectionDirs.getCArray();
812 params.offMeshConAreas = meshData.offMeshConnectionsAreas.getCArray();
813 params.offMeshConFlags = meshData.offMeshConnectionsFlags.getCArray();
814
815 params.walkableHeight = BASE_UNIT_DIM*config.walkableHeight; // agent height
816 params.walkableRadius = BASE_UNIT_DIM*config.walkableRadius; // agent radius
817 params.walkableClimb = BASE_UNIT_DIM*config.walkableClimb; // keep less that walkableHeight (aka agent height)!
818 params.tileX = (((bmin[0] + bmax[0]) / 2) - navMesh->getParams()->orig[0]) / GRID_SIZE;
819 params.tileY = (((bmin[2] + bmax[2]) / 2) - navMesh->getParams()->orig[2]) / GRID_SIZE;
820 rcVcopy(params.bmin, bmin);
821 rcVcopy(params.bmax, bmax);
822 params.cs = config.cs;
823 params.ch = config.ch;
824 params.tileLayer = 0;
825 params.buildBvTree = true;
826
827 // will hold final navmesh
828 unsigned char* navData = nullptr;
829 int navDataSize = 0;
830
831 do
832 {
833 // these values are checked within dtCreateNavMeshData - handle them here
834 // so we have a clear error message
835 if (params.nvp > DT_VERTS_PER_POLYGON)
836 {
837 printf("%s Invalid verts-per-polygon value! \n", tileString.c_str());
838 break;
839 }
840 if (params.vertCount >= 0xffff)
841 {
842 printf("%s Too many vertices! \n", tileString.c_str());
843 break;
844 }
845 if (!params.vertCount || !params.verts)
846 {
847 // occurs mostly when adjacent tiles have models
848 // loaded but those models don't span into this tile
849
850 // message is an annoyance
851 //printf("%sNo vertices to build tile! \n", tileString.c_str());
852 break;
853 }
854 if (!params.polyCount || !params.polys)
855 {
856 // we have flat tiles with no actual geometry - don't build those, its useless
857 // keep in mind that we do output those into debug info
858 printf("%s No polygons to build on tile! \n", tileString.c_str());
859 break;
860 }
861 if (!params.detailMeshes || !params.detailVerts || !params.detailTris)
862 {
863 printf("%s No detail mesh to build tile! \n", tileString.c_str());
864 break;
865 }
866
867 printf("%s Building navmesh tile...\n", tileString.c_str());
868 if (!dtCreateNavMeshData(&params, &navData, &navDataSize))
869 {
870 printf("%s Failed building navmesh tile! \n", tileString.c_str());
871 break;
872 }
873
874 dtTileRef tileRef = 0;
875 printf("%s Adding tile to navmesh...\n", tileString.c_str());
876 // DT_TILE_FREE_DATA tells detour to unallocate memory when the tile
877 // is removed via removeTile()
878 dtStatus dtResult = navMesh->addTile(navData, navDataSize, DT_TILE_FREE_DATA, 0, &tileRef);
879 if (!tileRef || dtResult != DT_SUCCESS)
880 {
881 printf("%s Failed adding tile to navmesh! \n", tileString.c_str());
882 break;
883 }
884
885 // file output
886 std::string fileName = Trinity::StringFormat("mmaps/{:03}{:02}{:02}.mmtile", mapID, tileY, tileX);
887 FILE* file = fopen(fileName.c_str(), "wb");
888 if (!file)
889 {
890 perror(Trinity::StringFormat("[Map {:03}] Failed to open {} for writing!\n", mapID, fileName).c_str());
891 navMesh->removeTile(tileRef, nullptr, nullptr);
892 break;
893 }
894
895 printf("%s Writing to file...\n", tileString.c_str());
896
897 // write header
898 MmapTileHeader header;
900 header.size = uint32(navDataSize);
901 fwrite(&header, sizeof(MmapTileHeader), 1, file);
902
903 /*
904 dtMeshHeader* navDataHeader = (dtMeshHeader*)navData;
905 printf("Poly count: %d\n", navDataHeader->polyCount);
906 */
907
908 // write data
909 fwrite(navData, sizeof(unsigned char), navDataSize, file);
910 fclose(file);
911
912 // now that tile is written to disk, we can unload it
913 navMesh->removeTile(tileRef, nullptr, nullptr);
914 }
915 while (false);
916
917 if (m_debugOutput)
918 {
919 // restore padding so that the debug visualization is correct
920 for (int i = 0; i < iv.polyMesh->nverts; ++i)
921 {
922 unsigned short* v = &iv.polyMesh->verts[i*3];
923 v[0] += (unsigned short)config.borderSize;
924 v[2] += (unsigned short)config.borderSize;
925 }
926
927 iv.generateObjFile(mapID, tileX, tileY, meshData);
928 iv.writeIV(mapID, tileX, tileY);
929 }
930 }
931
932 /**************************************************************************/
933 void MapBuilder::getTileBounds(uint32 tileX, uint32 tileY, float* verts, int vertCount, float* bmin, float* bmax) const
934 {
935 // this is for elevation
936 if (verts && vertCount)
937 rcCalcBounds(verts, vertCount, bmin, bmax);
938 else
939 {
940 bmin[1] = FLT_MIN;
941 bmax[1] = FLT_MAX;
942 }
943
944 // this is for width and depth
945 bmax[0] = (32 - int(tileX)) * GRID_SIZE;
946 bmax[2] = (32 - int(tileY)) * GRID_SIZE;
947 bmin[0] = bmax[0] - GRID_SIZE;
948 bmin[2] = bmax[2] - GRID_SIZE;
949 }
950
951 /**************************************************************************/
953 {
954 if (m_mapid >= 0)
955 return static_cast<uint32>(m_mapid) != mapID;
956
958 if (isContinentMap(mapID))
959 return true;
960
961 if (m_skipJunkMaps)
962 switch (mapID)
963 {
964 case 13: // test.wdt
965 case 25: // ScottTest.wdt
966 case 29: // Test.wdt
967 case 42: // Colin.wdt
968 case 169: // EmeraldDream.wdt (unused, and very large)
969 case 451: // development.wdt
970 case 573: // ExteriorTest.wdt
971 case 597: // CraigTest.wdt
972 case 605: // development_nonweighted.wdt
973 case 606: // QA_DVD.wdt
974 return true;
975 default:
976 if (isTransportMap(mapID))
977 return true;
978 break;
979 }
980
982 switch (mapID)
983 {
984 case 30: // AV
985 case 37: // ?
986 case 489: // WSG
987 case 529: // AB
988 case 566: // EotS
989 case 607: // SotA
990 case 628: // IoC
991 return true;
992 default:
993 break;
994 }
995
996 return false;
997 }
998
999 /**************************************************************************/
1001 {
1002 switch (mapID)
1003 {
1004 // transport maps
1005 case 582:
1006 case 584:
1007 case 586:
1008 case 587:
1009 case 588:
1010 case 589:
1011 case 590:
1012 case 591:
1013 case 592:
1014 case 593:
1015 case 594:
1016 case 596:
1017 case 610:
1018 case 612:
1019 case 613:
1020 case 614:
1021 case 620:
1022 case 621:
1023 case 622:
1024 case 623:
1025 case 641:
1026 case 642:
1027 case 647:
1028 case 672:
1029 case 673:
1030 case 712:
1031 case 713:
1032 case 718:
1033 return true;
1034 default:
1035 return false;
1036 }
1037 }
1038
1040 {
1041 switch (mapID)
1042 {
1043 case 0:
1044 case 1:
1045 case 530:
1046 case 571:
1047 return true;
1048 default:
1049 return false;
1050 }
1051 }
1052
1053 /**************************************************************************/
1054 bool TileBuilder::shouldSkipTile(uint32 mapID, uint32 tileX, uint32 tileY) const
1055 {
1056 std::string fileName = Trinity::StringFormat("mmaps/{:03}{:02}{:02}.mmtile", mapID, tileY, tileX);
1057 FILE* file = fopen(fileName.c_str(), "rb");
1058 if (!file)
1059 return false;
1060
1061 MmapTileHeader header;
1062 int count = fread(&header, sizeof(MmapTileHeader), 1, file);
1063 fclose(file);
1064 if (count != 1)
1065 return false;
1066
1067 if (header.mmapMagic != MMAP_MAGIC || header.dtVersion != uint32(DT_NAVMESH_VERSION))
1068 return false;
1069
1070 if (header.mmapVersion != MMAP_VERSION)
1071 return false;
1072
1073 return true;
1074 }
1075
1076 rcConfig MapBuilder::GetMapSpecificConfig(uint32 mapID, float bmin[3], float bmax[3], const TileConfig &tileConfig) const
1077 {
1078 rcConfig config;
1079 memset(&config, 0, sizeof(rcConfig));
1080
1081 rcVcopy(config.bmin, bmin);
1082 rcVcopy(config.bmax, bmax);
1083
1084 config.maxVertsPerPoly = DT_VERTS_PER_POLYGON;
1085 config.cs = tileConfig.BASE_UNIT_DIM;
1086 config.ch = tileConfig.BASE_UNIT_DIM;
1087 // Keeping these 2 slope angles the same reduces a lot the number of polys.
1088 // 55 should be the minimum, maybe 70 is ok (keep in mind blink uses mmaps), 85 is too much for players
1089 config.walkableSlopeAngle = m_maxWalkableAngle ? *m_maxWalkableAngle : 55;
1090 config.walkableSlopeAngleNotSteep = m_maxWalkableAngleNotSteep ? *m_maxWalkableAngleNotSteep : 55;
1091 config.tileSize = tileConfig.VERTEX_PER_TILE;
1092 config.walkableRadius = m_bigBaseUnit ? 1 : 2;
1093 config.borderSize = config.walkableRadius + 3;
1094 config.maxEdgeLen = tileConfig.VERTEX_PER_TILE + 1; // anything bigger than tileSize
1095 config.walkableHeight = m_bigBaseUnit ? 3 : 6;
1096 // a value >= 3|6 allows npcs to walk over some fences
1097 // a value >= 4|8 allows npcs to walk over all fences
1098 config.walkableClimb = m_bigBaseUnit ? 3 : 6;
1099 config.minRegionArea = rcSqr(60);
1100 config.mergeRegionArea = rcSqr(50);
1101 config.maxSimplificationError = 1.8f; // eliminates most jagged edges (tiny polygons)
1102 config.detailSampleDist = config.cs * 16;
1103 config.detailSampleMaxError = config.ch * 1;
1104
1105 switch (mapID)
1106 {
1107 // Blade's Edge Arena
1108 case 562:
1109 // This allows to walk on the ropes to the pillars
1110 config.walkableRadius = 0;
1111 break;
1112 // Blackfathom Deeps
1113 case 48:
1114 // Reduce the chance to have underground levels
1115 config.ch *= 2;
1116 break;
1117 default:
1118 break;
1119 }
1120
1121 return config;
1122 }
1123
1124 /**************************************************************************/
1125 uint32 MapBuilder::percentageDone(uint32 totalTiles, uint32 totalTilesBuilt) const
1126 {
1127 if (totalTiles)
1128 return totalTilesBuilt * 100 / totalTiles;
1129
1130 return 0;
1131 }
1132
1137
1138}
uint8_t uint8
Definition Define.h:135
uint32_t uint32
Definition Define.h:133
std::set< uint32 > params[2]
@ NAV_GROUND
Definition MapDefines.h:67
@ NAV_AREA_MIN_VALUE
Definition MapDefines.h:60
@ NAV_AREA_ALL_MASK
Definition MapDefines.h:61
@ NAV_AREA_GROUND_STEEP
Definition MapDefines.h:56
@ NAV_AREA_GROUND
Definition MapDefines.h:55
@ NAV_AREA_MAX_VALUE
Definition MapDefines.h:59
#define MMAP_VERSION
Definition MapDefines.h:26
const uint32 MMAP_MAGIC
Definition MapDefines.h:25
std::optional< T > Optional
Optional helper class to wrap optional values within.
Definition Optional.h:25
void buildMaps(Optional< uint32 > mapID)
char const * m_offMeshFilePath
Definition MapBuilder.h:199
ProducerConsumerQueue< TileInfo > _queue
Definition MapBuilder.h:219
std::vector< TileBuilder * > m_tileBuilders
Definition MapBuilder.h:218
void buildMeshFromFile(char *name)
std::set< uint32 > * getTileList(uint32 mapID)
uint32 percentageDone(uint32 totalTiles, uint32 totalTilesDone) const
Optional< float > m_maxWalkableAngleNotSteep
Definition MapBuilder.h:207
uint32 currentPercentageDone() const
void buildNavMesh(uint32 mapID, dtNavMesh *&navMesh)
bool shouldSkipMap(uint32 mapID) const
std::atomic< uint32 > m_totalTiles
Definition MapBuilder.h:212
rcConfig GetMapSpecificConfig(uint32 mapID, float bmin[3], float bmax[3], const TileConfig &tileConfig) const
rcContext * m_rcContext
Definition MapBuilder.h:216
bool isTransportMap(uint32 mapID) const
friend class TileBuilder
Definition MapBuilder.h:146
Optional< float > m_maxWalkableAngle
Definition MapBuilder.h:206
std::atomic< bool > _cancelationToken
Definition MapBuilder.h:220
bool isContinentMap(uint32 mapID) const
void getGridBounds(uint32 mapID, uint32 &minX, uint32 &minY, uint32 &maxX, uint32 &maxY) const
void buildMap(uint32 mapID)
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)
void getTileBounds(uint32 tileX, uint32 tileY, float *verts, int vertCount, float *bmin, float *bmax) const
std::atomic< uint32 > m_totalTilesProcessed
Definition MapBuilder.h:213
void buildSingleTile(uint32 mapID, uint32 tileX, uint32 tileY)
TerrainBuilder * m_terrainBuilder
Definition MapBuilder.h:194
unsigned int m_threads
Definition MapBuilder.h:200
void loadOffMeshConnections(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData, char const *offMeshFilePath)
static void cleanVertices(G3D::Array< float > &verts, G3D::Array< int > &tris)
void loadMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData)
bool usesLiquids() const
bool loadVMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData)
rcContext * m_rcContext
Definition MapBuilder.h:141
std::thread m_workerThread
Definition MapBuilder.h:139
bool shouldSkipTile(uint32 mapID, uint32 tileX, uint32 tileY) const
MapBuilder * m_mapBuilder
Definition MapBuilder.h:137
void buildTile(uint32 mapID, uint32 tileX, uint32 tileY, dtNavMesh *navMesh)
TileBuilder(MapBuilder *mapBuilder, bool skipLiquid, bool bigBaseUnit, bool debugOutput)
void buildMoveMapTile(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData, float bmin[3], float bmax[3], dtNavMesh *navMesh)
TerrainBuilder * m_terrainBuilder
Definition MapBuilder.h:138
static uint32 packTileID(uint32 tileX, uint32 tileY)
Definition MapTree.h:72
static void unpackTileID(uint32 ID, uint32 &tileX, uint32 &tileY)
Definition MapTree.h:73
static const float GRID_SIZE
ListFilesResult getDirContents(std::vector< std::string > &fileList, std::string dirpath=".", std::string filter="*")
Definition PathCommon.h:78
std::string StringFormat(FormatString< Args... > fmt, Args &&... args)
Default TC string format function.
rcPolyMeshDetail * polyMeshDetail
void generateObjFile(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData)
void writeIV(uint32 mapID, uint32 tileX, uint32 tileY)
G3D::Array< float > liquidVerts
G3D::Array< float > offMeshConnectionRads
G3D::Array< unsigned char > offMeshConnectionDirs
G3D::Array< float > offMeshConnections
G3D::Array< unsigned short > offMeshConnectionsFlags
G3D::Array< float > solidVerts
G3D::Array< int > liquidTris
G3D::Array< int > solidTris
G3D::Array< unsigned char > offMeshConnectionsAreas
G3D::Array< uint8 > liquidType
dtNavMeshParams m_navMeshParams
Definition MapBuilder.h:102
rcPolyMesh * pmesh
Definition MapBuilder.h:70
rcPolyMeshDetail * dmesh
Definition MapBuilder.h:71
rcHeightfield * solid
Definition MapBuilder.h:68
rcContourSet * cset
Definition MapBuilder.h:69
rcCompactHeightfield * chf
Definition MapBuilder.h:67
uint32 dtVersion
Definition MapDefines.h:31
uint32 mmapVersion
Definition MapDefines.h:32
uint32 mmapMagic
Definition MapDefines.h:30