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"
20#include "Duration.h"
21#include "IteratorPair.h"
22#include "MapTree.h"
23#include "Memory.h"
24#include "StringConvert.h"
25#include "StringFormat.h"
26#include "ThreadPool.h"
27#include "VMapDefinitions.h"
28#include <boost/filesystem/directory.hpp>
29#include <boost/filesystem/operations.hpp>
30#include <mutex>
31#include <set>
32
33template<> struct BoundsTrait<VMAP::ModelSpawn*>
34{
35 static void getBounds(VMAP::ModelSpawn const* obj, G3D::AABox& out) { out = obj->getBounds(); }
36 void operator()(VMAP::ModelSpawn const* obj, G3D::AABox& out) const { getBounds(obj, out); }
37};
38
39namespace VMAP
40{
41 static auto OpenFile(boost::filesystem::path const& p, char const* mode)
42 {
43 return Trinity::make_unique_ptr_with_deleter<&::fclose>(fopen(p.string().c_str(), mode));
44 }
45
46 G3D::Vector3 ModelPosition::transform(G3D::Vector3 const& pIn) const
47 {
48 G3D::Vector3 out = pIn * iScale;
49 out = iRotation * out;
50 return out;
51 }
52
53 //=================================================================
54
55 TileAssembler::TileAssembler(std::string const& srcDirName, std::string const& destDirName, uint32 threads)
56 : iSrcDir(srcDirName), iDestDir(destDirName), iThreads(threads)
57 {
58 }
59
61 {
62 boost::system::error_code ec;
63 Trinity::IteratorPair dirBin(boost::filesystem::directory_iterator(iSrcDir / "dir_bin", ec), {});
64 if (ec)
65 {
66 printf("Failed to open input %s/dir_bin: %s\n", iSrcDir.string().c_str(), ec.message().c_str());
67 return false;
68 }
69
70 if (!boost::filesystem::create_directories(iDestDir, ec))
71 {
72 boost::system::error_code existsErr;
73 if (!boost::filesystem::exists(iDestDir, existsErr))
74 {
75 printf("Failed to create output directory %s: %s\n", iDestDir.string().c_str(), ec.message().c_str());
76 return false;
77 }
78 // else already exists - this is fine, continue
79 }
80
81 // export Map data
82 Trinity::ThreadPool threadPool(iThreads);
83 bool aborted = false;
84 std::once_flag abortedFlag;
85 auto abortThreads = [&threadPool, &aborted, &abortedFlag]
86 {
87 std::call_once(abortedFlag, [&] { threadPool.Stop(); aborted = true; });
88 };
89
90 // Every worker thread gets its dedicated output container to avoid having to synchronize access
91 std::atomic<std::size_t> workerIndexGen;
92 std::vector<std::set<std::string>> spawnedModelFilesByThread(iThreads);
93
94 std::atomic<std::size_t> mapsToProcess;
95
96 for (boost::filesystem::directory_entry const& directoryEntry : dirBin)
97 {
98 if (!boost::filesystem::is_regular_file(directoryEntry))
99 continue;
100
101 ++mapsToProcess;
102 threadPool.PostWork([this, file = directoryEntry.path(), &abortThreads, &workerIndexGen, &spawnedModelFilesByThread, &mapsToProcess]
103 {
104 thread_local std::size_t workerIndex = workerIndexGen++;
105
106 auto dirf = OpenFile(file, "rb");
107 if (!dirf)
108 {
109 printf("Could not read dir_bin file!\n");
110 return abortThreads();
111 }
112
113 Optional<uint32> mapId = Trinity::StringTo<uint32>(file.filename().string());
114 if (!mapId)
115 {
116 printf("Invalid Map ID %s\n", file.filename().string().c_str());
117 return abortThreads();
118 }
119
120 printf("spawning Map %u\n", *mapId);
121
122 MapSpawns data;
123 data.MapId = *mapId;
124 if (!readMapSpawns(dirf.get(), &data))
125 return abortThreads();
126
127 if (!convertMap(data))
128 return abortThreads();
129
130 spawnedModelFilesByThread[workerIndex].merge(data.SpawnedModelFiles);
131 --mapsToProcess;
132 });
133 }
134
135 while (mapsToProcess && !aborted)
136 std::this_thread::sleep_for(1s);
137
138 if (aborted)
139 return false;
140
141 for (std::set<std::string>& modelsForThread : spawnedModelFilesByThread)
142 spawnedModelFiles.merge(modelsForThread);
143
144 // add an object models, listed in temp_gameobject_models file
146 // export objects
147 printf("\nConverting Model Files\n");
148 for (std::string const& spawnedModelFile : spawnedModelFiles)
149 {
150 threadPool.PostWork([&]
151 {
152 printf("Converting %s\n", spawnedModelFile.c_str());
153 if (!convertRawFile(spawnedModelFile))
154 {
155 printf("error converting %s\n", spawnedModelFile.c_str());
156 abortThreads();
157 }
158 });
159 }
160
161 threadPool.Join();
162
163 if (aborted)
164 return false;
165
166 return true;
167 }
168
170 {
171 float constexpr invTileSize = 1.0f / 533.33333f;
172
173 // build global map tree
174 std::vector<ModelSpawn*> mapSpawns;
175 mapSpawns.reserve(data.UniqueEntries.size());
176 printf("Calculating model bounds for map %u...\n", data.MapId);
177 for (auto& [spawnId, spawn] : data.UniqueEntries)
178 {
179 // M2 models don't have a bound set in WDT/ADT placement data, they're not used for LoS but are needed for pathfinding
180 if (!(spawn.flags & MOD_HAS_BOUND))
181 if (!calculateTransformedBound(spawn))
182 continue;
183
184 mapSpawns.push_back(&spawn);
185
186 std::map<uint32, std::set<uint32>>& tileEntries = (spawn.flags & MOD_PARENT_SPAWN) ? data.ParentTileEntries : data.TileEntries;
187
188 G3D::AABox const& bounds = spawn.iBound;
189 G3D::Vector2int16 low(int16(bounds.low().x * invTileSize), int16(bounds.low().y * invTileSize));
190 G3D::Vector2int16 high(int16(bounds.high().x * invTileSize), int16(bounds.high().y * invTileSize));
191 for (int x = low.x; x <= high.x; ++x)
192 for (int y = low.y; y <= high.y; ++y)
193 tileEntries[StaticMapTree::packTileID(x, y)].insert(spawnId);
194 }
195
196 printf("Creating map tree for map %u...\n", data.MapId);
197 BIH pTree;
198
199 try
200 {
201 pTree.build(mapSpawns, BoundsTrait<ModelSpawn*>());
202 }
203 catch (std::exception& e)
204 {
205 printf(R"(Exception "%s" when calling pTree.build)", e.what());
206 return false;
207 }
208
209 std::unordered_map<uint32, uint32> modelNodeIdx;
210 for (uint32 i = 0; i < mapSpawns.size(); ++i)
211 modelNodeIdx.try_emplace(mapSpawns[i]->ID, i);
212
213 boost::filesystem::path mapDestDir = iDestDir / Trinity::StringFormat("{:04}", data.MapId);
214
215 boost::system::error_code ec;
216 boost::filesystem::create_directory(mapDestDir, ec);
217
218 // write map tree file
219 boost::filesystem::path mapfilename = mapDestDir / Trinity::StringFormat("{:04}.vmtree", data.MapId);
220 auto mapfile = OpenFile(mapfilename, "wb");
221 if (!mapfile)
222 {
223 printf("Cannot open %s\n", mapfilename.string().c_str());
224 return false;
225 }
226
227 //general info
228 if (fwrite(VMAP_MAGIC, 1, 8, mapfile.get()) != 8)
229 return false;
230 // Nodes
231 if (fwrite("NODE", 4, 1, mapfile.get()) != 1)
232 return false;
233 if (!pTree.writeToFile(mapfile.get()))
234 return false;
235
236 mapfile = nullptr;
237
238 // <====
239
240 // write map tile files, similar to ADT files, only with extra BIH tree node info
241 for (auto const& [tileId, spawns] : data.TileEntries)
242 {
243 uint32 x, y;
244 StaticMapTree::unpackTileID(tileId, x, y);
245 auto tileFile = OpenFile(mapDestDir / Trinity::StringFormat("{:04}_{:02}_{:02}.vmtile", data.MapId, x, y), "wb");
246 auto tileSpawnIndicesFile = OpenFile(mapDestDir / Trinity::StringFormat("{:04}_{:02}_{:02}.vmtileidx", data.MapId, x, y), "wb");
247 if (tileFile && tileSpawnIndicesFile)
248 {
249 std::set<uint32> const& parentTileEntries = data.ParentTileEntries[tileId];
250
251 uint32 nSpawns = spawns.size() + parentTileEntries.size();
252
253 // file header
254 if (fwrite(VMAP_MAGIC, 1, 8, tileFile.get()) != 8)
255 return false;
256 if (fwrite(VMAP_MAGIC, 1, 8, tileSpawnIndicesFile.get()) != 8)
257 return false;
258
259 // write number of tile spawns
260 if (fwrite(&nSpawns, sizeof(uint32), 1, tileFile.get()) != 1)
261 return false;
262 if (fwrite(&nSpawns, sizeof(uint32), 1, tileSpawnIndicesFile.get()) != 1)
263 return false;
264
265 // write tile spawns
266 for (uint32 spawnId : spawns)
267 {
268 if (!ModelSpawn::writeToFile(tileFile.get(), data.UniqueEntries[spawnId]))
269 return false;
270 if (fwrite(&modelNodeIdx[spawnId], sizeof(uint32), 1, tileSpawnIndicesFile.get()) != 1)
271 return false;
272 }
273
274 for (uint32 spawnId : parentTileEntries)
275 {
276 if (!ModelSpawn::writeToFile(tileFile.get(), data.UniqueEntries[spawnId]))
277 return false;
278 if (fwrite(&modelNodeIdx[spawnId], sizeof(uint32), 1, tileSpawnIndicesFile.get()) != 1)
279 return false;
280 }
281 }
282 }
283
284 for (auto const& [tileId, spawns] : data.ParentTileEntries)
285 {
286 if (data.TileEntries.contains(tileId))
287 continue;
288
289 uint32 x, y;
290 StaticMapTree::unpackTileID(tileId, x, y);
291 auto tileSpawnIndicesFile = OpenFile(mapDestDir / Trinity::StringFormat("{:04}_{:02}_{:02}.vmtileidx", data.MapId, x, y), "wb");
292 if (tileSpawnIndicesFile)
293 {
294 uint32 nSpawns = spawns.size();
295
296 // file header
297 if (fwrite(VMAP_MAGIC, 1, 8, tileSpawnIndicesFile.get()) != 8)
298 return false;
299
300 // write number of tile spawns
301 if (fwrite(&nSpawns, sizeof(uint32), 1, tileSpawnIndicesFile.get()) != 1)
302 return false;
303
304 // write tile spawns
305 for (uint32 spawnId : spawns)
306 if (fwrite(&modelNodeIdx[spawnId], sizeof(uint32), 1, tileSpawnIndicesFile.get()) != 1)
307 return false;
308 }
309 }
310
311 return true;
312 }
313
315 {
316 while (!feof(dirf))
317 {
318 // read Flags, NameSet, UniqueId, Pos, Rot, Scale, Bound_lo, Bound_hi, name
319 ModelSpawn spawn;
320 if (!ModelSpawn::readFromFile(dirf, spawn))
321 {
322 if (feof(dirf))
323 break;
324
325 return false;
326 }
327
328 data->UniqueEntries.emplace(spawn.ID, spawn);
329 data->SpawnedModelFiles.insert(spawn.name);
330 }
331
332 return true;
333 }
334
336 {
337 boost::filesystem::path modelFilename = iSrcDir / spawn.name;
338
339 ModelPosition modelPosition;
340 modelPosition.iDir = spawn.iRot;
341 modelPosition.iScale = spawn.iScale;
342 modelPosition.init();
343
344 WorldModel_Raw raw_model;
345 if (!raw_model.Read(modelFilename))
346 return false;
347
348 uint32 groups = raw_model.groupsArray.size();
349 if (groups != 1)
350 printf("Warning: '%s' does not seem to be a M2 model!\n", modelFilename.string().c_str());
351
352 G3D::AABox rotated_bounds = G3D::AABox::empty();
353 for (int i = 0; i < 8; ++i)
354 rotated_bounds.merge(modelPosition.transform(raw_model.groupsArray[0].bounds.corner(i)));
355
356 spawn.iBound = rotated_bounds + spawn.iPos;
357 spawn.flags |= MOD_HAS_BOUND;
358 return true;
359 }
360
361#pragma pack(push, 1)
363 {
365 float pos_x;
366 float pos_y;
367 float pos_z;
368 short material;
369 };
370#pragma pack(pop)
371 //=================================================================
372 bool TileAssembler::convertRawFile(const std::string& pModelFilename) const
373 {
374 bool success = true;
375
376 WorldModel_Raw raw_model;
377 if (!raw_model.Read(iSrcDir / pModelFilename))
378 return false;
379
380 // write WorldModel
381 WorldModel model;
382 model.setFlags(raw_model.Flags);
383 model.setRootWmoID(raw_model.RootWMOID);
384 if (!raw_model.groupsArray.empty())
385 {
386 std::vector<GroupModel> groupsArray;
387
388 uint32 groups = raw_model.groupsArray.size();
389 for (uint32 g = 0; g < groups; ++g)
390 {
391 GroupModel_Raw& raw_group = raw_model.groupsArray[g];
392 groupsArray.push_back(GroupModel(raw_group.mogpflags, raw_group.GroupWMOID, raw_group.bounds));
393 groupsArray.back().setMeshData(std::move(raw_group.vertexArray), std::move(raw_group.triangles));
394 groupsArray.back().setLiquidData(raw_group.liquid.release());
395 }
396
397 model.setGroupModels(groupsArray);
398 }
399
400 success = model.writeFile((iDestDir / (pModelFilename + ".vmo")).string());
401 //std::cout << "readRawFile2: '" << pModelFilename << "' tris: " << nElements << " nodes: " << nNodes << std::endl;
402 return success;
403 }
404
406 {
407 auto model_list = OpenFile(iSrcDir / "temp_gameobject_models", "rb");
408 if (!model_list)
409 return;
410
411 char ident[8];
412 if (fread(ident, 1, 8, model_list.get()) != 8 || memcmp(ident, VMAP::RAW_VMAP_MAGIC, 8) != 0)
413 return;
414
415 auto model_list_copy = OpenFile(iDestDir / GAMEOBJECT_MODELS, "wb");
416 if (!model_list_copy)
417 return;
418
419 fwrite(VMAP::VMAP_MAGIC, 1, 8, model_list_copy.get());
420
421 uint32 name_length, displayId;
422 char buff[500];
423 while (true)
424 {
425 if (fread(&displayId, sizeof(uint32), 1, model_list.get()) != 1)
426 if (feof(model_list.get())) // EOF flag is only set after failed reading attempt
427 break;
428
429 if (fread(&name_length, sizeof(uint32), 1, model_list.get()) != 1
430 || name_length >= sizeof(buff)
431 || fread(&buff, sizeof(char), name_length, model_list.get()) != name_length)
432 {
433 printf("\nFile 'temp_gameobject_models' seems to be corrupted\n");
434 break;
435 }
436
437 std::string model_name(buff, name_length);
438
439 WorldModel_Raw raw_model;
440 if (!raw_model.Read(iSrcDir / model_name))
441 continue;
442
443 spawnedModelFiles.insert(model_name);
444 G3D::AABox bounds = G3D::AABox::empty();
445 for (GroupModel_Raw const& groupModel : raw_model.groupsArray)
446 for (G3D::Vector3 const& vertice : groupModel.vertexArray)
447 bounds.merge(vertice);
448
449 if (bounds.isEmpty())
450 {
451 printf("\nModel %s has empty bounding box\n", model_name.c_str());
452 continue;
453 }
454
455 if (!bounds.isFinite())
456 {
457 printf("\nModel %s has invalid bounding box\n", model_name.c_str());
458 continue;
459 }
460
461 fwrite(&displayId, sizeof(uint32), 1, model_list_copy.get());
462 fwrite(&name_length, sizeof(uint32), 1, model_list_copy.get());
463 fwrite(&buff, sizeof(char), name_length, model_list_copy.get());
464 fwrite(&bounds.low(), sizeof(G3D::Vector3), 1, model_list_copy.get());
465 fwrite(&bounds.high(), sizeof(G3D::Vector3), 1, model_list_copy.get());
466 }
467 }
468
469// temporary use defines to simplify read/check code (close file and return at fail)
470#define READ_OR_RETURN(V, S) if (fread((V), (S), 1, rf) != 1) do { \
471 printf("%s readfail, op = %s\n", __FUNCTION__, #V); return false; } while(false)
472#define CMP_OR_RETURN(V, S) if (strcmp((V), (S)) != 0) do { \
473 printf("%s cmpfail, %s!=%s\n", __FUNCTION__, V, S);return false; } while(false)
474
475 bool GroupModel_Raw::Read(FILE* rf)
476 {
477 char blockId[5];
478 blockId[4] = 0;
479 int blocksize;
480
483
484 G3D::Vector3 vec1, vec2;
485 READ_OR_RETURN(&vec1, sizeof(G3D::Vector3));
486
487 READ_OR_RETURN(&vec2, sizeof(G3D::Vector3));
488 bounds.set(vec1, vec2);
489
491
492 // will this ever be used? what is it good for anyway??
493 uint32 branches;
494 READ_OR_RETURN(&blockId, 4);
495 CMP_OR_RETURN(blockId, "GRP ");
496 READ_OR_RETURN(&blocksize, sizeof(int));
497 READ_OR_RETURN(&branches, sizeof(uint32));
498 for (uint32 b = 0; b < branches; ++b)
499 {
500 uint32 indexes;
501 // indexes for each branch (not used jet)
502 READ_OR_RETURN(&indexes, sizeof(uint32));
503 }
504
505 // ---- indexes
506 READ_OR_RETURN(&blockId, 4);
507 CMP_OR_RETURN(blockId, "INDX");
508 READ_OR_RETURN(&blocksize, sizeof(int));
509 uint32 nindexes;
510 READ_OR_RETURN(&nindexes, sizeof(uint32));
511 if (nindexes > 0)
512 {
513 std::unique_ptr<uint32[]> indexarray = std::make_unique<uint32[]>(nindexes);
514 READ_OR_RETURN(indexarray.get(), nindexes * sizeof(uint32));
515 triangles.reserve(nindexes / 3);
516 for (uint32 i = 0; i < nindexes; i += 3)
517 triangles.push_back({ .idx0 = indexarray[i], .idx1 = indexarray[i + 1], .idx2 = indexarray[i + 2] });
518 }
519
520 // ---- vectors
521 READ_OR_RETURN(&blockId, 4);
522 CMP_OR_RETURN(blockId, "VERT");
523 READ_OR_RETURN(&blocksize, sizeof(int));
524 uint32 nvectors;
525 READ_OR_RETURN(&nvectors, sizeof(uint32));
526
527 if (nvectors > 0)
528 {
529 std::unique_ptr<float[]> vectorarray = std::make_unique<float[]>(nvectors * 3);
530 READ_OR_RETURN(vectorarray.get(), nvectors * sizeof(float) * 3);
531 for (uint32 i = 0; i < nvectors; ++i)
532 vertexArray.push_back(G3D::Vector3(&vectorarray[3 * i]));
533 }
534 // ----- liquid
535 liquid = nullptr;
536 if (liquidflags & 3)
537 {
538 READ_OR_RETURN(&blockId, 4);
539 CMP_OR_RETURN(blockId, "LIQU");
540 READ_OR_RETURN(&blocksize, sizeof(int));
541 uint32 liquidType;
542 READ_OR_RETURN(&liquidType, sizeof(uint32));
543 if (liquidflags & 1)
544 {
545 WMOLiquidHeader hlq;
546 READ_OR_RETURN(&hlq, sizeof(WMOLiquidHeader));
547 liquid.reset(new WmoLiquid(hlq.xtiles, hlq.ytiles, G3D::Vector3(hlq.pos_x, hlq.pos_y, hlq.pos_z), liquidType));
548 uint32 size = hlq.xverts * hlq.yverts;
549 READ_OR_RETURN(liquid->GetHeightStorage(), size * sizeof(float));
550 size = hlq.xtiles * hlq.ytiles;
551 READ_OR_RETURN(liquid->GetFlagsStorage(), size);
552 }
553 else
554 {
555 liquid.reset(new WmoLiquid(0, 0, G3D::Vector3::zero(), liquidType));
556 liquid->GetHeightStorage()[0] = bounds.high().z;
557 }
558 }
559
560 return true;
561 }
562
563 bool WorldModel_Raw::Read(boost::filesystem::path const& path)
564 {
565 auto file = OpenFile(path, "rb");
566 if (!file)
567 {
568 printf("ERROR: Can't open raw model file: %s\n", path.string().c_str());
569 return false;
570 }
571
572 FILE* rf = file.get();
573
574 char ident[9];
575 ident[8] = '\0';
576
577 READ_OR_RETURN(&ident, 8);
579
580 // we have to read one int. This is needed during the export and we have to skip it here
581 uint32 tempNVectors;
582 READ_OR_RETURN(&tempNVectors, sizeof(tempNVectors));
583
584 uint32 groups;
585 READ_OR_RETURN(&groups, sizeof(uint32));
587 READ_OR_RETURN(&Flags, sizeof(Flags));
588
589 groupsArray.resize(groups);
590 bool succeed = true;
591 for (uint32 g = 0; g < groups && succeed; ++g)
592 succeed = groupsArray[g].Read(rf);
593
594 return succeed;
595 }
596
597 // drop of temporary use defines
598 #undef READ_OR_RETURN
599 #undef READ_OR_RETURN_WITH_DELETE
600 #undef CMP_OR_RETURN
601}
int16_t int16
Definition Define.h:151
uint32_t uint32
Definition Define.h:154
ModelList model_list
std::optional< T > Optional
Optional helper class to wrap optional values within.
Definition Optional.h:25
#define READ_OR_RETURN(V, S)
#define CMP_OR_RETURN(V, S)
bool writeToFile(FILE *wf) const
void build(PrimArray const &primitives, BoundsFunc const &getBounds, uint32 leafSize=3, bool printStats=false)
Utility class to enable range for loop syntax for multimap.equal_range uses.
decltype(auto) PostWork(T &&work)
Definition ThreadPool.h:35
G3D::Vector3 transform(const G3D::Vector3 &pIn) const
G3D::Matrix3 iRotation
static uint32 packTileID(uint32 tileX, uint32 tileY)
Definition MapTree.h:67
static void unpackTileID(uint32 ID, uint32 &tileX, uint32 &tileY)
Definition MapTree.h:68
boost::filesystem::path iSrcDir
TileAssembler(std::string const &srcDirName, std::string const &destDirName, uint32 threads)
bool calculateTransformedBound(ModelSpawn &spawn) const
boost::filesystem::path iDestDir
bool convertMap(MapSpawns &data) const
bool convertRawFile(const std::string &pModelFilename) const
std::set< std::string > spawnedModelFiles
static bool readMapSpawns(FILE *dirf, MapSpawns *data)
void setRootWmoID(uint32 id)
Definition WorldModel.h:125
void setFlags(ModelFlags flags)
Definition WorldModel.h:124
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)
std::string StringFormat(FormatString< Args... > fmt, Args &&... args) noexcept
Default TC string format function.
@ MOD_HAS_BOUND
@ MOD_PARENT_SPAWN
const char RAW_VMAP_MAGIC[]
const char VMAP_MAGIC[]
static auto OpenFile(boost::filesystem::path const &p, char const *mode)
const char GAMEOBJECT_MODELS[]
void operator()(VMAP::ModelSpawn const *obj, G3D::AABox &out) const
static void getBounds(VMAP::ModelSpawn const *obj, G3D::AABox &out)
std::unique_ptr< WmoLiquid > liquid
std::vector< G3D::Vector3 > vertexArray
std::vector< MeshTriangle > triangles
std::map< uint32, ModelSpawn > UniqueEntries
std::map< uint32, std::set< uint32 > > TileEntries
std::set< std::string > SpawnedModelFiles
std::map< uint32, std::set< uint32 > > ParentTileEntries
G3D::AABox const & getBounds() const
static bool readFromFile(FILE *rf, ModelSpawn &spawn)
std::string name
G3D::Vector3 iRot
static bool writeToFile(FILE *rw, ModelSpawn const &spawn)
bool Read(boost::filesystem::path const &path)
std::vector< GroupModel_Raw > groupsArray