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