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