TrinityCore
Loading...
Searching...
No Matches
TerrainBuilder.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 "TerrainBuilder.h"
19#include "Log.h"
20#include "MapDefines.h"
21#include "MapTree.h"
22#include "MMapDefines.h"
23#include "Memory.h"
24#include "ModelInstance.h"
25#include "StringFormat.h"
26#include "Util.h"
27#include "VMapManager.h"
28#include <unordered_map>
29
30namespace MMAP
31{
32 std::unique_ptr<VMAP::VMapManager> (*CreateVMapManager)(uint32 mapId);
33
34 TerrainBuilder::TerrainBuilder(boost::filesystem::path const& inputDirectory, bool skipLiquid) :
35 m_inputDirectory(inputDirectory),
36 m_skipLiquid (skipLiquid)
37 {
38 }
39
40 /**************************************************************************/
41 void TerrainBuilder::getLoopVars(Spot portion, int& loopStart, int& loopEnd, int& loopInc)
42 {
43 switch (portion)
44 {
45 case ENTIRE:
46 loopStart = 0;
47 loopEnd = V8_SIZE_SQ;
48 loopInc = 1;
49 break;
50 case TOP:
51 loopStart = 0;
52 loopEnd = V8_SIZE;
53 loopInc = 1;
54 break;
55 case LEFT:
56 loopStart = 0;
57 loopEnd = V8_SIZE_SQ - V8_SIZE + 1;
58 loopInc = V8_SIZE;
59 break;
60 case RIGHT:
61 loopStart = V8_SIZE - 1;
62 loopEnd = V8_SIZE_SQ;
63 loopInc = V8_SIZE;
64 break;
65 case BOTTOM:
66 loopStart = V8_SIZE_SQ - V8_SIZE;
67 loopEnd = V8_SIZE_SQ;
68 loopInc = 1;
69 break;
70 }
71 }
72
73 /**************************************************************************/
74 void TerrainBuilder::loadMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData, VMAP::VMapManager* vmapManager)
75 {
76 if (loadMap(mapID, tileX, tileY, meshData, vmapManager, ENTIRE))
77 {
78 loadMap(mapID, tileX, tileY+1, meshData, vmapManager, LEFT);
79 loadMap(mapID, tileX, tileY-1, meshData, vmapManager, RIGHT);
80 loadMap(mapID, tileX+1, tileY, meshData, vmapManager, TOP);
81 loadMap(mapID, tileX-1, tileY, meshData, vmapManager, BOTTOM);
82 }
83 }
84
85 /**************************************************************************/
86 bool TerrainBuilder::loadMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData, VMAP::VMapManager* vmapManager, Spot portion)
87 {
88 std::string mapFileName = Trinity::StringFormat("{}/maps/{:04}_{:02}_{:02}.map", m_inputDirectory.generic_string(), mapID, tileX, tileY);
89
90 auto mapFile = Trinity::make_unique_ptr_with_deleter<&::fclose>(fopen(mapFileName.c_str(), "rb"));
91 if (!mapFile)
92 {
93 int32 parentMapId = vmapManager->getParentMapId(mapID);
94 while (!mapFile && parentMapId != -1)
95 {
96 mapFileName = Trinity::StringFormat("{}/maps/{:04}_{:02}_{:02}.map", m_inputDirectory.generic_string(), parentMapId, tileX, tileY);
97 mapFile.reset(fopen(mapFileName.c_str(), "rb"));
98 parentMapId = vmapManager->getParentMapId(parentMapId);
99 }
100 }
101
102 if (!mapFile)
103 return false;
104
105 map_fileheader fheader;
106 if (fread(&fheader, sizeof(map_fileheader), 1, mapFile.get()) != 1 ||
107 fheader.versionMagic != MapVersionMagic)
108 {
109 TC_LOG_ERROR("maps.mmapgen", "{} is the wrong version, please extract new .map files", mapFileName);
110 return false;
111 }
112
113 map_heightHeader hheader;
114 fseek(mapFile.get(), fheader.heightMapOffset, SEEK_SET);
115
116 bool haveTerrain = false;
117 bool haveLiquid = false;
118 if (fread(&hheader, sizeof(map_heightHeader), 1, mapFile.get()) == 1)
119 {
120 haveTerrain = !hheader.flags.HasFlag(map_heightHeaderFlags::NoHeight);
121 haveLiquid = fheader.liquidMapOffset && !m_skipLiquid;
122 }
123
124 // no data in this map file
125 if (!haveTerrain && !haveLiquid)
126 return false;
127
128 // data used later
129 uint8 holes[16][16][8] = { };
130 uint16 liquid_entry[16][16] = { };
131 map_liquidHeaderTypeFlags liquid_flags[16][16] = { };
132 std::vector<int> ltriangles;
133 std::vector<int> ttriangles;
134
135 // terrain data
136 if (haveTerrain)
137 {
138 float heightMultiplier;
139 float V9[V9_SIZE_SQ], V8[V8_SIZE_SQ];
140 size_t expected = V9_SIZE_SQ + V8_SIZE_SQ;
141
143 {
144 uint8 v9[V9_SIZE_SQ];
145 uint8 v8[V8_SIZE_SQ];
146 size_t count = 0;
147 count += fread(v9, sizeof(uint8), V9_SIZE_SQ, mapFile.get());
148 count += fread(v8, sizeof(uint8), V8_SIZE_SQ, mapFile.get());
149 if (count != expected)
150 TC_LOG_ERROR("maps.mmapgen", "TerrainBuilder::loadMap: Failed to read {} height data expected {}, read {}", mapFileName, expected, count);
151
152 heightMultiplier = (hheader.gridMaxHeight - hheader.gridHeight) / 255;
153
154 for (int i = 0; i < V9_SIZE_SQ; ++i)
155 V9[i] = (float)v9[i]*heightMultiplier + hheader.gridHeight;
156
157 for (int i = 0; i < V8_SIZE_SQ; ++i)
158 V8[i] = (float)v8[i]*heightMultiplier + hheader.gridHeight;
159 }
161 {
162 uint16 v9[V9_SIZE_SQ];
163 uint16 v8[V8_SIZE_SQ];
164 size_t count = 0;
165 count += fread(v9, sizeof(uint16), V9_SIZE_SQ, mapFile.get());
166 count += fread(v8, sizeof(uint16), V8_SIZE_SQ, mapFile.get());
167 if (count != expected)
168 TC_LOG_ERROR("maps.mmapgen", "TerrainBuilder::loadMap: Failed to read {} height data expected {}, read {}", mapFileName, expected, count);
169
170 heightMultiplier = (hheader.gridMaxHeight - hheader.gridHeight) / 65535;
171
172 for (int i = 0; i < V9_SIZE_SQ; ++i)
173 V9[i] = (float)v9[i]*heightMultiplier + hheader.gridHeight;
174
175 for (int i = 0; i < V8_SIZE_SQ; ++i)
176 V8[i] = (float)v8[i]*heightMultiplier + hheader.gridHeight;
177 }
178 else
179 {
180 size_t count = 0;
181 count += fread(V9, sizeof(float), V9_SIZE_SQ, mapFile.get());
182 count += fread(V8, sizeof(float), V8_SIZE_SQ, mapFile.get());
183 if (count != expected)
184 TC_LOG_ERROR("maps.mmapgen", "TerrainBuilder::loadMap: Failed to read {} height data expected {}, read {}", mapFileName, expected, count);
185 }
186
187 // hole data
188 if (fheader.holesSize != 0)
189 {
190 memset(holes, 0, fheader.holesSize);
191 fseek(mapFile.get(), fheader.holesOffset, SEEK_SET);
192 if (fread(holes, fheader.holesSize, 1, mapFile.get()) != 1)
193 TC_LOG_ERROR("maps.mmapgen", "TerrainBuilder::loadMap: Failed to read {} holes data expected {}, read {}", mapFileName, 1, 0);
194 }
195
196 int count = meshData.solidVerts.size() / 3;
197 meshData.solidVerts.resize(meshData.solidVerts.size() + (V9_SIZE_SQ + V8_SIZE_SQ) * 3);
198 float* solidVerts = meshData.solidVerts.data() + count * 3;
199
200 float xoffset = (float(tileX)-32)*GRID_SIZE;
201 float yoffset = (float(tileY)-32)*GRID_SIZE;
202
203 for (int i = 0; i < V9_SIZE_SQ; ++i)
204 {
205 getHeightCoord(i, GRID_V9, xoffset, yoffset, solidVerts, V9);
206 solidVerts += 3;
207 }
208
209 for (int i = 0; i < V8_SIZE_SQ; ++i)
210 {
211 getHeightCoord(i, GRID_V8, xoffset, yoffset, solidVerts, V8);
212 solidVerts += 3;
213 }
214
215 int loopStart = 0, loopEnd = 0, loopInc = 0;
216 getLoopVars(portion, loopStart, loopEnd, loopInc);
217 for (int i = loopStart; i < loopEnd; i+=loopInc)
218 {
219 std::size_t trianglesOffset = ttriangles.size();
220 ttriangles.resize(ttriangles.size() + 12);
221 getHeightTriangle(i, TOP, ttriangles.data() + trianglesOffset + 0, count);
222 getHeightTriangle(i, RIGHT, ttriangles.data() + trianglesOffset + 3, count);
223 getHeightTriangle(i, LEFT, ttriangles.data() + trianglesOffset + 6, count);
224 getHeightTriangle(i, BOTTOM, ttriangles.data() + trianglesOffset + 9, count);
225 }
226 }
227
228 // liquid data
229 if (haveLiquid)
230 {
231 map_liquidHeader lheader;
232 fseek(mapFile.get(), fheader.liquidMapOffset, SEEK_SET);
233 if (fread(&lheader, sizeof(map_liquidHeader), 1, mapFile.get()) != 1)
234 TC_LOG_ERROR("maps.mmapgen", "TerrainBuilder::loadMap: Failed to read {} liquid header expected {}, read {}", mapFileName, 1, 0);
235
236 std::unique_ptr<float[]> liquid_map;
237
239 {
240 if (fread(liquid_entry, sizeof(liquid_entry), 1, mapFile.get()) != 1)
241 TC_LOG_ERROR("maps.mmapgen", "TerrainBuilder::loadMap: Failed to read {} liquid id expected {}, read {}", mapFileName, 1, 0);
242 if (fread(liquid_flags, sizeof(liquid_flags), 1, mapFile.get()) != 1)
243 TC_LOG_ERROR("maps.mmapgen", "TerrainBuilder::loadMap: Failed to read {} liquid flags expected {}, read {}", mapFileName, 1, 0);
244 }
245 else
246 {
247 std::fill_n(&liquid_entry[0][0], 16 * 16, lheader.liquidType);
248 std::fill_n(&liquid_flags[0][0], 16 * 16, lheader.liquidFlags);
249 }
250
252 {
253 uint32 toRead = lheader.width * lheader.height;
254 liquid_map = std::make_unique<float[]>(toRead);
255 if (fread(liquid_map.get(), sizeof(float), toRead, mapFile.get()) != toRead)
256 {
257 TC_LOG_ERROR("maps.mmapgen", "TerrainBuilder::loadMap: Failed to read {} liquid header expected {}, read {}", mapFileName, toRead, 0);
258 liquid_map = nullptr;
259 }
260 }
261
262 int count = meshData.liquidVerts.size() / 3;
263 meshData.liquidVerts.resize(meshData.liquidVerts.size() + V9_SIZE_SQ * 3);
264 float* liquidVerts = meshData.liquidVerts.data();
265 float xoffset = (float(tileX)-32)*GRID_SIZE;
266 float yoffset = (float(tileY)-32)*GRID_SIZE;
267
268 int row, col;
269
270 // generate coordinates
272 {
273 int j = 0;
274 for (int i = 0; i < V9_SIZE_SQ; ++i)
275 {
276 row = i / V9_SIZE;
277 col = i % V9_SIZE;
278
279 if (row < lheader.offsetY || row >= lheader.offsetY + lheader.height ||
280 col < lheader.offsetX || col >= lheader.offsetX + lheader.width)
281 {
282 // dummy vert using invalid height
283 liquidVerts[(count + i) * 3 + 0] = (yoffset + col * GRID_PART_SIZE) * -1;
284 liquidVerts[(count + i) * 3 + 1] = INVALID_MAP_LIQ_HEIGHT;
285 liquidVerts[(count + i) * 3 + 2] = (xoffset + row * GRID_PART_SIZE) * -1;
286 continue;
287 }
288
289 getLiquidCoord(i, j, xoffset, yoffset, &liquidVerts[(count + i) * 3], liquid_map.get());
290 j++;
291 }
292 }
293 else
294 {
295 for (int i = 0; i < V9_SIZE_SQ; ++i)
296 {
297 row = i / V9_SIZE;
298 col = i % V9_SIZE;
299 liquidVerts[(count + i) * 3 + 0] = (yoffset + col * GRID_PART_SIZE) * -1;
300 liquidVerts[(count + i) * 3 + 1] = lheader.liquidLevel;
301 liquidVerts[(count + i) * 3 + 2] = (xoffset + row * GRID_PART_SIZE) * -1;
302 }
303 }
304
305 int loopStart = 0, loopEnd = 0, loopInc = 0;
306 getLoopVars(portion, loopStart, loopEnd, loopInc);
307
308 // generate triangles
309 for (int i = loopStart; i < loopEnd; i += loopInc)
310 {
311 std::size_t trianglesOffset = ltriangles.size();
312 ltriangles.resize(ltriangles.size() + 6);
313 getHeightTriangle(i, TOP, ltriangles.data() + trianglesOffset + 0, count, true);
314 getHeightTriangle(i, BOTTOM, ltriangles.data() + trianglesOffset + 3, count, true);
315 }
316 }
317
318 // now that we have gathered the data, we can figure out which parts to keep:
319 // liquid above ground, ground above liquid
320 int loopStart = 0, loopEnd = 0, loopInc = 0, tTriCount = 4;
321 bool useTerrain, useLiquid;
322
323 float* lverts = meshData.liquidVerts.data();
324 int* ltris = ltriangles.data();
325
326 float* tverts = meshData.solidVerts.data();
327 int* ttris = ttriangles.data();
328
329 if ((ltriangles.size() + ttriangles.size()) == 0)
330 return false;
331
332 // make a copy of liquid vertices
333 // used to pad right-bottom frame due to lost vertex data at extraction
334 std::unique_ptr<float[]> lverts_copy;
335 if (!meshData.liquidVerts.empty())
336 {
337 lverts_copy = std::make_unique<float[]>(meshData.liquidVerts.size());
338 memcpy(lverts_copy.get(), lverts, sizeof(float)* meshData.liquidVerts.size());
339 }
340
341 getLoopVars(portion, loopStart, loopEnd, loopInc);
342 for (int i = loopStart; i < loopEnd; i+=loopInc)
343 {
344 for (int j = 0; j < 2; ++j)
345 {
346 // default is true, will change to false if needed
347 useTerrain = true;
348 useLiquid = true;
350 uint8 navLiquidType = NAV_AREA_EMPTY;
351
352 // if there is no liquid, don't use liquid
353 if (meshData.liquidVerts.empty() || ltriangles.empty())
354 useLiquid = false;
355 else
356 {
357 liquidType = getLiquidType(i, liquid_flags);
359 {
360 // players should not be here, so logically neither should creatures
361 useTerrain = false;
362 useLiquid = false;
363 }
365 navLiquidType = NAV_AREA_WATER;
367 navLiquidType = NAV_AREA_MAGMA_SLIME;
368 else
369 useLiquid = false;
370 }
371
372 // if there is no terrain, don't use terrain
373 if (ttriangles.empty())
374 useTerrain = false;
375
376 // while extracting ADT data we are losing right-bottom vertices
377 // this code adds fair approximation of lost data
378 if (useLiquid)
379 {
380 float quadHeight = 0;
381 uint32 validCount = 0;
382 for(uint32 idx = 0; idx < 3; idx++)
383 {
384 float h = lverts_copy[ltris[idx]*3 + 1];
386 {
387 quadHeight += h;
388 validCount++;
389 }
390 }
391
392 // update vertex height data
393 if (validCount > 0 && validCount < 3)
394 {
395 quadHeight /= validCount;
396 for(uint32 idx = 0; idx < 3; idx++)
397 {
398 float h = lverts[ltris[idx]*3 + 1];
400 lverts[ltris[idx]*3 + 1] = quadHeight;
401 }
402 }
403
404 // no valid vertexes - don't use this poly at all
405 if (validCount == 0)
406 useLiquid = false;
407 }
408
409 // if there is a hole here, don't use the terrain
410 if (useTerrain && fheader.holesSize != 0)
411 useTerrain = !isHole(i, holes);
412
413 // we use only one terrain kind per quad - pick higher one
414 if (useTerrain && useLiquid)
415 {
416 float minLLevel = INVALID_MAP_LIQ_HEIGHT_MAX;
417 float maxLLevel = INVALID_MAP_LIQ_HEIGHT;
418 for(uint32 x = 0; x < 3; x++)
419 {
420 float h = lverts[ltris[x]*3 + 1];
421 if (minLLevel > h)
422 minLLevel = h;
423
424 if (maxLLevel < h)
425 maxLLevel = h;
426 }
427
428 float maxTLevel = INVALID_MAP_LIQ_HEIGHT;
429 float minTLevel = INVALID_MAP_LIQ_HEIGHT_MAX;
430 for(uint32 x = 0; x < 6; x++)
431 {
432 float h = tverts[ttris[x]*3 + 1];
433 if (maxTLevel < h)
434 maxTLevel = h;
435
436 if (minTLevel > h)
437 minTLevel = h;
438 }
439
440 // terrain under the liquid?
441 if (minLLevel > maxTLevel)
442 useTerrain = false;
443
444 //liquid under the terrain?
445 if (minTLevel > maxLLevel)
446 useLiquid = false;
447 }
448
449 // store the result
450 if (useLiquid)
451 {
452 meshData.liquidTris.insert(meshData.liquidTris.end(), &ltris[0], &ltris[3]);
453 meshData.liquidType.push_back(navLiquidType);
454 }
455
456 if (useTerrain)
457 meshData.solidTris.insert(meshData.solidTris.end(), &ttris[0], &ttris[3 * tTriCount / 2]);
458
459 // advance to next set of triangles
460 ltris += 3;
461 ttris += 3*tTriCount/2;
462 }
463 }
464
465 return !meshData.solidTris.empty() || !meshData.liquidTris.empty();
466 }
467
468 /**************************************************************************/
469 inline void TerrainBuilder::getHeightCoord(int index, Grid grid, float xOffset, float yOffset, float* coord, float* v)
470 {
471 // wow coords: x, y, height
472 // coord is mirroed about the horizontal axes
473 switch (grid)
474 {
475 case GRID_V9:
476 coord[0] = (yOffset + (int)(index % V9_SIZE) * GRID_PART_SIZE) * -1.f;
477 coord[1] = v[index];
478 coord[2] = (xOffset + (int)(index / (V9_SIZE)) * GRID_PART_SIZE) * -1.f;
479 break;
480 case GRID_V8:
481 coord[0] = (yOffset + (int)(index % V8_SIZE) * GRID_PART_SIZE + GRID_PART_SIZE / 2.f) * -1.f;
482 coord[1] = v[index];
483 coord[2] = (xOffset + (int)(index / (V8_SIZE)) * GRID_PART_SIZE + GRID_PART_SIZE / 2.f) * -1.f;
484 break;
485 }
486 }
487
488 /**************************************************************************/
489 inline void TerrainBuilder::getHeightTriangle(int square, Spot triangle, int* indices, int offset, bool liquid/* = false*/)
490 {
491 int rowOffset = square/V8_SIZE;
492 if (!liquid)
493 switch (triangle)
494 {
495 case TOP:
496 indices[0] = V9_SIZE_SQ + square + offset; // 0-----1 .... 128
497 indices[1] = square + 1 + rowOffset + offset; // |\ T /|
498 indices[2] = square + rowOffset + offset; // | \ / |
499 break; // |L 0 R| .. 127
500 case LEFT: // | / \ |
501 indices[0] = square + V9_SIZE + rowOffset + offset; // |/ B \|
502 indices[1] = V9_SIZE_SQ + square + offset; // 129---130 ... 386
503 indices[2] = square + rowOffset + offset; // |\ /|
504 break; // | \ / |
505 case RIGHT: // | 128 | .. 255
506 indices[0] = V9_SIZE_SQ + square + offset; // | / \ |
507 indices[1] = square + V9_SIZE + 1 + rowOffset + offset; // |/ \|
508 indices[2] = square + 1 + rowOffset + offset; // 258---259 ... 515
509 break;
510 case BOTTOM:
511 indices[0] = square + V9_SIZE + rowOffset + offset;
512 indices[1] = square + V9_SIZE + 1 + rowOffset + offset;
513 indices[2] = V9_SIZE_SQ + square + offset;
514 break;
515 default: break;
516 }
517 else
518 switch (triangle)
519 { // 0-----1 .... 128
520 case TOP: // |\ |
521 indices[0] = square + V9_SIZE + 1 + rowOffset + offset; // | \ T |
522 indices[1] = square + 1 + rowOffset + offset; // | \ |
523 indices[2] = square + rowOffset + offset; // | B \ |
524 break; // | \|
525 case BOTTOM: // 129---130 ... 386
526 indices[0] = square + V9_SIZE + rowOffset + offset; // |\ |
527 indices[1] = square + V9_SIZE + 1 + rowOffset + offset; // | \ |
528 indices[2] = square + rowOffset + offset; // | \ |
529 break; // | \ |
530 default: break; // | \|
531 } // 258---259 ... 515
532
533 }
534
535 /**************************************************************************/
536 inline void TerrainBuilder::getLiquidCoord(int index, int index2, float xOffset, float yOffset, float* coord, float* v)
537 {
538 // wow coords: x, y, height
539 // coord is mirroed about the horizontal axes
540 coord[0] = (yOffset + (int)(index % V9_SIZE) * GRID_PART_SIZE) * -1.f;
541 coord[1] = v[index2];
542 coord[2] = (xOffset + (int)(index / (V9_SIZE)) * GRID_PART_SIZE) * -1.f;
543 }
544
545 /**************************************************************************/
546 inline bool TerrainBuilder::isHole(int square, uint8 const (&holes)[16][16][8])
547 {
548 int row = square / 128;
549 int col = square % 128;
550 int cellRow = row / 8; // 8 squares per cell
551 int cellCol = col / 8;
552 int holeRow = row % 8;
553 int holeCol = col % 8;
554
555 return (holes[cellRow][cellCol][holeRow] & (1 << holeCol)) != 0;
556 }
557
558 /**************************************************************************/
560 {
561 int row = square / 128;
562 int col = square % 128;
563 int cellRow = row / 8; // 8 squares per cell
564 int cellCol = col / 8;
565
566 return liquid_type[cellRow][cellCol];
567 }
568
569 /**************************************************************************/
570 bool TerrainBuilder::loadVMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData, VMAP::VMapManager* vmapManager)
571 {
572 VMAP::LoadResult result = vmapManager->loadMap((m_inputDirectory / "vmaps").string(), mapID, tileX, tileY);
573 if (result != VMAP::LoadResult::Success)
574 return false;
575
576 auto vmapTile = Trinity::make_unique_ptr_with_deleter(vmapManager, [=](VMAP::VMapManager* mgr)
577 {
578 mgr->unloadMap(mapID, tileX, tileY);
579 });
580
581 std::span<VMAP::ModelInstance const> models = vmapManager->getModelsOnMap(mapID);
582 if (models.empty())
583 return false;
584
585 bool retval = false;
586
587 for (VMAP::ModelInstance const& instance : models)
588 {
589 // model instances exist in tree even though there are instances of that model in this tile
590 VMAP::WorldModel const* worldModel = instance.getWorldModel();
591 if (!worldModel)
592 continue;
593
594 // now we have a model to add to the meshdata
595 retval = true;
596
597 G3D::Vector3 position = instance.iPos;
598 position.x -= 32 * GRID_SIZE;
599 position.y -= 32 * GRID_SIZE;
600 loadVMapModel(worldModel, position, instance.GetInvRot(), instance.iScale, meshData, vmapManager);
601 }
602
603 return retval;
604 }
605
606 void TerrainBuilder::loadVMapModel(VMAP::WorldModel const* worldModel, G3D::Vector3 const& position, G3D::Matrix3 const& rotation, float scale,
607 MeshData& meshData, VMAP::VMapManager* vmapManager)
608 {
609 std::vector<VMAP::GroupModel> const& groupModels = worldModel->getGroupModels();
610
611 // all M2s need to have triangle indices reversed
612 bool isM2 = worldModel->IsM2();
613
614 // transform data
615 for (std::vector<VMAP::GroupModel>::const_iterator it = groupModels.begin(); it != groupModels.end(); ++it)
616 {
617 // first handle collision mesh
618 int offset = meshData.solidVerts.size() / 3;
619 transformVertices(it->GetVertices(), meshData.solidVerts, scale, rotation, position);
620 copyIndices(it->GetTriangles(), meshData.solidTris, offset, isM2);
621
622 // now handle liquid data
623 VMAP::WmoLiquid const* liquid = it->GetLiquid();
624 if (liquid && liquid->GetFlagsStorage())
625 {
626 uint32 tilesX, tilesY;
627 G3D::Vector3 corner;
628 liquid->getPosInfo(tilesX, tilesY, corner);
629 uint32 vertsX = tilesX + 1;
630 uint32 vertsY = tilesY + 1;
631 uint8 const* flags = liquid->GetFlagsStorage();
632 float const* data = liquid->GetHeightStorage();
633 uint8 type = NAV_AREA_EMPTY;
634
635 // convert liquid type to NavTerrain
638 type = NAV_AREA_WATER;
641
642 // indexing is weird...
643 // after a lot of trial and error, this is what works:
644 // vertex = y*vertsX+x
645 // tile = x*tilesY+y
646 // flag = y*tilesY+x
647
648 uint32 liqOffset = meshData.liquidVerts.size() / 3;
649 meshData.liquidVerts.resize(meshData.liquidVerts.size() + vertsX * vertsY * 3);
650 float* liquidVerts = meshData.liquidVerts.data();
651 for (uint32 x = 0; x < vertsX; ++x)
652 {
653 for (uint32 y = 0; y < vertsY; ++y)
654 {
655 G3D::Vector3 vert = G3D::Vector3(corner.x + x * GRID_PART_SIZE, corner.y + y * GRID_PART_SIZE, data[y * vertsX + x]);
656 vert = vert * rotation * scale + position;
657 vert.x *= -1.f;
658 vert.y *= -1.f;
659 liquidVerts[(liqOffset + x * vertsY + y) * 3 + 0] = vert.y;
660 liquidVerts[(liqOffset + x * vertsY + y) * 3 + 1] = vert.z;
661 liquidVerts[(liqOffset + x * vertsY + y) * 3 + 2] = vert.x;
662 }
663 }
664
665 std::size_t liquidSquares = 0;
666 for (uint32 x = 0; x < tilesX; ++x)
667 {
668 for (uint32 y = 0; y < tilesY; ++y)
669 {
670 if ((flags[x + y * tilesX] & 0x0f) != 0x0f)
671 {
672 uint32 square = x * tilesY + y;
673 int idx1 = square + x;
674 int idx2 = square + 1 + x;
675 int idx3 = square + tilesY + 1 + 1 + x;
676 int idx4 = square + tilesY + 1 + x;
677
678 std::size_t liquidTriOffset = meshData.liquidTris.size();
679 meshData.liquidTris.resize(liquidTriOffset + 6);
680 int* liquidTris = meshData.liquidTris.data() + liquidTriOffset;
681
682 // top triangle
683 liquidTris[0] = idx2 + liqOffset;
684 liquidTris[1] = idx1 + liqOffset;
685 liquidTris[2] = idx3 + liqOffset;
686
687 // bottom triangle
688 liquidTris[3] = idx3 + liqOffset;
689 liquidTris[4] = idx1 + liqOffset;
690 liquidTris[5] = idx4 + liqOffset;
691
692 ++liquidSquares;
693 }
694 }
695 }
696
697 meshData.liquidType.resize(meshData.liquidType.size() + liquidSquares * 2, type);
698 }
699 }
700 }
701
702 /**************************************************************************/
703 void TerrainBuilder::transformVertices(std::vector<G3D::Vector3> const& source, std::vector<float>& dest, float scale, G3D::Matrix3 const& rotation, G3D::Vector3 const& position)
704 {
705 std::size_t offset = dest.size();
706 dest.resize(dest.size() + source.size() * 3);
707 float* d = dest.data();
708 for (std::size_t i = 0; i < source.size(); ++i)
709 {
710 // apply tranform, then mirror along the horizontal axes
711 G3D::Vector3 v(source[i] * rotation * scale + position);
712 v.x *= -1.f;
713 v.y *= -1.f;
714 d[offset + i * 3 + 0] = v.y;
715 d[offset + i * 3 + 1] = v.z;
716 d[offset + i * 3 + 2] = v.x;
717 }
718 }
719
720 /**************************************************************************/
721 void TerrainBuilder::copyIndices(std::vector<VMAP::MeshTriangle> const& source, std::vector<int>& dest, int offset, bool flip)
722 {
723 std::size_t destOffset = dest.size();
724 dest.resize(dest.size() + source.size() * 3);
725 int* d = dest.data();
726 if (flip)
727 {
728 for (VMAP::MeshTriangle const& triangle : source)
729 {
730 d[destOffset++] = triangle.idx2 + offset;
731 d[destOffset++] = triangle.idx1 + offset;
732 d[destOffset++] = triangle.idx0 + offset;
733 }
734 }
735 else
736 {
737 static_assert(sizeof(VMAP::MeshTriangle) == 3 * sizeof(uint32));
738 std::ranges::transform(reinterpret_cast<uint32 const*>(source.data()), reinterpret_cast<uint32 const*>(source.data() + source.size()),
739 dest.data() + destOffset, [&](int src) { return src + offset; });
740 }
741 }
742
743 /**************************************************************************/
744 void TerrainBuilder::copyIndices(std::vector<int> const& source, std::vector<int>& dest, int offset)
745 {
746 std::size_t destOffset = dest.size();
747 dest.resize(destOffset + source.size());
748 std::ranges::transform(source, dest.data() + destOffset, [&](int src) { return src + offset; });
749 }
750
751 /**************************************************************************/
752 void TerrainBuilder::cleanVertices(std::vector<float>& verts, std::vector<int>& tris)
753 {
754 std::unordered_map<int, int> vertMap;
755 vertMap.reserve(tris.size());
756
757 int* t = tris.data();
758 float* v = verts.data();
759
760 std::vector<float> cleanVerts;
761 cleanVerts.reserve(verts.size());
762 int count = 0;
763 // collect all the vertex indices from triangle
764 for (std::size_t i = 0; i < tris.size(); ++i)
765 {
766 auto [vertItr, isNew] = vertMap.try_emplace(t[i], count);
767 if (!isNew)
768 continue;
769
770 std::ptrdiff_t index = t[i];
771
772 cleanVerts.insert(cleanVerts.end(), &v[index * 3], &v[index * 3 + 3]);
773 count++;
774 }
775
776 verts = std::move(cleanVerts);
777
778 // update triangles to use new indices
779 for (std::size_t i = 0; i < tris.size(); ++i)
780 {
781 auto it = vertMap.find(t[i]);
782 if (it == vertMap.end())
783 continue;
784
785 t[i] = it->second;
786 }
787 }
788
789 /**************************************************************************/
790 void TerrainBuilder::loadOffMeshConnections(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData, std::vector<OffMeshData> const& offMeshConnections)
791 {
792 for (OffMeshData const& offMeshConnection : offMeshConnections)
793 {
794 if (mapID != offMeshConnection.MapId || tileX != offMeshConnection.TileX || tileY != offMeshConnection.TileY)
795 continue;
796
797 meshData.offMeshConnections.push_back(offMeshConnection.From[1]);
798 meshData.offMeshConnections.push_back(offMeshConnection.From[2]);
799 meshData.offMeshConnections.push_back(offMeshConnection.From[0]);
800
801 meshData.offMeshConnections.push_back(offMeshConnection.To[1]);
802 meshData.offMeshConnections.push_back(offMeshConnection.To[2]);
803 meshData.offMeshConnections.push_back(offMeshConnection.To[0]);
804
805 meshData.offMeshConnectionDirs.push_back(offMeshConnection.ConnectionFlags & OFFMESH_CONNECTION_FLAG_BIDIRECTIONAL);
806 meshData.offMeshConnectionRads.push_back(offMeshConnection.Radius); // agent size equivalent
807 // can be used same way as polygon flags
808 meshData.offMeshConnectionsAreas.push_back(offMeshConnection.AreaId);
809 meshData.offMeshConnectionsFlags.push_back(offMeshConnection.Flags);
810 }
811 }
812}
uint8_t uint8
Definition Define.h:156
int32_t int32
Definition Define.h:150
uint16_t uint16
Definition Define.h:155
uint32_t uint32
Definition Define.h:154
uint16 flags
#define TC_LOG_ERROR(filterType__, message__,...)
Definition Log.h:190
@ NAV_AREA_MAGMA_SLIME
Definition MMapDefines.h:64
@ NAV_AREA_EMPTY
Definition MMapDefines.h:58
@ NAV_AREA_WATER
Definition MMapDefines.h:63
@ OFFMESH_CONNECTION_FLAG_BIDIRECTIONAL
Definition MMapDefines.h:81
uint32 const MapVersionMagic
map_liquidHeaderTypeFlags
Definition MapDefines.h:97
constexpr bool HasFlag(T flag) const
Definition EnumFlag.h:106
Definition Grid.h:42
TerrainBuilder(boost::filesystem::path const &inputDirectory, bool skipLiquid)
void loadOffMeshConnections(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData, std::vector< OffMeshData > const &offMeshConnections)
void loadMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData, VMAP::VMapManager *vmapManager)
static void transformVertices(std::vector< G3D::Vector3 > const &source, std::vector< float > &dest, float scale, G3D::Matrix3 const &rotation, G3D::Vector3 const &position)
static bool isHole(int square, uint8 const (&holes)[16][16][8])
Determines if the specific position's triangles should be rendered.
static void copyIndices(std::vector< VMAP::MeshTriangle > const &source, std::vector< int > &dest, int offset, bool flip)
bool m_skipLiquid
Controls whether liquids are loaded.
boost::filesystem::path m_inputDirectory
static void getHeightTriangle(int square, Spot triangle, int *indices, int offset, bool liquid=false)
Get the triangle's vector indices for a specific position.
static void cleanVertices(std::vector< float > &verts, std::vector< int > &tris)
static void getLiquidCoord(int index, int index2, float xOffset, float yOffset, float *coord, float *v)
Get the liquid vector coordinate for a specific position.
static void getHeightCoord(int index, Grid grid, float xOffset, float yOffset, float *coord, float *v)
Get the vector coordinate for a specific position.
bool loadVMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData, VMAP::VMapManager *vmapManager)
static void getLoopVars(Spot portion, int &loopStart, int &loopEnd, int &loopInc)
Sets loop variables for selecting only certain parts of a map's terrain.
void loadVMapModel(VMAP::WorldModel const *worldModel, G3D::Vector3 const &position, G3D::Matrix3 const &rotation, float scale, MeshData &meshData, VMAP::VMapManager *vmapManager)
static map_liquidHeaderTypeFlags getLiquidType(int square, map_liquidHeaderTypeFlags const (&liquid_type)[16][16])
Get the liquid type for a specific position.
LoadResult loadMap(std::string const &basePath, uint32 mapId, uint32 x, uint32 y)
void unloadMap(uint32 mapId, uint32 x, uint32 y)
GetLiquidFlagsFn GetLiquidFlagsPtr
int32 getParentMapId(uint32 mapId) const
std::span< ModelInstance const > getModelsOnMap(uint32 mapId) const
void getPosInfo(uint32 &tilesX, uint32 &tilesY, G3D::Vector3 &corner) const
float * GetHeightStorage()
Definition WorldModel.h:61
uint32 GetType() const
Definition WorldModel.h:60
uint8 * GetFlagsStorage()
Definition WorldModel.h:62
std::vector< GroupModel > const & getGroupModels() const
Definition WorldModel.h:131
bool IsM2() const
Definition WorldModel.h:130
static const float GRID_SIZE
static const float INVALID_MAP_LIQ_HEIGHT_MAX
static const int V9_SIZE
static const float GRID_PART_SIZE
static const float INVALID_MAP_LIQ_HEIGHT
std::unique_ptr< VMAP::VMapManager >(* CreateVMapManager)(uint32 mapId)
static const int V8_SIZE
static const int V9_SIZE_SQ
static const int V8_SIZE_SQ
std::unique_ptr< T, Impl::stateful_unique_ptr_deleter< Ptr, Del > > make_unique_ptr_with_deleter(Ptr ptr, Del deleter)
Definition Memory.h:133
std::string StringFormat(FormatString< Args... > fmt, Args &&... args) noexcept
Default TC string format function.
std::vector< uint8 > liquidType
std::vector< float > liquidVerts
std::vector< unsigned short > offMeshConnectionsFlags
std::vector< float > solidVerts
std::vector< int > solidTris
std::vector< int > liquidTris
std::vector< float > offMeshConnections
std::vector< unsigned char > offMeshConnectionDirs
std::vector< float > offMeshConnectionRads
std::vector< unsigned char > offMeshConnectionsAreas
uint32 holesSize
Definition MapDefines.h:50
uint32 heightMapOffset
Definition MapDefines.h:45
uint32 holesOffset
Definition MapDefines.h:49
uint32 versionMagic
Definition MapDefines.h:41
uint32 liquidMapOffset
Definition MapDefines.h:47
EnumFlag< map_heightHeaderFlags > flags
Definition MapDefines.h:82
EnumFlag< map_liquidHeaderFlags > flags
Definition MapDefines.h:122
EnumFlag< map_liquidHeaderTypeFlags > liquidFlags
Definition MapDefines.h:123