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