AzerothCore 3.3.5a
OpenSource WoW Emulator
Loading...
Searching...
No Matches
MMAP::TerrainBuilder Class Reference

#include "TerrainBuilder.h"

Public Member Functions

 TerrainBuilder (const std::string &mapsPath, bool skipLiquid)
 
 ~TerrainBuilder ()
 
 TerrainBuilder (const TerrainBuilder &tb)=delete
 
void loadMap (uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData)
 
bool loadVMap (uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData)
 
void loadOffMeshConnections (uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData, const char *offMeshFilePath)
 
bool usesLiquids () const
 

Static Public Member Functions

static void transform (std::vector< G3D::Vector3 > &original, std::vector< G3D::Vector3 > &transformed, float scale, G3D::Matrix3 &rotation, G3D::Vector3 &position)
 
static void copyVertices (std::vector< G3D::Vector3 > &source, G3D::Array< float > &dest)
 
static void copyIndices (std::vector< VMAP::MeshTriangle > &source, G3D::Array< int > &dest, int offest, bool flip)
 
static void copyIndices (G3D::Array< int > &src, G3D::Array< int > &dest, int offset)
 
static void cleanVertices (G3D::Array< float > &verts, G3D::Array< int > &tris)
 

Private Member Functions

bool loadMap (uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData, Spot portion)
 Loads a portion of a map's terrain.
 
void getLoopVars (Spot portion, int &loopStart, int &loopEnd, int &loopInc)
 Sets loop variables for selecting only certain parts of a map's terrain.
 
bool loadHeightMap (uint32 mapID, uint32 tileX, uint32 tileY, G3D::Array< float > &vertices, G3D::Array< int > &triangles, Spot portion)
 Load the map terrain from file.
 
void getHeightCoord (int index, Grid grid, float xOffset, float yOffset, float *coord, float *v)
 Get the vector coordinate for a specific position.
 
void getHeightTriangle (int square, Spot triangle, int *indices, bool liquid=false)
 Get the triangle's vector indices for a specific position.
 
bool isHole (int square, const uint16 holes[16][16])
 Determines if the specific position's triangles should be rendered.
 
void getLiquidCoord (int index, int index2, float xOffset, float yOffset, float *coord, float *v)
 Get the liquid vector coordinate for a specific position.
 
uint8 getLiquidType (int square, const uint8 liquid_type[16][16])
 Get the liquid type for a specific position.
 

Private Attributes

bool m_skipLiquid
 Controls whether liquids are loaded.
 
std::string m_mapsPath
 
std::string m_vmapsPath
 

Detailed Description

Constructor & Destructor Documentation

◆ TerrainBuilder() [1/2]

MMAP::TerrainBuilder::TerrainBuilder ( const std::string &  mapsPath,
bool  skipLiquid 
)
89 :
90 m_skipLiquid (skipLiquid),
91 m_mapsPath((std::filesystem::path(dataDirPath) / "maps").string()),
92 m_vmapsPath((std::filesystem::path(dataDirPath) / "vmaps").string())
93 {
94 }
std::string m_mapsPath
Definition TerrainBuilder.h:125
bool m_skipLiquid
Controls whether liquids are loaded.
Definition TerrainBuilder.h:105
std::string m_vmapsPath
Definition TerrainBuilder.h:126

◆ ~TerrainBuilder()

MMAP::TerrainBuilder::~TerrainBuilder ( )
default

◆ TerrainBuilder() [2/2]

MMAP::TerrainBuilder::TerrainBuilder ( const TerrainBuilder tb)
delete

Member Function Documentation

◆ cleanVertices()

void MMAP::TerrainBuilder::cleanVertices ( G3D::Array< float > &  verts,
G3D::Array< int > &  tris 
)
static
895 {
896 std::map<int, int> vertMap;
897
898 int* t = tris.getCArray();
899 float* v = verts.getCArray();
900
901 G3D::Array<float> cleanVerts;
902 int index, count = 0;
903 // collect all the vertex indices from triangle
904 for (int i = 0; i < tris.size(); ++i)
905 {
906 if (vertMap.find(t[i]) != vertMap.end())
907 continue;
908 std::pair<int, int> val;
909 val.first = t[i];
910
911 index = val.first;
912 val.second = count;
913
914 vertMap.insert(val);
915 cleanVerts.append(v[index * 3], v[index * 3 + 1], v[index * 3 + 2]);
916 count++;
917 }
918
919 verts.fastClear();
920 verts.append(cleanVerts);
921 cleanVerts.clear();
922
923 // update triangles to use new indices
924 for (int i = 0; i < tris.size(); ++i)
925 {
926 std::map<int, int>::iterator it;
927 if ((it = vertMap.find(t[i])) == vertMap.end())
928 continue;
929
930 t[i] = (*it).second;
931 }
932
933 vertMap.clear();
934 }

Referenced by MMAP::MapBuilder::buildMeshFromFile(), and MMAP::TileBuilder::buildTile().

◆ copyIndices() [1/2]

void MMAP::TerrainBuilder::copyIndices ( G3D::Array< int > &  src,
G3D::Array< int > &  dest,
int  offset 
)
static
887 {
888 int* src = source.getCArray();
889 for (int32 i = 0; i < source.size(); ++i)
890 dest.append(src[i] + offset);
891 }
std::int32_t int32
Definition Define.h:103

◆ copyIndices() [2/2]

void MMAP::TerrainBuilder::copyIndices ( std::vector< VMAP::MeshTriangle > &  source,
G3D::Array< int > &  dest,
int  offest,
bool  flip 
)
static
864 {
865 if (flip)
866 {
867 for (auto & it : source)
868 {
869 dest.push_back(it.idx2 + offset);
870 dest.push_back(it.idx1 + offset);
871 dest.push_back(it.idx0 + offset);
872 }
873 }
874 else
875 {
876 for (auto & it : source)
877 {
878 dest.push_back(it.idx0 + offset);
879 dest.push_back(it.idx1 + offset);
880 dest.push_back(it.idx2 + offset);
881 }
882 }
883 }

Referenced by MMAP::IntermediateValues::generateObjFile(), and loadVMap().

◆ copyVertices()

void MMAP::TerrainBuilder::copyVertices ( std::vector< G3D::Vector3 > &  source,
G3D::Array< float > &  dest 
)
static
853 {
854 for (auto & it : source)
855 {
856 dest.push_back(it.y);
857 dest.push_back(it.z);
858 dest.push_back(it.x);
859 }
860 }

Referenced by loadVMap().

◆ getHeightCoord()

void MMAP::TerrainBuilder::getHeightCoord ( int  index,
Grid  grid,
float  xOffset,
float  yOffset,
float *  coord,
float *  v 
)
private

Get the vector coordinate for a specific position.

555 {
556 // wow coords: x, y, height
557 // coord is mirroed about the horizontal axes
558 switch (grid)
559 {
560 case GRID_V9:
561 coord[0] = (xOffset + index % (V9_SIZE) * GRID_PART_SIZE) * -1.f;
562 coord[1] = (yOffset + (int)(index / (V9_SIZE)) * GRID_PART_SIZE) * -1.f;
563 coord[2] = v[index];
564 break;
565 case GRID_V8:
566 coord[0] = (xOffset + index % (V8_SIZE) * GRID_PART_SIZE + GRID_PART_SIZE / 2.f) * -1.f;
567 coord[1] = (yOffset + (int)(index / (V8_SIZE)) * GRID_PART_SIZE + GRID_PART_SIZE / 2.f) * -1.f;
568 coord[2] = v[index];
569 break;
570 }
571 }
static const int V9_SIZE
Definition TerrainBuilder.h:44
static const float GRID_PART_SIZE
Definition TerrainBuilder.h:49
static const int V8_SIZE
Definition TerrainBuilder.h:46
@ GRID_V8
Definition TerrainBuilder.h:40
@ GRID_V9
Definition TerrainBuilder.h:41

References MMAP::GRID_PART_SIZE, MMAP::GRID_V8, MMAP::GRID_V9, MMAP::V8_SIZE, and MMAP::V9_SIZE.

Referenced by loadMap().

◆ getHeightTriangle()

void MMAP::TerrainBuilder::getHeightTriangle ( int  square,
Spot  triangle,
int *  indices,
bool  liquid = false 
)
private

Get the triangle's vector indices for a specific position.

575 {
576 int rowOffset = square / V8_SIZE;
577 if (!liquid)
578 switch (triangle)
579 {
580 case TOP:
581 indices[0] = square + rowOffset; // 0-----1 .... 128
582 indices[1] = square + 1 + rowOffset; // |\ T /|
583 indices[2] = (V9_SIZE_SQ) + square; // | \ / |
584 break; // |L 0 R| .. 127
585 case LEFT: // | / \ |
586 indices[0] = square + rowOffset; // |/ B \|
587 indices[1] = (V9_SIZE_SQ) + square; // 129---130 ... 386
588 indices[2] = square + V9_SIZE + rowOffset; // |\ /|
589 break; // | \ / |
590 case RIGHT: // | 128 | .. 255
591 indices[0] = square + 1 + rowOffset; // | / \ |
592 indices[1] = square + V9_SIZE + 1 + rowOffset; // |/ \|
593 indices[2] = (V9_SIZE_SQ) + square; // 258---259 ... 515
594 break;
595 case BOTTOM:
596 indices[0] = (V9_SIZE_SQ) + square;
597 indices[1] = square + V9_SIZE + 1 + rowOffset;
598 indices[2] = square + V9_SIZE + rowOffset;
599 break;
600 default:
601 break;
602 }
603 else
604 switch (triangle)
605 {
606 case TOP:
607 indices[0] = square + rowOffset;
608 indices[1] = square + 1 + rowOffset;
609 indices[2] = square + V9_SIZE + 1 + rowOffset;
610 break;
611 case BOTTOM:
612 indices[0] = square + rowOffset;
613 indices[1] = square + V9_SIZE + 1 + rowOffset;
614 indices[2] = square + V9_SIZE + rowOffset;
615 break;
616 default:
617 break;
618 }
619
620 /*
621 0-----1 .... 128
622 |\ |
623 | \ T |
624 | \ |
625 | B \ |
626 | \|
627 129---130 ... 386
628 |\ |
629 | \ |
630 | \ |
631 | \ |
632 | \|
633 258---259 ... 515
634 */
635 }
static const int V9_SIZE_SQ
Definition TerrainBuilder.h:45
@ LEFT
Definition TerrainBuilder.h:33
@ RIGHT
Definition TerrainBuilder.h:32
@ BOTTOM
Definition TerrainBuilder.h:34
@ TOP
Definition TerrainBuilder.h:31

References MMAP::BOTTOM, MMAP::LEFT, MMAP::RIGHT, MMAP::TOP, MMAP::V8_SIZE, MMAP::V9_SIZE, and MMAP::V9_SIZE_SQ.

Referenced by loadMap().

◆ getLiquidCoord()

void MMAP::TerrainBuilder::getLiquidCoord ( int  index,
int  index2,
float  xOffset,
float  yOffset,
float *  coord,
float *  v 
)
private

Get the liquid vector coordinate for a specific position.

639 {
640 // wow coords: x, y, height
641 // coord is mirroed about the horizontal axes
642 coord[0] = (xOffset + index % (V9_SIZE) * GRID_PART_SIZE) * -1.f;
643 coord[1] = (yOffset + (int)(index / (V9_SIZE)) * GRID_PART_SIZE) * -1.f;
644 coord[2] = v[index2];
645 }

References MMAP::GRID_PART_SIZE, and MMAP::V9_SIZE.

Referenced by loadMap().

◆ getLiquidType()

uint8 MMAP::TerrainBuilder::getLiquidType ( int  square,
const uint8  liquid_type[16][16] 
)
private

Get the liquid type for a specific position.

667 {
668 int row = square / 128;
669 int col = square % 128;
670 int cellRow = row / 8; // 8 squares per cell
671 int cellCol = col / 8;
672
673 return liquid_type[cellRow][cellCol];
674 }

Referenced by loadMap().

◆ getLoopVars()

void MMAP::TerrainBuilder::getLoopVars ( Spot  portion,
int &  loopStart,
int &  loopEnd,
int &  loopInc 
)
private

Sets loop variables for selecting only certain parts of a map's terrain.

100 {
101 switch (portion)
102 {
103 case ENTIRE:
104 loopStart = 0;
105 loopEnd = V8_SIZE_SQ;
106 loopInc = 1;
107 break;
108 case TOP:
109 loopStart = 0;
110 loopEnd = V8_SIZE;
111 loopInc = 1;
112 break;
113 case LEFT:
114 loopStart = 0;
115 loopEnd = V8_SIZE_SQ - V8_SIZE + 1;
116 loopInc = V8_SIZE;
117 break;
118 case RIGHT:
119 loopStart = V8_SIZE - 1;
120 loopEnd = V8_SIZE_SQ;
121 loopInc = V8_SIZE;
122 break;
123 case BOTTOM:
124 loopStart = V8_SIZE_SQ - V8_SIZE;
125 loopEnd = V8_SIZE_SQ;
126 loopInc = 1;
127 break;
128 }
129 }
static const int V8_SIZE_SQ
Definition TerrainBuilder.h:47
@ ENTIRE
Definition TerrainBuilder.h:35

References MMAP::BOTTOM, MMAP::ENTIRE, MMAP::LEFT, MMAP::RIGHT, MMAP::TOP, MMAP::V8_SIZE, and MMAP::V8_SIZE_SQ.

Referenced by loadMap().

◆ isHole()

bool MMAP::TerrainBuilder::isHole ( int  square,
const uint16  holes[16][16] 
)
private

Determines if the specific position's triangles should be rendered.

652 {
653 int row = square / 128;
654 int col = square % 128;
655 int cellRow = row / 8; // 8 squares per cell
656 int cellCol = col / 8;
657 int holeRow = row % 8 / 2;
658 int holeCol = (square - (row * 128 + cellCol * 8)) / 2;
659
660 uint16 hole = holes[cellRow][cellCol];
661
662 return (hole & holetab_h[holeCol] & holetab_v[holeRow]) != 0;
663 }
std::uint16_t uint16
Definition Define.h:108
uint16 holes[ADT_CELLS_PER_GRID][ADT_CELLS_PER_GRID]
Definition System.cpp:409
static uint16 holetab_v[4]
Definition TerrainBuilder.cpp:648
static uint16 holetab_h[4]
Definition TerrainBuilder.cpp:647

References holes, MMAP::holetab_h, and MMAP::holetab_v.

Referenced by loadMap().

◆ loadHeightMap()

bool MMAP::TerrainBuilder::loadHeightMap ( uint32  mapID,
uint32  tileX,
uint32  tileY,
G3D::Array< float > &  vertices,
G3D::Array< int > &  triangles,
Spot  portion 
)
private

Load the map terrain from file.

◆ loadMap() [1/2]

void MMAP::TerrainBuilder::loadMap ( uint32  mapID,
uint32  tileX,
uint32  tileY,
MeshData meshData 
)
133 {
134 if (loadMap(mapID, tileX, tileY, meshData, ENTIRE))
135 {
136 loadMap(mapID, tileX + 1, tileY, meshData, LEFT);
137 loadMap(mapID, tileX - 1, tileY, meshData, RIGHT);
138 loadMap(mapID, tileX, tileY + 1, meshData, TOP);
139 loadMap(mapID, tileX, tileY - 1, meshData, BOTTOM);
140 }
141 }
void loadMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData)
Definition TerrainBuilder.cpp:132

References MMAP::BOTTOM, MMAP::ENTIRE, MMAP::LEFT, loadMap(), MMAP::RIGHT, and MMAP::TOP.

Referenced by MMAP::TileBuilder::buildTile(), and loadMap().

◆ loadMap() [2/2]

bool MMAP::TerrainBuilder::loadMap ( uint32  mapID,
uint32  tileX,
uint32  tileY,
MeshData meshData,
Spot  portion 
)
private

Loads a portion of a map's terrain.

145 {
146 const std::string mapFileName = Acore::StringFormat(
149 mapID, tileY, tileX
150 );
151
152 FILE* mapFile = fopen(mapFileName.c_str(), "rb");
153 if (!mapFile)
154 return false;
155
156 map_fileheader fheader;
157 if (fread(&fheader, sizeof(map_fileheader), 1, mapFile) != 1 ||
159 {
160 fclose(mapFile);
161 printf("%s is the wrong version, please extract new .map files\n", mapFileName.c_str());
162 return false;
163 }
164
165 map_heightHeader hheader;
166 fseek(mapFile, fheader.heightMapOffset, SEEK_SET);
167
168 bool haveTerrain = false;
169 bool haveLiquid = false;
170 if (fread(&hheader, sizeof(map_heightHeader), 1, mapFile) == 1)
171 {
172 haveTerrain = !(hheader.flags & MAP_HEIGHT_NO_HEIGHT);
173 haveLiquid = fheader.liquidMapOffset && !m_skipLiquid;
174 }
175
176 // no data in this map file
177 if (!haveTerrain && !haveLiquid)
178 {
179 fclose(mapFile);
180 return false;
181 }
182
183 // data used later
184 uint16 holes[16][16];
185 memset(holes, 0, sizeof(holes));
186 uint16 liquid_entry[16][16];
187 memset(liquid_entry, 0, sizeof(liquid_entry));
188 uint8 liquid_flags[16][16];
189 memset(liquid_flags, 0, sizeof(liquid_flags));
190 G3D::Array<int> ltriangles;
191 G3D::Array<int> ttriangles;
192
193 // terrain data
194 if (haveTerrain)
195 {
196 float heightMultiplier;
197 float V9[V9_SIZE_SQ], V8[V8_SIZE_SQ];
198 int expected = V9_SIZE_SQ + V8_SIZE_SQ;
199
200 if (hheader.flags & MAP_HEIGHT_AS_INT8)
201 {
202 uint8 v9[V9_SIZE_SQ];
203 uint8 v8[V8_SIZE_SQ];
204 int count = 0;
205 count += fread(v9, sizeof(uint8), V9_SIZE_SQ, mapFile);
206 count += fread(v8, sizeof(uint8), V8_SIZE_SQ, mapFile);
207 if (count != expected)
208 printf("TerrainBuilder::loadMap: Failed to read some data expected %d, read %d\n", expected, count);
209
210 heightMultiplier = (hheader.gridMaxHeight - hheader.gridHeight) / 255;
211
212 for (int i = 0; i < V9_SIZE_SQ; ++i)
213 V9[i] = (float)v9[i] * heightMultiplier + hheader.gridHeight;
214
215 for (int i = 0; i < V8_SIZE_SQ; ++i)
216 V8[i] = (float)v8[i] * heightMultiplier + hheader.gridHeight;
217 }
218 else if (hheader.flags & MAP_HEIGHT_AS_INT16)
219 {
220 uint16 v9[V9_SIZE_SQ];
221 uint16 v8[V8_SIZE_SQ];
222 int count = 0;
223 count += fread(v9, sizeof(uint16), V9_SIZE_SQ, mapFile);
224 count += fread(v8, sizeof(uint16), V8_SIZE_SQ, mapFile);
225 if (count != expected)
226 printf("TerrainBuilder::loadMap: Failed to read some data expected %d, read %d\n", expected, count);
227
228 heightMultiplier = (hheader.gridMaxHeight - hheader.gridHeight) / 65535;
229
230 for (int i = 0; i < V9_SIZE_SQ; ++i)
231 V9[i] = (float)v9[i] * heightMultiplier + hheader.gridHeight;
232
233 for (int i = 0; i < V8_SIZE_SQ; ++i)
234 V8[i] = (float)v8[i] * heightMultiplier + hheader.gridHeight;
235 }
236 else
237 {
238 int count = 0;
239 count += fread(V9, sizeof(float), V9_SIZE_SQ, mapFile);
240 count += fread(V8, sizeof(float), V8_SIZE_SQ, mapFile);
241 if (count != expected)
242 printf("TerrainBuilder::loadMap: Failed to read some data expected %d, read %d\n", expected, count);
243 }
244
245 // hole data
246 if (fheader.holesSize != 0)
247 {
248 memset(holes, 0, fheader.holesSize);
249 fseek(mapFile, fheader.holesOffset, SEEK_SET);
250 if (fread(holes, fheader.holesSize, 1, mapFile) != 1)
251 printf("TerrainBuilder::loadMap: Failed to read some data expected 1, read 0\n");
252 }
253
254 int count = meshData.solidVerts.size() / 3;
255 float xoffset = (float(tileX) - 32) * GRID_SIZE;
256 float yoffset = (float(tileY) - 32) * GRID_SIZE;
257
258 float coord[3];
259
260 for (int i = 0; i < V9_SIZE_SQ; ++i)
261 {
262 getHeightCoord(i, GRID_V9, xoffset, yoffset, coord, V9);
263 meshData.solidVerts.append(coord[0]);
264 meshData.solidVerts.append(coord[2]);
265 meshData.solidVerts.append(coord[1]);
266 }
267
268 for (int i = 0; i < V8_SIZE_SQ; ++i)
269 {
270 getHeightCoord(i, GRID_V8, xoffset, yoffset, coord, V8);
271 meshData.solidVerts.append(coord[0]);
272 meshData.solidVerts.append(coord[2]);
273 meshData.solidVerts.append(coord[1]);
274 }
275
276 int indices[] = { 0, 0, 0 };
277 int loopStart = 0, loopEnd = 0, loopInc = 0;
278 getLoopVars(portion, loopStart, loopEnd, loopInc);
279 for (int i = loopStart; i < loopEnd; i += loopInc)
280 for (int j = TOP; j <= BOTTOM; j += 1)
281 {
282 getHeightTriangle(i, Spot(j), indices);
283 ttriangles.append(indices[2] + count);
284 ttriangles.append(indices[1] + count);
285 ttriangles.append(indices[0] + count);
286 }
287 }
288
289 // liquid data
290 if (haveLiquid)
291 {
292 map_liquidHeader lheader;
293 fseek(mapFile, fheader.liquidMapOffset, SEEK_SET);
294 if (fread(&lheader, sizeof(map_liquidHeader), 1, mapFile) != 1)
295 printf("TerrainBuilder::loadMap: Failed to read some data expected 1, read 0\n");
296
297 float* liquid_map = nullptr;
298
299 if (!(lheader.flags & MAP_LIQUID_NO_TYPE))
300 {
301 if (fread(liquid_entry, sizeof(liquid_entry), 1, mapFile) != 1)
302 printf("TerrainBuilder::loadMap: Failed to read some data expected 1, read 0\n");
303 if (fread(liquid_flags, sizeof(liquid_flags), 1, mapFile) != 1)
304 printf("TerrainBuilder::loadMap: Failed to read some data expected 1, read 0\n");
305 }
306 else
307 {
308 std::fill_n(&liquid_entry[0][0], 16 * 16, lheader.liquidType);
309 std::fill_n(&liquid_flags[0][0], 16 * 16, lheader.liquidFlags);
310 }
311
312 if (!(lheader.flags & MAP_LIQUID_NO_HEIGHT))
313 {
314 uint32 toRead = lheader.width * lheader.height;
315 liquid_map = new float [toRead];
316 if (fread(liquid_map, sizeof(float), toRead, mapFile) != toRead)
317 {
318 printf("TerrainBuilder::loadMap: Failed to read some data expected 1, read 0\n");
319 delete[] liquid_map;
320 liquid_map = nullptr;
321 }
322 }
323
324 int count = meshData.liquidVerts.size() / 3;
325 float xoffset = (float(tileX)-32)*GRID_SIZE;
326 float yoffset = (float(tileY)-32)*GRID_SIZE;
327
328 float coord[3];
329 int row, col;
330
331 // generate coordinates
332 if (!(lheader.flags & MAP_LIQUID_NO_HEIGHT))
333 {
334 int j = 0;
335 for (int i = 0; i < V9_SIZE_SQ; ++i)
336 {
337 row = i / V9_SIZE;
338 col = i % V9_SIZE;
339
340 if (row < lheader.offsetY || row >= lheader.offsetY + lheader.height ||
341 col < lheader.offsetX || col >= lheader.offsetX + lheader.width)
342 {
343 // dummy vert using invalid height
344 meshData.liquidVerts.append((xoffset+col*GRID_PART_SIZE)*-1, INVALID_MAP_LIQ_HEIGHT, (yoffset+row*GRID_PART_SIZE)*-1);
345 continue;
346 }
347
348 getLiquidCoord(i, j, xoffset, yoffset, coord, liquid_map);
349 meshData.liquidVerts.append(coord[0]);
350 meshData.liquidVerts.append(coord[2]);
351 meshData.liquidVerts.append(coord[1]);
352 j++;
353 }
354 }
355 else
356 {
357 for (int i = 0; i < V9_SIZE_SQ; ++i)
358 {
359 row = i / V9_SIZE;
360 col = i % V9_SIZE;
361 meshData.liquidVerts.append((xoffset+col*GRID_PART_SIZE)*-1, lheader.liquidLevel, (yoffset+row*GRID_PART_SIZE)*-1);
362 }
363 }
364
365 delete[] liquid_map;
366
367 int indices[] = { 0, 0, 0 };
368 int loopStart = 0, loopEnd = 0, loopInc = 0, triInc = BOTTOM-TOP;
369 getLoopVars(portion, loopStart, loopEnd, loopInc);
370
371 // generate triangles
372 for (int i = loopStart; i < loopEnd; i += loopInc)
373 {
374 for (int j = TOP; j <= BOTTOM; j += triInc)
375 {
376 getHeightTriangle(i, Spot(j), indices, true);
377 ltriangles.append(indices[2] + count);
378 ltriangles.append(indices[1] + count);
379 ltriangles.append(indices[0] + count);
380 }
381 }
382 }
383
384 fclose(mapFile);
385
386 // now that we have gathered the data, we can figure out which parts to keep:
387 // liquid above ground, ground above liquid
388 int loopStart = 0, loopEnd = 0, loopInc = 0, tTriCount = 4;
389 bool useTerrain, useLiquid;
390
391 float* lverts = meshData.liquidVerts.getCArray();
392 int* ltris = ltriangles.getCArray();
393
394 float* tverts = meshData.solidVerts.getCArray();
395 int* ttris = ttriangles.getCArray();
396
397 if ((ltriangles.size() + ttriangles.size()) == 0)
398 return false;
399
400 // make a copy of liquid vertices
401 // used to pad right-bottom frame due to lost vertex data at extraction
402 float* lverts_copy = nullptr;
403 if (meshData.liquidVerts.size())
404 {
405 lverts_copy = new float[meshData.liquidVerts.size()];
406 memcpy(lverts_copy, lverts, sizeof(float)*meshData.liquidVerts.size());
407 }
408
409 getLoopVars(portion, loopStart, loopEnd, loopInc);
410 for (int i = loopStart; i < loopEnd; i += loopInc)
411 {
412 for (int j = 0; j < 2; ++j)
413 {
414 // default is true, will change to false if needed
415 useTerrain = true;
416 useLiquid = true;
417 uint8 liquidType = MAP_LIQUID_TYPE_NO_WATER;
418 // FIXME: "warning: the address of ‘liquid_type’ will always evaluate as ‘true’"
419
420 // if there is no liquid, don't use liquid
421 if (!meshData.liquidVerts.size() || !ltriangles.size())
422 {
423 useLiquid = false;
424 }
425 else
426 {
427 liquidType = getLiquidType(i, liquid_flags);
428 switch (liquidType)
429 {
430 default:
431 useLiquid = false;
432 break;
435 // merge different types of water
436 liquidType = NAV_WATER;
437 break;
439 liquidType = NAV_MAGMA;
440 break;
442 liquidType = NAV_SLIME;
443 break;
445 // players should not be here, so logically neither should creatures
446 useTerrain = false;
447 useLiquid = false;
448 break;
449 }
450 }
451
452 // if there is no terrain, don't use terrain
453 if (!ttriangles.size())
454 useTerrain = false;
455
456 // while extracting ADT data we are losing right-bottom vertices
457 // this code adds fair approximation of lost data
458 if (useLiquid)
459 {
460 float quadHeight = 0;
461 uint32 validCount = 0;
462 for (uint32 idx = 0; idx < 3; idx++)
463 {
464 float h = lverts_copy[ltris[idx] * 3 + 1];
466 {
467 quadHeight += h;
468 validCount++;
469 }
470 }
471
472 // update vertex height data
473 if (validCount > 0 && validCount < 3)
474 {
475 quadHeight /= validCount;
476 for (uint32 idx = 0; idx < 3; idx++)
477 {
478 float h = lverts[ltris[idx] * 3 + 1];
480 lverts[ltris[idx] * 3 + 1] = quadHeight;
481 }
482 }
483
484 // no valid vertexes - don't use this poly at all
485 if (validCount == 0)
486 useLiquid = false;
487 }
488
489 // if there is a hole here, don't use the terrain
490 if (useTerrain && fheader.holesSize != 0)
491 useTerrain = !isHole(i, holes);
492
493 // we use only one terrain kind per quad - pick higher one
494 if (useTerrain && useLiquid)
495 {
496 float minLLevel = INVALID_MAP_LIQ_HEIGHT_MAX;
497 float maxLLevel = INVALID_MAP_LIQ_HEIGHT;
498 for (uint32 x = 0; x < 3; x++)
499 {
500 float h = lverts[ltris[x] * 3 + 1];
501 if (minLLevel > h)
502 minLLevel = h;
503
504 if (maxLLevel < h)
505 maxLLevel = h;
506 }
507
508 float maxTLevel = INVALID_MAP_LIQ_HEIGHT;
509 float minTLevel = INVALID_MAP_LIQ_HEIGHT_MAX;
510 for (uint32 x = 0; x < 6; x++)
511 {
512 float h = tverts[ttris[x] * 3 + 1];
513 if (maxTLevel < h)
514 maxTLevel = h;
515
516 if (minTLevel > h)
517 minTLevel = h;
518 }
519
520 // terrain under the liquid?
521 if (minLLevel > maxTLevel)
522 useTerrain = false;
523
524 //liquid under the terrain?
525 if (minTLevel > maxLLevel)
526 useLiquid = false;
527 }
528
529 // store the result
530 if (useLiquid)
531 {
532 meshData.liquidType.append(liquidType);
533 for (int k = 0; k < 3; ++k)
534 meshData.liquidTris.append(ltris[k]);
535 }
536
537 if (useTerrain)
538 for (int k = 0; k < 3 * tTriCount / 2; ++k)
539 meshData.solidTris.append(ttris[k]);
540
541 // advance to next set of triangles
542 ltris += 3;
543 ttris += 3 * tTriCount / 2;
544 }
545 }
546
547 if (lverts_copy)
548 delete [] lverts_copy;
549
550 return meshData.solidTris.size() || meshData.liquidTris.size();
551 }
std::uint8_t uint8
Definition Define.h:109
std::uint32_t uint32
Definition Define.h:107
#define MAP_LIQUID_TYPE_MAGMA
Definition GridTerrainData.h:37
#define MAP_HEIGHT_AS_INT8
Definition GridTerrainData.h:85
#define MAP_LIQUID_TYPE_NO_WATER
Definition GridTerrainData.h:34
#define MAP_LIQUID_NO_TYPE
Definition GridTerrainData.h:96
#define MAP_LIQUID_NO_HEIGHT
Definition GridTerrainData.h:97
#define MAP_LIQUID_TYPE_WATER
Definition GridTerrainData.h:35
#define MAP_LIQUID_TYPE_DARK_WATER
Definition GridTerrainData.h:42
#define MAP_LIQUID_TYPE_OCEAN
Definition GridTerrainData.h:36
#define MAP_LIQUID_TYPE_SLIME
Definition GridTerrainData.h:38
#define MAP_HEIGHT_NO_HEIGHT
Definition GridTerrainData.h:83
#define MAP_HEIGHT_AS_INT16
Definition GridTerrainData.h:84
@ NAV_MAGMA
Definition MapDefines.h:92
@ NAV_SLIME
Definition MapDefines.h:93
@ NAV_WATER
Definition MapDefines.h:94
uint16 liquid_entry[ADT_CELLS_PER_GRID][ADT_CELLS_PER_GRID]
Definition System.cpp:405
uint8 liquid_flags[ADT_CELLS_PER_GRID][ADT_CELLS_PER_GRID]
Definition System.cpp:406
float V8[ADT_GRID_SIZE][ADT_GRID_SIZE]
Definition System.cpp:398
float V9[ADT_GRID_SIZE+1][ADT_GRID_SIZE+1]
Definition System.cpp:399
bool isHole(int square, const uint16 holes[16][16])
Determines if the specific position's triangles should be rendered.
Definition TerrainBuilder.cpp:651
uint8 getLiquidType(int square, const uint8 liquid_type[16][16])
Get the liquid type for a specific position.
Definition TerrainBuilder.cpp:666
void getHeightTriangle(int square, Spot triangle, int *indices, bool liquid=false)
Get the triangle's vector indices for a specific position.
Definition TerrainBuilder.cpp:574
void getLiquidCoord(int index, int index2, float xOffset, float yOffset, float *coord, float *v)
Get the liquid vector coordinate for a specific position.
Definition TerrainBuilder.cpp:638
void getHeightCoord(int index, Grid grid, float xOffset, float yOffset, float *coord, float *v)
Get the vector coordinate for a specific position.
Definition TerrainBuilder.cpp:554
void getLoopVars(Spot portion, int &loopStart, int &loopEnd, int &loopInc)
Sets loop variables for selecting only certain parts of a map's terrain.
Definition TerrainBuilder.cpp:99
std::string StringFormat(FormatString< Args... > fmt, Args &&... args)
Default AC string format function.
Definition StringFormat.h:34
static const float GRID_SIZE
Definition TerrainBuilder.h:48
static char const *const MAP_FILE_NAME_FORMAT
Definition MMapMgr.h:42
static const float INVALID_MAP_LIQ_HEIGHT_MAX
Definition TerrainBuilder.h:53
static const float INVALID_MAP_LIQ_HEIGHT
Definition TerrainBuilder.h:52
Spot
Definition TerrainBuilder.h:30
uint32 const MAP_VERSION_MAGIC
Definition TerrainBuilder.cpp:87
Definition GridTerrainData.h:60
uint32 holesSize
Definition GridTerrainData.h:71
uint32 heightMapOffset
Definition GridTerrainData.h:66
uint32 holesOffset
Definition GridTerrainData.h:70
uint32 versionMagic
Definition GridTerrainData.h:62
uint32 liquidMapOffset
Definition GridTerrainData.h:68
Definition GridTerrainData.h:89
float gridMaxHeight
Definition GridTerrainData.h:93
uint32 flags
Definition GridTerrainData.h:91
float gridHeight
Definition GridTerrainData.h:92
Definition GridTerrainData.h:100
uint8 offsetX
Definition GridTerrainData.h:105
uint8 liquidFlags
Definition GridTerrainData.h:103
uint8 width
Definition GridTerrainData.h:107
uint8 height
Definition GridTerrainData.h:108
uint8 flags
Definition GridTerrainData.h:102
uint16 liquidType
Definition GridTerrainData.h:104
uint8 offsetY
Definition GridTerrainData.h:106
float liquidLevel
Definition GridTerrainData.h:109

References MMAP::BOTTOM, map_heightHeader::flags, map_liquidHeader::flags, getHeightCoord(), getHeightTriangle(), getLiquidCoord(), getLiquidType(), getLoopVars(), MMAP::GRID_PART_SIZE, MMAP::GRID_SIZE, MMAP::GRID_V8, MMAP::GRID_V9, map_heightHeader::gridHeight, map_heightHeader::gridMaxHeight, map_liquidHeader::height, map_fileheader::heightMapOffset, holes, map_fileheader::holesOffset, map_fileheader::holesSize, MMAP::INVALID_MAP_LIQ_HEIGHT, MMAP::INVALID_MAP_LIQ_HEIGHT_MAX, isHole(), liquid_entry, liquid_flags, map_liquidHeader::liquidFlags, map_liquidHeader::liquidLevel, map_fileheader::liquidMapOffset, MMAP::MeshData::liquidTris, map_liquidHeader::liquidType, MMAP::MeshData::liquidType, MMAP::MeshData::liquidVerts, m_mapsPath, m_skipLiquid, MMAP::MAP_FILE_NAME_FORMAT, MAP_HEIGHT_AS_INT16, MAP_HEIGHT_AS_INT8, MAP_HEIGHT_NO_HEIGHT, MAP_LIQUID_NO_HEIGHT, MAP_LIQUID_NO_TYPE, MAP_LIQUID_TYPE_DARK_WATER, MAP_LIQUID_TYPE_MAGMA, MAP_LIQUID_TYPE_NO_WATER, MAP_LIQUID_TYPE_OCEAN, MAP_LIQUID_TYPE_SLIME, MAP_LIQUID_TYPE_WATER, MMAP::MAP_VERSION_MAGIC, NAV_MAGMA, NAV_SLIME, NAV_WATER, map_liquidHeader::offsetX, map_liquidHeader::offsetY, MMAP::MeshData::solidTris, MMAP::MeshData::solidVerts, Acore::StringFormat(), MMAP::TOP, V8, MMAP::V8_SIZE_SQ, V9, MMAP::V9_SIZE, MMAP::V9_SIZE_SQ, map_fileheader::versionMagic, and map_liquidHeader::width.

◆ loadOffMeshConnections()

void MMAP::TerrainBuilder::loadOffMeshConnections ( uint32  mapID,
uint32  tileX,
uint32  tileY,
MeshData meshData,
const char *  offMeshFilePath 
)
938 {
939 // no meshfile input given?
940 if (!offMeshFilePath)
941 return;
942
943 FILE* fp = fopen(offMeshFilePath, "rb");
944 if (!fp)
945 {
946 printf(" loadOffMeshConnections:: input file %s not found!\n", offMeshFilePath);
947 return;
948 }
949
950 // pretty silly thing, as we parse entire file and load only the tile we need
951 // but we don't expect this file to be too large
952 char* buf = new char[512];
953 while (fgets(buf, 512, fp))
954 {
955 float p0[3], p1[3];
956 uint32 mid, tx, ty;
957 float size;
958 if (sscanf(buf, "%u %u,%u (%f %f %f) (%f %f %f) %f", &mid, &tx, &ty,
959 &p0[0], &p0[1], &p0[2], &p1[0], &p1[1], &p1[2], &size) != 10)
960 continue;
961
962 if (mapID == mid && tileX == tx && tileY == ty)
963 {
964 meshData.offMeshConnections.append(p0[1]);
965 meshData.offMeshConnections.append(p0[2]);
966 meshData.offMeshConnections.append(p0[0]);
967
968 meshData.offMeshConnections.append(p1[1]);
969 meshData.offMeshConnections.append(p1[2]);
970 meshData.offMeshConnections.append(p1[0]);
971
972 meshData.offMeshConnectionDirs.append(1); // 1 - both direction, 0 - one sided
973 meshData.offMeshConnectionRads.append(size); // agent size equivalent
974 // can be used same way as polygon flags
975 meshData.offMeshConnectionsAreas.append((unsigned char)0xFF);
976 meshData.offMeshConnectionsFlags.append((unsigned short)0xFF); // all movement masks can make this path
977 }
978 }
979
980 delete [] buf;
981 fclose(fp);
982 }

References MMAP::MeshData::offMeshConnectionDirs, MMAP::MeshData::offMeshConnectionRads, MMAP::MeshData::offMeshConnections, MMAP::MeshData::offMeshConnectionsAreas, and MMAP::MeshData::offMeshConnectionsFlags.

Referenced by MMAP::TileBuilder::buildTile().

◆ loadVMap()

bool MMAP::TerrainBuilder::loadVMap ( uint32  mapID,
uint32  tileX,
uint32  tileY,
MeshData meshData 
)
678 {
679 IVMapMgr* vmapMgr = new VMapMgr2();
680 int result = vmapMgr->loadMap(m_vmapsPath.c_str(), mapID, tileX, tileY);
681 bool retval = false;
682
683 do
684 {
685 if (result == VMAP_LOAD_RESULT_ERROR)
686 break;
687
688 InstanceTreeMap instanceTrees;
689 ((VMapMgr2*)vmapMgr)->GetInstanceMapTree(instanceTrees);
690
691 if (!instanceTrees[mapID])
692 break;
693
694 ModelInstance* models = nullptr;
695 uint32 count = 0;
696 instanceTrees[mapID]->GetModelInstances(models, count);
697
698 if (!models)
699 break;
700
701 for (uint32 i = 0; i < count; ++i)
702 {
703 ModelInstance instance = models[i];
704
705 // model instances exist in tree even though there are instances of that model in this tile
706 WorldModel* worldModel = instance.getWorldModel();
707 if (!worldModel)
708 continue;
709
710 // now we have a model to add to the meshdata
711 retval = true;
712
713 std::vector<GroupModel> groupModels;
714 worldModel->GetGroupModels(groupModels);
715
716 // all M2s need to have triangle indices reversed
717 bool isM2 = instance.name.find(".m2") != std::string::npos || instance.name.find(".M2") != std::string::npos;
718
719 // transform data
720 float scale = instance.iScale;
721 G3D::Matrix3 rotation = G3D::Matrix3::fromEulerAnglesXYZ(G3D::pi() * instance.iRot.z / -180.f, G3D::pi() * instance.iRot.x / -180.f, G3D::pi() * instance.iRot.y / -180.f);
722 G3D::Vector3 position = instance.iPos;
723 position.x -= 32 * GRID_SIZE;
724 position.y -= 32 * GRID_SIZE;
725
726 for (auto & groupModel : groupModels)
727 {
728 std::vector<G3D::Vector3> tempVertices;
729 std::vector<G3D::Vector3> transformedVertices;
730 std::vector<MeshTriangle> tempTriangles;
731 WmoLiquid* liquid = nullptr;
732
733 groupModel.GetMeshData(tempVertices, tempTriangles, liquid);
734
735 // first handle collision mesh
736 transform(tempVertices, transformedVertices, scale, rotation, position);
737
738 int offset = meshData.solidVerts.size() / 3;
739
740 copyVertices(transformedVertices, meshData.solidVerts);
741 copyIndices(tempTriangles, meshData.solidTris, offset, isM2);
742
743 // now handle liquid data
744 if (liquid && liquid->GetFlagsStorage())
745 {
746 std::vector<G3D::Vector3> liqVerts;
747 std::vector<int> liqTris;
748 uint32 tilesX, tilesY, vertsX, vertsY;
749 G3D::Vector3 corner;
750 liquid->GetPosInfo(tilesX, tilesY, corner);
751 vertsX = tilesX + 1;
752 vertsY = tilesY + 1;
753 uint8* flags = liquid->GetFlagsStorage();
754 float* data = liquid->GetHeightStorage();
755 uint8 type = NAV_EMPTY;
756
757 switch (liquid->GetType() & 3)
758 {
759 case 0:
760 case 1:
761 type = NAV_WATER;
762 break;
763 case 2:
764 type = NAV_MAGMA;
765 break;
766 case 3:
767 type = NAV_SLIME;
768 break;
769 }
770
771 // indexing is weird...
772 // after a lot of trial and error, this is what works:
773 // vertex = y*vertsX+x
774 // tile = x*tilesY+y
775 // flag = y*tilesY+x
776
777 G3D::Vector3 vert;
778 for (uint32 x = 0; x < vertsX; ++x)
779 {
780 for (uint32 y = 0; y < vertsY; ++y)
781 {
782 vert = G3D::Vector3(corner.x + x * GRID_PART_SIZE, corner.y + y * GRID_PART_SIZE, data[y * vertsX + x]);
783 vert = vert * rotation * scale + position;
784 vert.x *= -1.f;
785 vert.y *= -1.f;
786 liqVerts.push_back(vert);
787 }
788 }
789
790 int idx1, idx2, idx3, idx4;
791 uint32 square;
792 for (uint32 x = 0; x < tilesX; ++x)
793 {
794 for (uint32 y = 0; y < tilesY; ++y)
795 {
796 if ((flags[x + y * tilesX] & 0x0f) != 0x0f)
797 {
798 square = x * tilesY + y;
799 idx1 = square + x;
800 idx2 = square + 1 + x;
801 idx3 = square + tilesY + 1 + 1 + x;
802 idx4 = square + tilesY + 1 + x;
803
804 // top triangle
805 liqTris.push_back(idx3);
806 liqTris.push_back(idx2);
807 liqTris.push_back(idx1);
808 // bottom triangle
809 liqTris.push_back(idx4);
810 liqTris.push_back(idx3);
811 liqTris.push_back(idx1);
812 }
813 }
814 }
815
816 uint32 liqOffset = meshData.liquidVerts.size() / 3;
817 for (auto & liqVert : liqVerts)
818 {
819 meshData.liquidVerts.append(liqVert.y, liqVert.z, liqVert.x);
820 }
821
822 for (uint32 j = 0; j < liqTris.size() / 3; ++j)
823 {
824 meshData.liquidTris.append(liqTris[j * 3 + 1] + liqOffset, liqTris[j * 3 + 2] + liqOffset, liqTris[j * 3] + liqOffset);
825 meshData.liquidType.append(type);
826 }
827 }
828 }
829 }
830 } while (false);
831
832 vmapMgr->unloadMap(mapID, tileX, tileY);
833 delete vmapMgr;
834
835 return retval;
836 }
@ NAV_EMPTY
Definition MapDefines.h:90
static void copyVertices(std::vector< G3D::Vector3 > &source, G3D::Array< float > &dest)
Definition TerrainBuilder.cpp:852
static void transform(std::vector< G3D::Vector3 > &original, std::vector< G3D::Vector3 > &transformed, float scale, G3D::Matrix3 &rotation, G3D::Vector3 &position)
Definition TerrainBuilder.cpp:839
static void copyIndices(std::vector< VMAP::MeshTriangle > &source, G3D::Array< int > &dest, int offest, bool flip)
Definition TerrainBuilder.cpp:863
Definition IVMapMgr.h:81
virtual void unloadMap(unsigned int pMapId, int x, int y)=0
virtual int loadMap(const char *pBasePath, unsigned int pMapId, int x, int y)=0
Definition ModelInstance.h:63
WorldModel * getWorldModel()
Definition ModelInstance.h:71
std::string name
Definition ModelInstance.h:52
G3D::Vector3 iRot
Definition ModelInstance.h:49
float iScale
Definition ModelInstance.h:50
G3D::Vector3 iPos
Definition ModelInstance.h:48
Definition VMapMgr2.h:76
Definition WorldModel.h:47
float * GetHeightStorage()
Definition WorldModel.h:55
uint32 GetType() const
Definition WorldModel.h:54
uint8 * GetFlagsStorage()
Definition WorldModel.h:56
void GetPosInfo(uint32 &tilesX, uint32 &tilesY, G3D::Vector3 &corner) const
Definition WorldModel.cpp:305
Definition WorldModel.h:107
void GetGroupModels(std::vector< GroupModel > &outGroupModels)
Definition WorldModel.cpp:703
std::unordered_map< uint32, StaticMapTree * > InstanceTreeMap
Definition VMapMgr2.h:64
@ VMAP_LOAD_RESULT_ERROR
Definition IVMapMgr.h:36

References copyIndices(), copyVertices(), VMAP::WmoLiquid::GetFlagsStorage(), VMAP::WorldModel::GetGroupModels(), VMAP::WmoLiquid::GetHeightStorage(), VMAP::WmoLiquid::GetPosInfo(), VMAP::WmoLiquid::GetType(), VMAP::ModelInstance::getWorldModel(), MMAP::GRID_PART_SIZE, MMAP::GRID_SIZE, VMAP::ModelSpawn::iPos, VMAP::ModelSpawn::iRot, VMAP::ModelSpawn::iScale, MMAP::MeshData::liquidTris, MMAP::MeshData::liquidType, MMAP::MeshData::liquidVerts, VMAP::IVMapMgr::loadMap(), m_vmapsPath, VMAP::ModelSpawn::name, NAV_EMPTY, NAV_MAGMA, NAV_SLIME, NAV_WATER, MMAP::MeshData::solidTris, MMAP::MeshData::solidVerts, transform(), VMAP::IVMapMgr::unloadMap(), and VMAP::VMAP_LOAD_RESULT_ERROR.

Referenced by MMAP::TileBuilder::buildTile(), and MMAP::MapBuilder::getGridBounds().

◆ transform()

void MMAP::TerrainBuilder::transform ( std::vector< G3D::Vector3 > &  original,
std::vector< G3D::Vector3 > &  transformed,
float  scale,
G3D::Matrix3 &  rotation,
G3D::Vector3 &  position 
)
static
840 {
841 for (auto & it : source)
842 {
843 // apply tranform, then mirror along the horizontal axes
844 G3D::Vector3 v(it * rotation * scale + position);
845 v.x *= -1.f;
846 v.y *= -1.f;
847 transformedVertices.push_back(v);
848 }
849 }

Referenced by loadVMap().

◆ usesLiquids()

bool MMAP::TerrainBuilder::usesLiquids ( ) const
inline
88{ return !m_skipLiquid; }

References m_skipLiquid.

Referenced by MMAP::TileBuilder::buildMoveMapTile().

Member Data Documentation

◆ m_mapsPath

std::string MMAP::TerrainBuilder::m_mapsPath
private

Referenced by loadMap().

◆ m_skipLiquid

bool MMAP::TerrainBuilder::m_skipLiquid
private

Controls whether liquids are loaded.

Referenced by loadMap(), and usesLiquids().

◆ m_vmapsPath

std::string MMAP::TerrainBuilder::m_vmapsPath
private

Referenced by loadVMap().


The documentation for this class was generated from the following files: