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