b0bee33b4b24b670afaabae49f7e85c7317f296b
[trinitycore] / src / common / Collision / Maps / TileAssembler.cpp
1 /*
2 * Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/>
3 * Copyright (C) 2005-2010 MaNGOS <http://getmangos.com/>
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "TileAssembler.h"
20 #include "MapTree.h"
21 #include "BoundingIntervalHierarchy.h"
22 #include "VMapDefinitions.h"
23
24 #include <set>
25 #include <iomanip>
26 #include <sstream>
27 #include <boost/filesystem.hpp>
28
29 using G3D::Vector3;
30 using G3D::AABox;
31 using G3D::inf;
32 using std::pair;
33
34 template<> struct BoundsTrait<VMAP::ModelSpawn*>
35 {
36 static void getBounds(const VMAP::ModelSpawn* const &obj, G3D::AABox& out) { out = obj->getBounds(); }
37 };
38
39 namespace VMAP
40 {
41 bool readChunk(FILE* rf, char *dest, const char *compare, uint32 len)
42 {
43 if (fread(dest, sizeof(char), len, rf) != len) return false;
44 return memcmp(dest, compare, len) == 0;
45 }
46
47 Vector3 ModelPosition::transform(const Vector3& pIn) const
48 {
49 Vector3 out = pIn * iScale;
50 out = iRotation * out;
51 return(out);
52 }
53
54 //=================================================================
55
56 TileAssembler::TileAssembler(const std::string& pSrcDirName, const std::string& pDestDirName)
57 : iDestDir(pDestDirName), iSrcDir(pSrcDirName)
58 {
59 boost::filesystem::create_directory(iDestDir);
60 //init();
61 }
62
63 TileAssembler::~TileAssembler()
64 {
65 //delete iCoordModelMapping;
66 }
67
68 bool TileAssembler::convertWorld2()
69 {
70 bool success = readMapSpawns();
71 if (!success)
72 return false;
73
74 // export Map data
75 for (MapData::iterator map_iter = mapData.begin(); map_iter != mapData.end() && success; ++map_iter)
76 {
77 // build global map tree
78 std::vector<ModelSpawn*> mapSpawns;
79 UniqueEntryMap::iterator entry;
80 printf("Calculating model bounds for map %u...\n", map_iter->first);
81 for (entry = map_iter->second->UniqueEntries.begin(); entry != map_iter->second->UniqueEntries.end(); ++entry)
82 {
83 // M2 models don't have a bound set in WDT/ADT placement data, i still think they're not used for LoS at all on retail
84 if (entry->second.flags & MOD_M2)
85 {
86 if (!calculateTransformedBound(entry->second))
87 continue;
88 }
89 else if (entry->second.flags & MOD_WORLDSPAWN) // WMO maps and terrain maps use different origin, so we need to adapt :/
90 {
91 /// @todo remove extractor hack and uncomment below line:
92 //entry->second.iPos += Vector3(533.33333f*32, 533.33333f*32, 0.f);
93 entry->second.iBound = entry->second.iBound + Vector3(533.33333f*32, 533.33333f*32, 0.f);
94 }
95 mapSpawns.push_back(&entry->second);
96 spawnedModelFiles.insert(entry->second.name);
97 }
98
99 printf("Creating map tree for map %u...\n", map_iter->first);
100 BIH pTree;
101
102 try
103 {
104 pTree.build(mapSpawns, BoundsTrait<ModelSpawn*>::getBounds);
105 }
106 catch (std::exception& e)
107 {
108 printf("Exception ""%s"" when calling pTree.build", e.what());
109 return false;
110 }
111
112 // ===> possibly move this code to StaticMapTree class
113
114 // write map tree file
115 std::stringstream mapfilename;
116 mapfilename << iDestDir << '/' << std::setfill('0') << std::setw(4) << map_iter->first << ".vmtree";
117 FILE* mapfile = fopen(mapfilename.str().c_str(), "wb");
118 if (!mapfile)
119 {
120 success = false;
121 printf("Cannot open %s\n", mapfilename.str().c_str());
122 break;
123 }
124
125 //general info
126 if (success && fwrite(VMAP_MAGIC, 1, 8, mapfile) != 8) success = false;
127 uint32 globalTileID = StaticMapTree::packTileID(65, 65);
128 pair<TileMap::iterator, TileMap::iterator> globalRange = map_iter->second->TileEntries.equal_range(globalTileID);
129 char isTiled = globalRange.first == globalRange.second; // only maps without terrain (tiles) have global WMO
130 if (success && fwrite(&isTiled, sizeof(char), 1, mapfile) != 1) success = false;
131 // Nodes
132 if (success && fwrite("NODE", 4, 1, mapfile) != 1) success = false;
133 if (success) success = pTree.writeToFile(mapfile);
134 // global map spawns (WDT), if any (most instances)
135 if (success && fwrite("GOBJ", 4, 1, mapfile) != 1) success = false;
136
137 for (TileMap::iterator glob=globalRange.first; glob != globalRange.second && success; ++glob)
138 {
139 success = ModelSpawn::writeToFile(mapfile, map_iter->second->UniqueEntries[glob->second.Id]);
140 }
141
142 // spawn id to index map
143 if (success && fwrite("SIDX", 4, 1, mapfile) != 1) success = false;
144 uint32 mapSpawnsSize = mapSpawns.size();
145 if (success && fwrite(&mapSpawnsSize, sizeof(uint32), 1, mapfile) != 1) success = false;
146 for (uint32 i = 0; i < mapSpawnsSize; ++i)
147 {
148 if (success && fwrite(&mapSpawns[i]->ID, sizeof(uint32), 1, mapfile) != 1) success = false;
149 if (success && fwrite(&i, sizeof(uint32), 1, mapfile) != 1) success = false;
150 }
151
152 fclose(mapfile);
153
154 // <====
155
156 // write map tile files, similar to ADT files, only with extra BSP tree node info
157 TileMap &tileEntries = map_iter->second->TileEntries;
158 TileMap::iterator tile;
159 for (tile = tileEntries.begin(); tile != tileEntries.end(); ++tile)
160 {
161 if (tile->second.Flags & MOD_WORLDSPAWN) // WDT spawn, saved as tile 65/65 currently...
162 continue;
163 if (tile->second.Flags & MOD_PARENT_SPAWN) // tile belongs to parent map
164 continue;
165 uint32 nSpawns = tileEntries.count(tile->first);
166 std::stringstream tilefilename;
167 tilefilename.fill('0');
168 tilefilename << iDestDir << '/' << std::setw(4) << map_iter->first << '_';
169 uint32 x, y;
170 StaticMapTree::unpackTileID(tile->first, x, y);
171 tilefilename << std::setw(2) << x << '_' << std::setw(2) << y << ".vmtile";
172 if (FILE* tilefile = fopen(tilefilename.str().c_str(), "wb"))
173 {
174 // file header
175 if (success && fwrite(VMAP_MAGIC, 1, 8, tilefile) != 8) success = false;
176 // write number of tile spawns
177 if (success && fwrite(&nSpawns, sizeof(uint32), 1, tilefile) != 1) success = false;
178 // write tile spawns
179 for (uint32 s=0; s<nSpawns; ++s)
180 {
181 if (s)
182 ++tile;
183 const ModelSpawn &spawn2 = map_iter->second->UniqueEntries[tile->second.Id];
184 success = success && ModelSpawn::writeToFile(tilefile, spawn2);
185 }
186 fclose(tilefile);
187 }
188 }
189 }
190
191 // add an object models, listed in temp_gameobject_models file
192 exportGameobjectModels();
193 // export objects
194 std::cout << "\nConverting Model Files" << std::endl;
195 for (std::set<std::string>::iterator mfile = spawnedModelFiles.begin(); mfile != spawnedModelFiles.end(); ++mfile)
196 {
197 std::cout << "Converting " << *mfile << std::endl;
198 if (!convertRawFile(*mfile))
199 {
200 std::cout << "error converting " << *mfile << std::endl;
201 success = false;
202 break;
203 }
204 }
205
206 //cleanup:
207 for (MapData::iterator map_iter = mapData.begin(); map_iter != mapData.end(); ++map_iter)
208 {
209 delete map_iter->second;
210 }
211 return success;
212 }
213
214 bool TileAssembler::readMapSpawns()
215 {
216 std::string fname = iSrcDir + "/dir_bin";
217 FILE* dirf = fopen(fname.c_str(), "rb");
218 if (!dirf)
219 {
220 printf("Could not read dir_bin file!\n");
221 return false;
222 }
223 printf("Read coordinate mapping...\n");
224 uint32 mapID, tileX, tileY, check=0;
225 G3D::Vector3 v1, v2;
226 ModelSpawn spawn;
227 while (!feof(dirf))
228 {
229 check = 0;
230 // read mapID, tileX, tileY, Flags, NameSet, UniqueId, Pos, Rot, Scale, Bound_lo, Bound_hi, name
231 check += fread(&mapID, sizeof(uint32), 1, dirf);
232 if (check == 0) // EoF...
233 break;
234 check += fread(&tileX, sizeof(uint32), 1, dirf);
235 check += fread(&tileY, sizeof(uint32), 1, dirf);
236 if (!ModelSpawn::readFromFile(dirf, spawn))
237 break;
238
239 MapSpawns *current;
240 MapData::iterator map_iter = mapData.find(mapID);
241 if (map_iter == mapData.end())
242 {
243 printf("spawning Map %u\n", mapID);
244 mapData[mapID] = current = new MapSpawns();
245 }
246 else
247 current = map_iter->second;
248
249 current->UniqueEntries.emplace(spawn.ID, spawn);
250 current->TileEntries.insert(pair<uint32, TileSpawn>(StaticMapTree::packTileID(tileX, tileY), TileSpawn{ spawn.ID, spawn.flags }));
251 }
252 bool success = (ferror(dirf) == 0);
253 fclose(dirf);
254 return success;
255 }
256
257 bool TileAssembler::calculateTransformedBound(ModelSpawn &spawn)
258 {
259 std::string modelFilename(iSrcDir);
260 modelFilename.push_back('/');
261 modelFilename.append(spawn.name);
262
263 ModelPosition modelPosition;
264 modelPosition.iDir = spawn.iRot;
265 modelPosition.iScale = spawn.iScale;
266 modelPosition.init();
267
268 WorldModel_Raw raw_model;
269 if (!raw_model.Read(modelFilename.c_str()))
270 return false;
271
272 uint32 groups = raw_model.groupsArray.size();
273 if (groups != 1)
274 printf("Warning: '%s' does not seem to be a M2 model!\n", modelFilename.c_str());
275
276 AABox modelBound;
277 bool boundEmpty=true;
278
279 for (uint32 g=0; g<groups; ++g) // should be only one for M2 files...
280 {
281 std::vector<Vector3>& vertices = raw_model.groupsArray[g].vertexArray;
282
283 if (vertices.empty())
284 {
285 std::cout << "error: model '" << spawn.name << "' has no geometry!" << std::endl;
286 continue;
287 }
288
289 uint32 nvectors = vertices.size();
290 for (uint32 i = 0; i < nvectors; ++i)
291 {
292 Vector3 v = modelPosition.transform(vertices[i]);
293
294 if (boundEmpty)
295 modelBound = AABox(v, v), boundEmpty=false;
296 else
297 modelBound.merge(v);
298 }
299 }
300 spawn.iBound = modelBound + spawn.iPos;
301 spawn.flags |= MOD_HAS_BOUND;
302 return true;
303 }
304
305 #pragma pack(push, 1)
306 struct WMOLiquidHeader
307 {
308 int xverts, yverts, xtiles, ytiles;
309 float pos_x;
310 float pos_y;
311 float pos_z;
312 short material;
313 };
314 #pragma pack(pop)
315 //=================================================================
316 bool TileAssembler::convertRawFile(const std::string& pModelFilename)
317 {
318 bool success = true;
319 std::string filename = iSrcDir;
320 if (filename.length() >0)
321 filename.push_back('/');
322 filename.append(pModelFilename);
323
324 WorldModel_Raw raw_model;
325 if (!raw_model.Read(filename.c_str()))
326 return false;
327
328 // write WorldModel
329 WorldModel model;
330 model.setRootWmoID(raw_model.RootWMOID);
331 if (!raw_model.groupsArray.empty())
332 {
333 std::vector<GroupModel> groupsArray;
334
335 uint32 groups = raw_model.groupsArray.size();
336 for (uint32 g = 0; g < groups; ++g)
337 {
338 GroupModel_Raw& raw_group = raw_model.groupsArray[g];
339 groupsArray.push_back(GroupModel(raw_group.mogpflags, raw_group.GroupWMOID, raw_group.bounds ));
340 groupsArray.back().setMeshData(raw_group.vertexArray, raw_group.triangles);
341 groupsArray.back().setLiquidData(raw_group.liquid);
342 }
343
344 model.setGroupModels(groupsArray);
345 }
346
347 success = model.writeFile(iDestDir + "/" + pModelFilename + ".vmo");
348 //std::cout << "readRawFile2: '" << pModelFilename << "' tris: " << nElements << " nodes: " << nNodes << std::endl;
349 return success;
350 }
351
352 void TileAssembler::exportGameobjectModels()
353 {
354 FILE* model_list = fopen((iSrcDir + "/" + "temp_gameobject_models").c_str(), "rb");
355 if (!model_list)
356 return;
357
358 char ident[8];
359 if (fread(ident, 1, 8, model_list) != 8 || memcmp(ident, VMAP::RAW_VMAP_MAGIC, 8) != 0)
360 {
361 fclose(model_list);
362 return;
363 }
364
365 FILE* model_list_copy = fopen((iDestDir + "/" + GAMEOBJECT_MODELS).c_str(), "wb");
366 if (!model_list_copy)
367 {
368 fclose(model_list);
369 return;
370 }
371
372 fwrite(VMAP::VMAP_MAGIC, 1, 8, model_list_copy);
373
374 uint32 name_length, displayId;
375 uint8 isWmo;
376 char buff[500];
377 while (true)
378 {
379 if (fread(&displayId, sizeof(uint32), 1, model_list) != 1)
380 if (feof(model_list)) // EOF flag is only set after failed reading attempt
381 break;
382
383 if (fread(&isWmo, sizeof(uint8), 1, model_list) != 1
384 || fread(&name_length, sizeof(uint32), 1, model_list) != 1
385 || name_length >= sizeof(buff)
386 || fread(&buff, sizeof(char), name_length, model_list) != name_length)
387 {
388 std::cout << "\nFile 'temp_gameobject_models' seems to be corrupted" << std::endl;
389 break;
390 }
391
392 std::string model_name(buff, name_length);
393
394 WorldModel_Raw raw_model;
395 if (!raw_model.Read((iSrcDir + "/" + model_name).c_str()) )
396 continue;
397
398 spawnedModelFiles.insert(model_name);
399 AABox bounds;
400 bool boundEmpty = true;
401 for (uint32 g = 0; g < raw_model.groupsArray.size(); ++g)
402 {
403 std::vector<Vector3>& vertices = raw_model.groupsArray[g].vertexArray;
404
405 uint32 nvectors = vertices.size();
406 for (uint32 i = 0; i < nvectors; ++i)
407 {
408 Vector3& v = vertices[i];
409 if (boundEmpty)
410 bounds = AABox(v, v), boundEmpty = false;
411 else
412 bounds.merge(v);
413 }
414 }
415
416 if (bounds.isEmpty())
417 {
418 std::cout << "\nModel " << std::string(buff, name_length) << " has empty bounding box" << std::endl;
419 continue;
420 }
421
422 if (!bounds.isFinite())
423 {
424 std::cout << "\nModel " << std::string(buff, name_length) << " has invalid bounding box" << std::endl;
425 continue;
426 }
427
428 fwrite(&displayId, sizeof(uint32), 1, model_list_copy);
429 fwrite(&isWmo, sizeof(uint8), 1, model_list_copy);
430 fwrite(&name_length, sizeof(uint32), 1, model_list_copy);
431 fwrite(&buff, sizeof(char), name_length, model_list_copy);
432 fwrite(&bounds.low(), sizeof(Vector3), 1, model_list_copy);
433 fwrite(&bounds.high(), sizeof(Vector3), 1, model_list_copy);
434 }
435
436 fclose(model_list);
437 fclose(model_list_copy);
438 }
439
440 // temporary use defines to simplify read/check code (close file and return at fail)
441 #define READ_OR_RETURN(V, S) if (fread((V), (S), 1, rf) != 1) { \
442 fclose(rf); printf("readfail, op = %i\n", readOperation); return(false); }
443 #define READ_OR_RETURN_WITH_DELETE(V, S) if (fread((V), (S), 1, rf) != 1) { \
444 fclose(rf); printf("readfail, op = %i\n", readOperation); delete[] V; return(false); };
445 #define CMP_OR_RETURN(V, S) if (strcmp((V), (S)) != 0) { \
446 fclose(rf); printf("cmpfail, %s!=%s\n", V, S);return(false); }
447
448 bool GroupModel_Raw::Read(FILE* rf)
449 {
450 char blockId[5];
451 blockId[4] = 0;
452 int blocksize;
453 int readOperation = 0;
454
455 READ_OR_RETURN(&mogpflags, sizeof(uint32));
456 READ_OR_RETURN(&GroupWMOID, sizeof(uint32));
457
458
459 Vector3 vec1, vec2;
460 READ_OR_RETURN(&vec1, sizeof(Vector3));
461
462 READ_OR_RETURN(&vec2, sizeof(Vector3));
463 bounds.set(vec1, vec2);
464
465 READ_OR_RETURN(&liquidflags, sizeof(uint32));
466
467 // will this ever be used? what is it good for anyway??
468 uint32 branches;
469 READ_OR_RETURN(&blockId, 4);
470 CMP_OR_RETURN(blockId, "GRP ");
471 READ_OR_RETURN(&blocksize, sizeof(int));
472 READ_OR_RETURN(&branches, sizeof(uint32));
473 for (uint32 b=0; b<branches; ++b)
474 {
475 uint32 indexes;
476 // indexes for each branch (not used jet)
477 READ_OR_RETURN(&indexes, sizeof(uint32));
478 }
479
480 // ---- indexes
481 READ_OR_RETURN(&blockId, 4);
482 CMP_OR_RETURN(blockId, "INDX");
483 READ_OR_RETURN(&blocksize, sizeof(int));
484 uint32 nindexes;
485 READ_OR_RETURN(&nindexes, sizeof(uint32));
486 if (nindexes >0)
487 {
488 uint16 *indexarray = new uint16[nindexes];
489 READ_OR_RETURN_WITH_DELETE(indexarray, nindexes*sizeof(uint16));
490 triangles.reserve(nindexes / 3);
491 for (uint32 i=0; i<nindexes; i+=3)
492 triangles.push_back(MeshTriangle(indexarray[i], indexarray[i+1], indexarray[i+2]));
493
494 delete[] indexarray;
495 }
496
497 // ---- vectors
498 READ_OR_RETURN(&blockId, 4);
499 CMP_OR_RETURN(blockId, "VERT");
500 READ_OR_RETURN(&blocksize, sizeof(int));
501 uint32 nvectors;
502 READ_OR_RETURN(&nvectors, sizeof(uint32));
503
504 if (nvectors >0)
505 {
506 float *vectorarray = new float[nvectors*3];
507 READ_OR_RETURN_WITH_DELETE(vectorarray, nvectors*sizeof(float)*3);
508 for (uint32 i=0; i<nvectors; ++i)
509 vertexArray.push_back( Vector3(vectorarray + 3*i) );
510
511 delete[] vectorarray;
512 }
513 // ----- liquid
514 liquid = nullptr;
515 if (liquidflags & 3)
516 {
517 READ_OR_RETURN(&blockId, 4);
518 CMP_OR_RETURN(blockId, "LIQU");
519 READ_OR_RETURN(&blocksize, sizeof(int));
520 uint32 liquidType;
521 READ_OR_RETURN(&liquidType, sizeof(uint32));
522 if (liquidflags & 1)
523 {
524 WMOLiquidHeader hlq;
525 READ_OR_RETURN(&hlq, sizeof(WMOLiquidHeader));
526 liquid = new WmoLiquid(hlq.xtiles, hlq.ytiles, Vector3(hlq.pos_x, hlq.pos_y, hlq.pos_z), liquidType);
527 uint32 size = hlq.xverts * hlq.yverts;
528 READ_OR_RETURN(liquid->GetHeightStorage(), size * sizeof(float));
529 size = hlq.xtiles * hlq.ytiles;
530 READ_OR_RETURN(liquid->GetFlagsStorage(), size);
531 }
532 else
533 {
534 liquid = new WmoLiquid(0, 0, Vector3::zero(), liquidType);
535 liquid->GetHeightStorage()[0] = bounds.high().z;
536 }
537 }
538
539 return true;
540 }
541
542 GroupModel_Raw::~GroupModel_Raw()
543 {
544 delete liquid;
545 }
546
547 bool WorldModel_Raw::Read(const char * path)
548 {
549 FILE* rf = fopen(path, "rb");
550 if (!rf)
551 {
552 printf("ERROR: Can't open raw model file: %s\n", path);
553 return false;
554 }
555
556 char ident[9];
557 ident[8] = '\0';
558 int readOperation = 0;
559
560 READ_OR_RETURN(&ident, 8);
561 CMP_OR_RETURN(ident, RAW_VMAP_MAGIC);
562
563 // we have to read one int. This is needed during the export and we have to skip it here
564 uint32 tempNVectors;
565 READ_OR_RETURN(&tempNVectors, sizeof(tempNVectors));
566
567 uint32 groups;
568 READ_OR_RETURN(&groups, sizeof(uint32));
569 READ_OR_RETURN(&RootWMOID, sizeof(uint32));
570
571 groupsArray.resize(groups);
572 bool succeed = true;
573 for (uint32 g = 0; g < groups && succeed; ++g)
574 succeed = groupsArray[g].Read(rf);
575
576 if (succeed) /// rf will be freed inside Read if the function had any errors.
577 fclose(rf);
578 return succeed;
579 }
580
581 // drop of temporary use defines
582 #undef READ_OR_RETURN
583 #undef CMP_OR_RETURN
584 }