47dfadd09473bf3ab44d086d96a1b25eb3b876b5
[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 }
61
62 TileAssembler::~TileAssembler()
63 {
64 }
65
66 bool TileAssembler::convertWorld2()
67 {
68 bool success = readMapSpawns();
69 if (!success)
70 return false;
71
72 float constexpr invTileSize = 1.0f / 533.33333f;
73
74 // export Map data
75 while (!mapData.empty())
76 {
77 MapSpawns data = std::move(mapData.front());
78 mapData.pop_front();
79
80 // build global map tree
81 std::vector<ModelSpawn*> mapSpawns;
82 mapSpawns.reserve(data.UniqueEntries.size());
83 printf("Calculating model bounds for map %u...\n", data.MapId);
84 for (auto entry = data.UniqueEntries.begin(); entry != data.UniqueEntries.end(); ++entry)
85 {
86 // 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
87 if (entry->second.flags & MOD_M2)
88 if (!calculateTransformedBound(entry->second))
89 continue;
90
91 mapSpawns.push_back(&entry->second);
92 spawnedModelFiles.insert(entry->second.name);
93
94 std::map<uint32, std::set<TileSpawn>>& tileEntries = (entry->second.flags & MOD_PARENT_SPAWN) ? data.ParentTileEntries : data.TileEntries;
95
96 G3D::AABox const& bounds = entry->second.iBound;
97 G3D::Vector2int16 low(int16(bounds.low().x * invTileSize), int16(bounds.low().y * invTileSize));
98 G3D::Vector2int16 high(int16(bounds.high().x * invTileSize), int16(bounds.high().y * invTileSize));
99 for (int x = low.x; x <= high.x; ++x)
100 for (int y = low.y; y <= high.y; ++y)
101 tileEntries[StaticMapTree::packTileID(x, y)].emplace(entry->second.ID, entry->second.flags);
102 }
103
104 printf("Creating map tree for map %u...\n", data.MapId);
105 BIH pTree;
106
107 try
108 {
109 pTree.build(mapSpawns, BoundsTrait<ModelSpawn*>::getBounds);
110 }
111 catch (std::exception& e)
112 {
113 printf("Exception ""%s"" when calling pTree.build", e.what());
114 return false;
115 }
116
117 // ===> possibly move this code to StaticMapTree class
118
119 // write map tree file
120 std::stringstream mapfilename;
121 mapfilename << iDestDir << '/' << std::setfill('0') << std::setw(4) << data.MapId << ".vmtree";
122 FILE* mapfile = fopen(mapfilename.str().c_str(), "wb");
123 if (!mapfile)
124 {
125 success = false;
126 printf("Cannot open %s\n", mapfilename.str().c_str());
127 break;
128 }
129
130 //general info
131 if (success && fwrite(VMAP_MAGIC, 1, 8, mapfile) != 8) success = false;
132 // Nodes
133 if (success && fwrite("NODE", 4, 1, mapfile) != 1) success = false;
134 if (success) success = pTree.writeToFile(mapfile);
135
136 // spawn id to index map
137 uint32 mapSpawnsSize = mapSpawns.size();
138 if (success && fwrite("SIDX", 4, 1, mapfile) != 1) success = false;
139 if (success && fwrite(&mapSpawnsSize, sizeof(uint32), 1, mapfile) != 1) success = false;
140 for (uint32 i = 0; i < mapSpawnsSize; ++i)
141 {
142 if (success && fwrite(&mapSpawns[i]->ID, sizeof(uint32), 1, mapfile) != 1) success = false;
143 if (success && fwrite(&i, sizeof(uint32), 1, mapfile) != 1) success = false;
144 }
145
146 fclose(mapfile);
147
148 // <====
149
150 // write map tile files, similar to ADT files, only with extra BIH tree node info
151 for (auto tileItr = data.TileEntries.begin(); tileItr != data.TileEntries.end(); ++tileItr)
152 {
153 uint32 x, y;
154 StaticMapTree::unpackTileID(tileItr->first, x, y);
155 std::string tileFileName = Trinity::StringFormat("%s/%04u_%02u_%02u.vmtile", iDestDir.c_str(), data.MapId, y, x);
156 if (FILE* tileFile = fopen(tileFileName.c_str(), "wb"))
157 {
158 std::set<TileSpawn> const& parentTileEntries = data.ParentTileEntries[tileItr->first];
159
160 uint32 nSpawns = tileItr->second.size() + parentTileEntries.size();
161
162 // file header
163 if (success && fwrite(VMAP_MAGIC, 1, 8, tileFile) != 8) success = false;
164 // write number of tile spawns
165 if (success && fwrite(&nSpawns, sizeof(uint32), 1, tileFile) != 1) success = false;
166 // write tile spawns
167 for (auto spawnItr = tileItr->second.begin(); spawnItr != tileItr->second.end() && success; ++spawnItr)
168 success = ModelSpawn::writeToFile(tileFile, data.UniqueEntries[spawnItr->Id]);
169
170 for (auto spawnItr = parentTileEntries.begin(); spawnItr != parentTileEntries.end() && success; ++spawnItr)
171 success = ModelSpawn::writeToFile(tileFile, data.UniqueEntries[spawnItr->Id]);
172
173 fclose(tileFile);
174 }
175 }
176 }
177
178 // add an object models, listed in temp_gameobject_models file
179 exportGameobjectModels();
180 // export objects
181 std::cout << "\nConverting Model Files" << std::endl;
182 for (std::set<std::string>::iterator mfile = spawnedModelFiles.begin(); mfile != spawnedModelFiles.end(); ++mfile)
183 {
184 std::cout << "Converting " << *mfile << std::endl;
185 if (!convertRawFile(*mfile))
186 {
187 std::cout << "error converting " << *mfile << std::endl;
188 success = false;
189 break;
190 }
191 }
192
193 return success;
194 }
195
196 bool TileAssembler::readMapSpawns()
197 {
198 std::string fname = iSrcDir + "/dir_bin";
199 FILE* dirf = fopen(fname.c_str(), "rb");
200 if (!dirf)
201 {
202 printf("Could not read dir_bin file!\n");
203 return false;
204 }
205 printf("Read coordinate mapping...\n");
206 uint32 mapID, check=0;
207 std::map<uint32, MapSpawns> data;
208 while (!feof(dirf))
209 {
210 check = 0;
211 // read mapID, Flags, NameSet, UniqueId, Pos, Rot, Scale, Bound_lo, Bound_hi, name
212 check += fread(&mapID, sizeof(uint32), 1, dirf);
213 if (check == 0) // EoF...
214 break;
215
216 ModelSpawn spawn;
217 if (!ModelSpawn::readFromFile(dirf, spawn))
218 break;
219
220 auto map_iter = data.emplace(std::piecewise_construct, std::forward_as_tuple(mapID), std::forward_as_tuple());
221 if (map_iter.second)
222 {
223 map_iter.first->second.MapId = mapID;
224 printf("spawning Map %u\n", mapID);
225 }
226
227 map_iter.first->second.UniqueEntries.emplace(spawn.ID, spawn);
228 }
229
230 mapData.resize(data.size());
231 auto dst = mapData.begin();
232 for (auto src = data.begin(); src != data.end(); ++src, ++dst)
233 *dst = std::move(src->second);
234
235 bool success = (ferror(dirf) == 0);
236 fclose(dirf);
237 return success;
238 }
239
240 bool TileAssembler::calculateTransformedBound(ModelSpawn &spawn)
241 {
242 std::string modelFilename(iSrcDir);
243 modelFilename.push_back('/');
244 modelFilename.append(spawn.name);
245
246 ModelPosition modelPosition;
247 modelPosition.iDir = spawn.iRot;
248 modelPosition.iScale = spawn.iScale;
249 modelPosition.init();
250
251 WorldModel_Raw raw_model;
252 if (!raw_model.Read(modelFilename.c_str()))
253 return false;
254
255 uint32 groups = raw_model.groupsArray.size();
256 if (groups != 1)
257 printf("Warning: '%s' does not seem to be a M2 model!\n", modelFilename.c_str());
258
259 AABox modelBound;
260
261 modelBound.merge(modelPosition.transform(raw_model.groupsArray[0].bounds.low()));
262 modelBound.merge(modelPosition.transform(raw_model.groupsArray[0].bounds.high()));
263
264 spawn.iBound = modelBound + spawn.iPos;
265 spawn.flags |= MOD_HAS_BOUND;
266 return true;
267 }
268
269 #pragma pack(push, 1)
270 struct WMOLiquidHeader
271 {
272 int xverts, yverts, xtiles, ytiles;
273 float pos_x;
274 float pos_y;
275 float pos_z;
276 short material;
277 };
278 #pragma pack(pop)
279 //=================================================================
280 bool TileAssembler::convertRawFile(const std::string& pModelFilename)
281 {
282 bool success = true;
283 std::string filename = iSrcDir;
284 if (filename.length() >0)
285 filename.push_back('/');
286 filename.append(pModelFilename);
287
288 WorldModel_Raw raw_model;
289 if (!raw_model.Read(filename.c_str()))
290 return false;
291
292 // write WorldModel
293 WorldModel model;
294 model.setRootWmoID(raw_model.RootWMOID);
295 if (!raw_model.groupsArray.empty())
296 {
297 std::vector<GroupModel> groupsArray;
298
299 uint32 groups = raw_model.groupsArray.size();
300 for (uint32 g = 0; g < groups; ++g)
301 {
302 GroupModel_Raw& raw_group = raw_model.groupsArray[g];
303 groupsArray.push_back(GroupModel(raw_group.mogpflags, raw_group.GroupWMOID, raw_group.bounds ));
304 groupsArray.back().setMeshData(raw_group.vertexArray, raw_group.triangles);
305 groupsArray.back().setLiquidData(raw_group.liquid);
306 }
307
308 model.setGroupModels(groupsArray);
309 }
310
311 success = model.writeFile(iDestDir + "/" + pModelFilename + ".vmo");
312 //std::cout << "readRawFile2: '" << pModelFilename << "' tris: " << nElements << " nodes: " << nNodes << std::endl;
313 return success;
314 }
315
316 void TileAssembler::exportGameobjectModels()
317 {
318 FILE* model_list = fopen((iSrcDir + "/" + "temp_gameobject_models").c_str(), "rb");
319 if (!model_list)
320 return;
321
322 char ident[8];
323 if (fread(ident, 1, 8, model_list) != 8 || memcmp(ident, VMAP::RAW_VMAP_MAGIC, 8) != 0)
324 {
325 fclose(model_list);
326 return;
327 }
328
329 FILE* model_list_copy = fopen((iDestDir + "/" + GAMEOBJECT_MODELS).c_str(), "wb");
330 if (!model_list_copy)
331 {
332 fclose(model_list);
333 return;
334 }
335
336 fwrite(VMAP::VMAP_MAGIC, 1, 8, model_list_copy);
337
338 uint32 name_length, displayId;
339 uint8 isWmo;
340 char buff[500];
341 while (true)
342 {
343 if (fread(&displayId, sizeof(uint32), 1, model_list) != 1)
344 if (feof(model_list)) // EOF flag is only set after failed reading attempt
345 break;
346
347 if (fread(&isWmo, sizeof(uint8), 1, model_list) != 1
348 || fread(&name_length, sizeof(uint32), 1, model_list) != 1
349 || name_length >= sizeof(buff)
350 || fread(&buff, sizeof(char), name_length, model_list) != name_length)
351 {
352 std::cout << "\nFile 'temp_gameobject_models' seems to be corrupted" << std::endl;
353 break;
354 }
355
356 std::string model_name(buff, name_length);
357
358 WorldModel_Raw raw_model;
359 if (!raw_model.Read((iSrcDir + "/" + model_name).c_str()) )
360 continue;
361
362 spawnedModelFiles.insert(model_name);
363 AABox bounds;
364 bool boundEmpty = true;
365 for (uint32 g = 0; g < raw_model.groupsArray.size(); ++g)
366 {
367 std::vector<Vector3>& vertices = raw_model.groupsArray[g].vertexArray;
368
369 uint32 nvectors = vertices.size();
370 for (uint32 i = 0; i < nvectors; ++i)
371 {
372 Vector3& v = vertices[i];
373 if (boundEmpty)
374 bounds = AABox(v, v), boundEmpty = false;
375 else
376 bounds.merge(v);
377 }
378 }
379
380 if (bounds.isEmpty())
381 {
382 std::cout << "\nModel " << std::string(buff, name_length) << " has empty bounding box" << std::endl;
383 continue;
384 }
385
386 if (!bounds.isFinite())
387 {
388 std::cout << "\nModel " << std::string(buff, name_length) << " has invalid bounding box" << std::endl;
389 continue;
390 }
391
392 fwrite(&displayId, sizeof(uint32), 1, model_list_copy);
393 fwrite(&isWmo, sizeof(uint8), 1, model_list_copy);
394 fwrite(&name_length, sizeof(uint32), 1, model_list_copy);
395 fwrite(&buff, sizeof(char), name_length, model_list_copy);
396 fwrite(&bounds.low(), sizeof(Vector3), 1, model_list_copy);
397 fwrite(&bounds.high(), sizeof(Vector3), 1, model_list_copy);
398 }
399
400 fclose(model_list);
401 fclose(model_list_copy);
402 }
403
404 // temporary use defines to simplify read/check code (close file and return at fail)
405 #define READ_OR_RETURN(V, S) if (fread((V), (S), 1, rf) != 1) { \
406 fclose(rf); printf("readfail, op = %i\n", readOperation); return(false); }
407 #define READ_OR_RETURN_WITH_DELETE(V, S) if (fread((V), (S), 1, rf) != 1) { \
408 fclose(rf); printf("readfail, op = %i\n", readOperation); delete[] V; return(false); };
409 #define CMP_OR_RETURN(V, S) if (strcmp((V), (S)) != 0) { \
410 fclose(rf); printf("cmpfail, %s!=%s\n", V, S);return(false); }
411
412 bool GroupModel_Raw::Read(FILE* rf)
413 {
414 char blockId[5];
415 blockId[4] = 0;
416 int blocksize;
417 int readOperation = 0;
418
419 READ_OR_RETURN(&mogpflags, sizeof(uint32));
420 READ_OR_RETURN(&GroupWMOID, sizeof(uint32));
421
422
423 Vector3 vec1, vec2;
424 READ_OR_RETURN(&vec1, sizeof(Vector3));
425
426 READ_OR_RETURN(&vec2, sizeof(Vector3));
427 bounds.set(vec1, vec2);
428
429 READ_OR_RETURN(&liquidflags, sizeof(uint32));
430
431 // will this ever be used? what is it good for anyway??
432 uint32 branches;
433 READ_OR_RETURN(&blockId, 4);
434 CMP_OR_RETURN(blockId, "GRP ");
435 READ_OR_RETURN(&blocksize, sizeof(int));
436 READ_OR_RETURN(&branches, sizeof(uint32));
437 for (uint32 b=0; b<branches; ++b)
438 {
439 uint32 indexes;
440 // indexes for each branch (not used jet)
441 READ_OR_RETURN(&indexes, sizeof(uint32));
442 }
443
444 // ---- indexes
445 READ_OR_RETURN(&blockId, 4);
446 CMP_OR_RETURN(blockId, "INDX");
447 READ_OR_RETURN(&blocksize, sizeof(int));
448 uint32 nindexes;
449 READ_OR_RETURN(&nindexes, sizeof(uint32));
450 if (nindexes >0)
451 {
452 uint16 *indexarray = new uint16[nindexes];
453 READ_OR_RETURN_WITH_DELETE(indexarray, nindexes*sizeof(uint16));
454 triangles.reserve(nindexes / 3);
455 for (uint32 i=0; i<nindexes; i+=3)
456 triangles.push_back(MeshTriangle(indexarray[i], indexarray[i+1], indexarray[i+2]));
457
458 delete[] indexarray;
459 }
460
461 // ---- vectors
462 READ_OR_RETURN(&blockId, 4);
463 CMP_OR_RETURN(blockId, "VERT");
464 READ_OR_RETURN(&blocksize, sizeof(int));
465 uint32 nvectors;
466 READ_OR_RETURN(&nvectors, sizeof(uint32));
467
468 if (nvectors >0)
469 {
470 float *vectorarray = new float[nvectors*3];
471 READ_OR_RETURN_WITH_DELETE(vectorarray, nvectors*sizeof(float)*3);
472 for (uint32 i=0; i<nvectors; ++i)
473 vertexArray.push_back( Vector3(vectorarray + 3*i) );
474
475 delete[] vectorarray;
476 }
477 // ----- liquid
478 liquid = nullptr;
479 if (liquidflags & 3)
480 {
481 READ_OR_RETURN(&blockId, 4);
482 CMP_OR_RETURN(blockId, "LIQU");
483 READ_OR_RETURN(&blocksize, sizeof(int));
484 uint32 liquidType;
485 READ_OR_RETURN(&liquidType, sizeof(uint32));
486 if (liquidflags & 1)
487 {
488 WMOLiquidHeader hlq;
489 READ_OR_RETURN(&hlq, sizeof(WMOLiquidHeader));
490 liquid = new WmoLiquid(hlq.xtiles, hlq.ytiles, Vector3(hlq.pos_x, hlq.pos_y, hlq.pos_z), liquidType);
491 uint32 size = hlq.xverts * hlq.yverts;
492 READ_OR_RETURN(liquid->GetHeightStorage(), size * sizeof(float));
493 size = hlq.xtiles * hlq.ytiles;
494 READ_OR_RETURN(liquid->GetFlagsStorage(), size);
495 }
496 else
497 {
498 liquid = new WmoLiquid(0, 0, Vector3::zero(), liquidType);
499 liquid->GetHeightStorage()[0] = bounds.high().z;
500 }
501 }
502
503 return true;
504 }
505
506 GroupModel_Raw::~GroupModel_Raw()
507 {
508 delete liquid;
509 }
510
511 bool WorldModel_Raw::Read(const char * path)
512 {
513 FILE* rf = fopen(path, "rb");
514 if (!rf)
515 {
516 printf("ERROR: Can't open raw model file: %s\n", path);
517 return false;
518 }
519
520 char ident[9];
521 ident[8] = '\0';
522 int readOperation = 0;
523
524 READ_OR_RETURN(&ident, 8);
525 CMP_OR_RETURN(ident, RAW_VMAP_MAGIC);
526
527 // we have to read one int. This is needed during the export and we have to skip it here
528 uint32 tempNVectors;
529 READ_OR_RETURN(&tempNVectors, sizeof(tempNVectors));
530
531 uint32 groups;
532 READ_OR_RETURN(&groups, sizeof(uint32));
533 READ_OR_RETURN(&RootWMOID, sizeof(uint32));
534
535 groupsArray.resize(groups);
536 bool succeed = true;
537 for (uint32 g = 0; g < groups && succeed; ++g)
538 succeed = groupsArray[g].Read(rf);
539
540 if (succeed) /// rf will be freed inside Read if the function had any errors.
541 fclose(rf);
542 return succeed;
543 }
544
545 // drop of temporary use defines
546 #undef READ_OR_RETURN
547 #undef CMP_OR_RETURN
548 }