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