aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/tools/CMakeLists.txt1
-rw-r--r--src/tools/mesh_extractor/ADT.h26
-rw-r--r--src/tools/mesh_extractor/CMakeLists.txt62
-rw-r--r--src/tools/mesh_extractor/Cache.h44
-rw-r--r--src/tools/mesh_extractor/Chunk.cpp31
-rw-r--r--src/tools/mesh_extractor/Chunk.h20
-rw-r--r--src/tools/mesh_extractor/ChunkedData.cpp63
-rw-r--r--src/tools/mesh_extractor/ChunkedData.h20
-rw-r--r--src/tools/mesh_extractor/Constants.h33
-rw-r--r--src/tools/mesh_extractor/ContinentBuilder.cpp11
-rw-r--r--src/tools/mesh_extractor/ContinentBuilder.h15
-rw-r--r--src/tools/mesh_extractor/DoodadHandler.cpp99
-rw-r--r--src/tools/mesh_extractor/DoodadHandler.h51
-rw-r--r--src/tools/mesh_extractor/Geometry.cpp102
-rw-r--r--src/tools/mesh_extractor/Geometry.h21
-rw-r--r--src/tools/mesh_extractor/LiquidHandler.cpp1
-rw-r--r--src/tools/mesh_extractor/LiquidHandler.h17
-rw-r--r--src/tools/mesh_extractor/MPQ.cpp117
-rw-r--r--src/tools/mesh_extractor/MPQ.h88
-rw-r--r--src/tools/mesh_extractor/MPQManager.cpp58
-rw-r--r--src/tools/mesh_extractor/MPQManager.h24
-rw-r--r--src/tools/mesh_extractor/MapChunk.cpp77
-rw-r--r--src/tools/mesh_extractor/MapChunk.h25
-rw-r--r--src/tools/mesh_extractor/MeshExtractor.cpp34
-rw-r--r--src/tools/mesh_extractor/Model.cpp49
-rw-r--r--src/tools/mesh_extractor/Model.h21
-rw-r--r--src/tools/mesh_extractor/ObjectDataHandler.cpp21
-rw-r--r--src/tools/mesh_extractor/ObjectDataHandler.h15
-rw-r--r--src/tools/mesh_extractor/TileBuilder.cpp37
-rw-r--r--src/tools/mesh_extractor/TileBuilder.h22
-rw-r--r--src/tools/mesh_extractor/Utils.cpp163
-rw-r--r--src/tools/mesh_extractor/Utils.h497
-rw-r--r--src/tools/mesh_extractor/WDT.cpp55
-rw-r--r--src/tools/mesh_extractor/WDT.h27
-rw-r--r--src/tools/mesh_extractor/WorldModelGroup.cpp122
-rw-r--r--src/tools/mesh_extractor/WorldModelGroup.h33
-rw-r--r--src/tools/mesh_extractor/WorldModelHandler.cpp190
-rw-r--r--src/tools/mesh_extractor/WorldModelHandler.h42
-rw-r--r--src/tools/mesh_extractor/WorldModelRoot.cpp74
-rw-r--r--src/tools/mesh_extractor/WorldModelRoot.h26
-rw-r--r--src/tools/mesh_extractor/readme6
41 files changed, 2440 insertions, 0 deletions
diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt
index 2d378966aff..bbff6f85751 100644
--- a/src/tools/CMakeLists.txt
+++ b/src/tools/CMakeLists.txt
@@ -12,3 +12,4 @@ add_subdirectory(map_extractor)
add_subdirectory(vmap4_assembler)
add_subdirectory(vmap4_extractor)
add_subdirectory(mmaps_generator)
+add_subdirectory(mesh_extractor)
diff --git a/src/tools/mesh_extractor/ADT.h b/src/tools/mesh_extractor/ADT.h
new file mode 100644
index 00000000000..18b811a4e4c
--- /dev/null
+++ b/src/tools/mesh_extractor/ADT.h
@@ -0,0 +1,26 @@
+#ifndef ADT_H
+#define ADT_H
+#include "ChunkedData.h"
+#include "MapChunk.h"
+#include "DoodadHandler.h"
+#include "WorldModelHandler.h"
+
+class ADT
+{
+public:
+ ADT();
+ ~ADT() { delete[] MapChunks; delete ObjectData; delete Data; }
+
+ ChunkedData* ObjectData;
+ ChunkedData* Data;
+ // This here is not a pointer, is an array of objects ( made this way to allow the dynamic allocation )
+ MapChunk* MapChunks;
+ MHDR Header;
+ // Can we dispose of this?
+ bool HasObjectData;
+
+ DoodadHandler* _DoodadHandler;
+ WorldModelHandler* _WorldModelHandler;
+ LiquidHandler* _LiquidHandler;
+};
+#endif \ No newline at end of file
diff --git a/src/tools/mesh_extractor/CMakeLists.txt b/src/tools/mesh_extractor/CMakeLists.txt
new file mode 100644
index 00000000000..3f5b5526dd4
--- /dev/null
+++ b/src/tools/mesh_extractor/CMakeLists.txt
@@ -0,0 +1,62 @@
+# Copyright (C) 2005-2009 MaNGOS project <http://getmangos.com/>
+# Copyright (C) 2008-2012 TrinityCore <http://www.trinitycore.org/>
+#
+# This file is free software; as a special exception the author gives
+# unlimited permission to copy and/or distribute it, with or without
+# modifications, as long as this notice is preserved.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
+# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+file(GLOB_RECURSE sources *.cpp *.h)
+
+if( UNIX )
+ include_directories (
+ ${CMAKE_SOURCE_DIR}/src/server/shared
+ ${CMAKE_SOURCE_DIR}/dep/recastnavigation/Recast
+ ${CMAKE_SOURCE_DIR}/dep/recastnavigation/Detour
+ ${CMAKE_SOURCE_DIR}/dep/libmpq
+ ${CMAKE_SOURCE_DIR}/dep/g3dlite/include
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ )
+elseif( WIN32 )
+ include_directories (
+ ${CMAKE_SOURCE_DIR}/src/server/shared
+ ${CMAKE_SOURCE_DIR}/src/server/shared/Database
+ ${CMAKE_SOURCE_DIR}/src/server/shared/Database/Implementation
+ ${CMAKE_SOURCE_DIR}/src/server/shared/Threading
+ ${CMAKE_SOURCE_DIR}/src/server/shared/Logging
+ ${CMAKE_SOURCE_DIR}/src/server/shared/Utilities
+ ${CMAKE_SOURCE_DIR}/src/server/shared/Dynamic
+ ${CMAKE_SOURCE_DIR}/src/server/shared/Dynamic/LinkedReference
+ ${CMAKE_SOURCE_DIR}/dep/recastnavigation/Recast
+ ${CMAKE_SOURCE_DIR}/dep/recastnavigation/Detour
+ ${CMAKE_SOURCE_DIR}/dep/libmpq
+ ${CMAKE_SOURCE_DIR}/dep/libmpq/win
+ ${CMAKE_SOURCE_DIR}/dep/g3dlite/include
+ ${ACE_INCLUDE_DIR}
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ )
+endif()
+
+add_executable(MeshExtractor
+ ${sources}
+)
+
+target_link_libraries(MeshExtractor
+ mpq
+ Recast
+ Detour
+ ${BZIP2_LIBRARIES}
+ ${ZLIB_LIBRARIES}
+ ${ACE_LIBRARY}
+)
+
+add_dependencies(MeshExtractor mpq Recast Detour)
+
+if( UNIX )
+ install(TARGETS MeshExtractor DESTINATION bin)
+elseif( WIN32 )
+ install(TARGETS MeshExtractor DESTINATION "${CMAKE_INSTALL_PREFIX}")
+endif() \ No newline at end of file
diff --git a/src/tools/mesh_extractor/Cache.h b/src/tools/mesh_extractor/Cache.h
new file mode 100644
index 00000000000..186a7870af1
--- /dev/null
+++ b/src/tools/mesh_extractor/Cache.h
@@ -0,0 +1,44 @@
+#ifndef CACHE_H
+#define CACHE_H
+#include <string>
+#include "Common.h"
+
+class WorldModelRoot;
+class Model;
+
+template<class T>
+class GenericCache
+{
+public:
+ GenericCache() {}
+ const int32 FlushLimit = 1000;
+
+ void Insert(std::string key, T* val)
+ {
+ if (_items.size() > FlushLimit)
+ Clear();
+ _items.insert(key, val);
+ }
+
+ T* Get(std::string key)
+ {
+ UNORDERED_MAP<std::string, T*>::iterator itr = _items.find(key);
+ if (itr != _items.end())
+ return itr->second;
+ return NULL;
+ }
+
+ void Clear() { _items.clear(); }
+private:
+ UNORDERED_MAP<std::string, T*> _items;
+};
+
+class CacheClass
+{
+ CacheClass() {}
+ GenericCache<Model> ModelCache;
+ GenericCache<WorldModelRoot> WorldModelCache;
+};
+
+extern CacheClass* Cache;
+#endif \ No newline at end of file
diff --git a/src/tools/mesh_extractor/Chunk.cpp b/src/tools/mesh_extractor/Chunk.cpp
new file mode 100644
index 00000000000..b41fa7d07d4
--- /dev/null
+++ b/src/tools/mesh_extractor/Chunk.cpp
@@ -0,0 +1,31 @@
+#include "Chunk.h"
+#include "Utils.h"
+
+int32 Chunk::FindSubChunkOffset(std::string name)
+{
+ // Reverse the name
+ name = std::string(name.rbegin(), name.rend());
+ if (name.size() != 4)
+ return -1;
+
+ FILE* stream = GetStream();
+ uint32 matched = 0;
+ while (ftell(stream) < Utils::Size(stream))
+ {
+ char b;
+ fread(&b, sizeof(char), 1, stream);
+ if (b == name[matched])
+ ++matched;
+ else
+ matched = 0;
+ if (matched == 4)
+ return ftell(stream) - 4;
+ }
+ return -1;
+}
+
+FILE* Chunk::GetStream()
+{
+ fseek(Stream, Offset, SEEK_SET);
+ return Stream;
+}
diff --git a/src/tools/mesh_extractor/Chunk.h b/src/tools/mesh_extractor/Chunk.h
new file mode 100644
index 00000000000..2eea36f69b6
--- /dev/null
+++ b/src/tools/mesh_extractor/Chunk.h
@@ -0,0 +1,20 @@
+#ifndef CHUNK_H
+#define CHUNK_H
+#include "Common.h"
+#include <string>
+class ChunkedData;
+
+class Chunk
+{
+public:
+ Chunk(const char* name, uint32 length, uint32 offset, FILE* stream) : Name(name), Length(length), Offset(offset), Stream(stream) {}
+
+ int32 FindSubChunkOffset(std::string name);
+ FILE* GetStream();
+ std::string Name;
+ uint32 Length;
+ uint32 Offset;
+ FILE* Stream;
+};
+
+#endif \ No newline at end of file
diff --git a/src/tools/mesh_extractor/ChunkedData.cpp b/src/tools/mesh_extractor/ChunkedData.cpp
new file mode 100644
index 00000000000..470b8b94f39
--- /dev/null
+++ b/src/tools/mesh_extractor/ChunkedData.cpp
@@ -0,0 +1,63 @@
+#include "ChunkedData.h"
+#include "MPQManager.h"
+#include "Utils.h"
+
+#include <string>
+
+ChunkedData::ChunkedData( FILE* stream, uint32 maxLength, uint32 chunksHint /*= 300*/ ) :
+Stream(stream)
+{
+ if (!Stream)
+ return;
+ Load(maxLength, chunksHint);
+}
+
+ChunkedData::ChunkedData( std::string file, uint32 chunksHint /*= 300*/ )
+{
+ Stream = MPQHandler->GetFile(file);
+ if (!Stream)
+ return;
+ Load(0, chunksHint);
+}
+
+void ChunkedData::Load( uint32 maxLength, uint32 chunksHint )
+{
+ if (!maxLength)
+ maxLength = Utils::Size(Stream);
+ Chunks.reserve(chunksHint);
+ uint32 baseOffset = ftell(Stream);
+ uint32 calcOffset = 0;
+ while ((calcOffset + baseOffset) < Utils::Size(Stream) && (calcOffset < maxLength))
+ {
+ char nameBytes[5];
+ uint32 read = fread(&nameBytes, sizeof(char), 4, Stream);
+ nameBytes[read] = '\0';
+ std::string name = std::string(nameBytes);
+ // Utils::Reverse(nameBytes);
+ name = std::string(name.rbegin(), name.rend());
+ uint32 length;
+ fread(&length, sizeof(uint32), 1, Stream);
+ calcOffset += 8;
+ Chunks.push_back(new Chunk(name.c_str(), length, calcOffset + baseOffset, Stream));
+ calcOffset += length;
+ // save an extra seek at the end
+ if ((calcOffset + baseOffset) < Utils::Size(Stream) && calcOffset < maxLength)
+ fseek(Stream, length, SEEK_CUR);
+ }
+}
+
+int ChunkedData::GetFirstIndex( std::string name )
+{
+ for (int i = 0; i < Chunks.size(); ++i)
+ if (Chunks[i]->Name == name)
+ return i;
+ return -1;
+}
+
+Chunk* ChunkedData::GetChunkByName( std::string name )
+{
+ for (int i = 0; i < Chunks.size(); ++i)
+ if (Chunks[i]->Name == name)
+ return Chunks[i];
+ return NULL;
+}
diff --git a/src/tools/mesh_extractor/ChunkedData.h b/src/tools/mesh_extractor/ChunkedData.h
new file mode 100644
index 00000000000..6e102828222
--- /dev/null
+++ b/src/tools/mesh_extractor/ChunkedData.h
@@ -0,0 +1,20 @@
+#ifndef CHNK_H
+#define CHNK_H
+
+#include <vector>
+#include "Chunk.h"
+
+class ChunkedData
+{
+public:
+ ChunkedData(FILE* stream, uint32 maxLength, uint32 chunksHint = 300);
+ ChunkedData(std::string file, uint32 chunksHint = 300);
+
+ int GetFirstIndex(std::string name);
+ Chunk* GetChunkByName(std::string name);
+
+ void Load(uint32 maxLength, uint32 chunksHint);
+ std::vector<Chunk*> Chunks;
+ FILE* Stream;
+};
+#endif \ No newline at end of file
diff --git a/src/tools/mesh_extractor/Constants.h b/src/tools/mesh_extractor/Constants.h
new file mode 100644
index 00000000000..885761f1687
--- /dev/null
+++ b/src/tools/mesh_extractor/Constants.h
@@ -0,0 +1,33 @@
+#ifndef CONSTANTS_H
+#define CONSTANTS_H
+
+#include "Common.h"
+
+class Constants
+{
+public:
+ enum TriangleType
+ {
+ TRIANGLE_TYPE_UNKNOWN,
+ TRIANGLE_TYPE_TERRAIN,
+ TRIANGLE_TYPE_WATER,
+ TRIANGLE_TYPE_DOODAD,
+ TRIANGLE_TYPE_WMO
+ };
+
+ enum PolyArea
+ {
+ POLY_AREA_TERRAIN = 1,
+ POLY_AREA_WATER = 2,
+ POLY_AREA_ROAD = 3,
+ POLY_AREA_DANGER = 4,
+ };
+ static const float TileSize;
+ static const float MaxXY;
+ static const float ChunkSize;
+ static const float UnitSize;
+ static const float Origin[];
+ static const float PI;
+};
+
+#endif \ No newline at end of file
diff --git a/src/tools/mesh_extractor/ContinentBuilder.cpp b/src/tools/mesh_extractor/ContinentBuilder.cpp
new file mode 100644
index 00000000000..8af6f3a02e8
--- /dev/null
+++ b/src/tools/mesh_extractor/ContinentBuilder.cpp
@@ -0,0 +1,11 @@
+#include "ContinentBuilder.h"
+#include "WDT.h"
+#include "Utils.h"
+
+void ContinentBuilder::Build()
+{
+ for (std::vector<TilePos>::iterator itr = TileMap->TileTable.begin(); itr != TileMap->TileTable.end(); ++itr)
+ {
+
+ }
+}
diff --git a/src/tools/mesh_extractor/ContinentBuilder.h b/src/tools/mesh_extractor/ContinentBuilder.h
new file mode 100644
index 00000000000..7db141ddcf1
--- /dev/null
+++ b/src/tools/mesh_extractor/ContinentBuilder.h
@@ -0,0 +1,15 @@
+#ifndef CONT_BUILDER_H
+#define CONT_BUILDER_H
+#include <string>
+#include "WDT.h"
+
+class ContinentBuilder
+{
+public:
+ ContinentBuilder(std::string continent, WDT* wdt) : Continent(continent), TileMap(wdt) {}
+ void Build();
+private:
+ std::string Continent;
+ WDT* TileMap;
+};
+#endif \ No newline at end of file
diff --git a/src/tools/mesh_extractor/DoodadHandler.cpp b/src/tools/mesh_extractor/DoodadHandler.cpp
new file mode 100644
index 00000000000..4a7707753c4
--- /dev/null
+++ b/src/tools/mesh_extractor/DoodadHandler.cpp
@@ -0,0 +1,99 @@
+#include "DoodadHandler.h"
+#include "Chunk.h"
+#include "Cache.h"
+#include "Model.h"
+#include "g3d/Matrix4.h"
+
+DoodadHandler::DoodadHandler( ADT* adt ) : ObjectDataHandler(adt), _definitions(NULL), _paths(NULL)
+{
+ if (!adt->HasObjectData)
+ return;
+ Chunk* mddf = adt->ObjectData->GetChunkByName("MDDF");
+ if (mddf)
+ ReadDoodadDefinitions(mddf);
+
+ Chunk* mmid = adt->ObjectData->GetChunkByName("MMID");
+ Chunk* mmdx = adt->ObjectData->GetChunkByName("MMDX");
+ if (mmid && mmdx)
+ ReadDoodadPaths(mmid, mmdx);
+}
+
+void DoodadHandler::ProcessInternal( ChunkedData* subChunks )
+{
+ if (!IsSane())
+ return;
+ Chunk* doodadReferencesChunk = subChunks->GetChunkByName("MCRD");
+ if (!doodadReferencesChunk)
+ return;
+ FILE* stream = doodadReferencesChunk->GetStream();
+ uint32 refCount = doodadReferencesChunk->Length / 4;
+ for (int i = 0; i < refCount; i++)
+ {
+ int32 index;
+ fread(&index, sizeof(int32), 1, stream);
+ if (index < 0 || index >= _definitions->size())
+ continue;
+ DoodadDefinition doodad = (*_definitions)[index];
+ if (_drawn.find(doodad.UniqueId) != _drawn.end())
+ continue;
+ _drawn.insert(doodad.UniqueId);
+ if (doodad.MmidIndex >= _paths->size())
+ continue;
+
+ std::string path = (*_paths)[doodad.MmidIndex];
+ Model* model = Cache.ModelCache.Get(path);
+ if (!model)
+ {
+ model = new Model(path);
+ Cache.ModelCache.Insert(path, model);
+ }
+ if (!model->IsCollidable)
+ continue;
+
+ Vertices.reserve(refCount * model->Vertices.size() * 0.2);
+ Triangles.reserve(refCount * model->Triangles.size() * 0.2);
+
+ InsertModelGeometry(doodad, model);
+ }
+}
+
+void DoodadHandler::ReadDoodadDefinitions( Chunk* chunk )
+{
+ int32 count = chunk->Length / 36;
+ _definitions = new std::vector<DoodadDefinition>;
+ _definitions->reserve(count);
+ FILE* stream = chunk->GetStream();
+ for (int i = 0; i < count; i++)
+ {
+ DoodadDefinition def;
+ def.Read(stream);
+ _definitions->push_back(def);
+ }
+}
+
+void DoodadHandler::ReadDoodadPaths( Chunk* id, Chunk* data )
+{
+ int paths = id->Length / 4;
+ _paths = new std::vector<std::string>();
+ _paths->reserve(paths);
+ for (int i = 0; i < paths; i++)
+ {
+ FILE* idStream = id->GetStream();
+ fseek(idStream, i * 4, SEEK_CUR);
+ uint32 offset;
+ fread(&offset, sizeof(uint32), 1, idStream);
+ FILE* dataStream = data->GetStream();
+ fseek(dataStream, offset + data->Offset, SEEK_SET);
+ _paths->push_back(Utils::ReadString(dataStream));
+ }
+}
+
+void DoodadHandler::InsertModelGeometry(DoodadDefinition def, Model* model)
+{
+ G3D::Matrix4 transformation = Utils::GetTransformation(def);
+ uint32 vertOffset = Vertices.size();
+ for (std::vector<Vector3>::iterator itr = model->Vertices.begin(); itr != model->Vertices.end(); ++itr)
+ Vertices.push_back(Utils::VectorTransform(*itr, transformation));
+ for (std::vector<Triangle<uint16> >::iterator itr = model->Triangles.begin(); itr != model->Triangles.end(); ++itr)
+ Triangles.push_back(Triangle<uint16>(Constants::TRIANGLE_TYPE_DOODAD, itr->V0 + vertOffset, itr->V1 + vertOffset, itr->V2 + vertOffset));
+} \ No newline at end of file
diff --git a/src/tools/mesh_extractor/DoodadHandler.h b/src/tools/mesh_extractor/DoodadHandler.h
new file mode 100644
index 00000000000..c62584ca1f1
--- /dev/null
+++ b/src/tools/mesh_extractor/DoodadHandler.h
@@ -0,0 +1,51 @@
+#ifndef DOOADHNDL_H
+#define DOOADHNDL_H
+#include "ObjectDataHandler.h"
+#include "Utils.h"
+#include "Chunk.h"
+#include "Model.h"
+#include <set>
+#include <vector>
+
+class DoodadDefinition : IDefinition
+{
+public:
+ uint32 MmidIndex;
+ uint32 UniqueId;
+ uint16 DecimalScale;
+ uint16 Flags;
+
+ float Scale() { return DecimalScale / 1024.0f; }
+
+ void Read(FILE* stream)
+ {
+ fread(&MmidIndex, sizeof(uint32), 1, stream);
+ fread(&UniqueId, sizeof(uint32), 1, stream);
+ Position = Vector3::Read(stream);
+ Rotation = Vector3::Read(stream);
+ fread(&DecimalScale, sizeof(uint16), 1, stream);
+ fread(&Flags, sizeof(uint16), 1, stream);
+ }
+};
+
+class DoodadHandler : public ObjectDataHandler
+{
+public:
+ DoodadHandler(ADT* adt);
+ std::vector<Vector3> Vertices;
+ std::vector<Triangle<uint32> > Triangles;
+ bool IsSane() { return _definitions && _paths; }
+
+
+protected:
+ void ProcessInternal(ChunkedData* chunk);
+
+private:
+ void ReadDoodadDefinitions(Chunk* chunk);
+ void ReadDoodadPaths(Chunk* id, Chunk* data);
+ void InsertModelGeometry(DoodadDefinition def, Model* model);
+ std::set<uint32> _drawn;
+ std::vector<DoodadDefinition>* _definitions;
+ std::vector<std::string>* _paths;
+};
+#endif \ No newline at end of file
diff --git a/src/tools/mesh_extractor/Geometry.cpp b/src/tools/mesh_extractor/Geometry.cpp
new file mode 100644
index 00000000000..8e417af4c73
--- /dev/null
+++ b/src/tools/mesh_extractor/Geometry.cpp
@@ -0,0 +1,102 @@
+#include "Geometry.h"
+#include "Constants.h"
+
+Geometry::Geometry() : Transform(false)
+{
+ Vertices.reserve(10000);
+ Triangles.reserve(10000);
+}
+
+void Geometry::CalculateBoundingBox( float*& min, float*& max )
+{
+ min = new float[3];
+ max = new float[3];
+
+ for (std::vector<Vector3>::iterator itr = Vertices.begin(); itr != Vertices.end(); ++itr)
+ {
+ if (itr->x > max[0])
+ max[0] = itr->x;
+ if (itr->x < min[0])
+ min[0] = itr->x;
+
+ if (itr->y > max[1])
+ max[1] = itr->y;
+ if (itr->y < min[1])
+ min[1] = itr->y;
+
+ if (itr->z > max[2])
+ max[2] = itr->z;
+ if (itr->z < min[2])
+ min[2] = itr->z;
+ }
+}
+
+void Geometry::CalculateMinMaxHeight( float& min, float& max )
+{
+ min = 0.0f;
+ max = 0.0f;
+
+ for (std::vector<Vector3>::iterator itr = Vertices.begin(); itr != Vertices.end(); ++itr)
+ {
+ if (Transform)
+ {
+ if (itr->y < min)
+ min = itr->y;
+ if (itr->y > max)
+ max = itr->y;
+ }
+ else
+ {
+ if (itr->z < min)
+ min = itr->z;
+ if (itr->z > max)
+ max = itr->z;
+ }
+ }
+}
+
+void Geometry::AddData( std::vector<Vector3>& verts, std::vector<Triangle<uint32> >& tris )
+{
+ uint32 vertOffset = Vertices.size();
+ for (std::vector<Vector3>::iterator itr = verts.begin(); itr != verts.end(); ++itr)
+ Vertices.push_back(Transform ? Utils::ToRecast(*itr) : *itr);
+
+ for (std::vector<Triangle<uint32> >::iterator itr = tris.begin(); itr != tris.end(); ++itr)
+ Triangles.push_back(Triangle<uint32>(itr->Type, itr->V0 + vertOffset, itr->V1 + vertOffset, itr->V2 + vertOffset));
+}
+
+void Geometry::GetRawData( float*& verts, int*& tris, uint8*& areas )
+{
+ verts = new float[Vertices.size() * 3];
+ for (int i = 0; i < Vertices.size(); ++i)
+ {
+ Vector3& vert = Vertices[i];
+ verts[(i * 3) + 0] = vert.x;
+ verts[(i * 3) + 1] = vert.y;
+ verts[(i * 3) + 2] = vert.z;
+ }
+
+ tris = new int[Triangles.size() * 3];
+ for (int i = 0; i < Triangles.size(); ++i)
+ {
+ Triangle<uint32>& tri = Triangles[i];
+ tris[(i * 3) + 0] = (int)tri.V0;
+ tris[(i * 3) + 1] = (int)tri.V1;
+ tris[(i * 3) + 2] = (int)tri.V2;
+ }
+
+ areas = new uint8[Triangles.size()];
+ for (int i = 0; i < Triangles.size(); i++)
+ {
+ switch (Triangles[i].Type)
+ {
+ case Constants::TRIANGLE_TYPE_WATER:
+ areas[i] = Constants::POLY_AREA_WATER;
+ break;
+ default:
+ areas[i] = Constants::POLY_AREA_TERRAIN;
+ break;
+ }
+ }
+}
+
diff --git a/src/tools/mesh_extractor/Geometry.h b/src/tools/mesh_extractor/Geometry.h
new file mode 100644
index 00000000000..048d9fde578
--- /dev/null
+++ b/src/tools/mesh_extractor/Geometry.h
@@ -0,0 +1,21 @@
+#ifndef GEOMETRY_H
+#define GEOMETRY_H
+#include <vector>
+
+#include "Utils.h"
+
+class Geometry
+{
+public:
+ Geometry();
+
+ void CalculateBoundingBox(float*& min, float*& max);
+ void CalculateMinMaxHeight(float& min, float& max);
+ void AddData(std::vector<Vector3>& verts, std::vector<Triangle<uint32> >& tris);
+ void GetRawData(float*& verts, int*& tris, uint8*& areas);
+
+ std::vector<Vector3> Vertices;
+ std::vector<Triangle<uint32> > Triangles;
+ bool Transform;
+};
+#endif \ No newline at end of file
diff --git a/src/tools/mesh_extractor/LiquidHandler.cpp b/src/tools/mesh_extractor/LiquidHandler.cpp
new file mode 100644
index 00000000000..5924c693196
--- /dev/null
+++ b/src/tools/mesh_extractor/LiquidHandler.cpp
@@ -0,0 +1 @@
+#include "LiquidHandler.h" \ No newline at end of file
diff --git a/src/tools/mesh_extractor/LiquidHandler.h b/src/tools/mesh_extractor/LiquidHandler.h
new file mode 100644
index 00000000000..ba89be33e08
--- /dev/null
+++ b/src/tools/mesh_extractor/LiquidHandler.h
@@ -0,0 +1,17 @@
+#ifndef LIQUID_H
+#define LIQUID_H
+#include "ADT.h"
+#include "Utils.h"
+#include "Common.h"
+
+#include <vector>
+
+class LiquidHandler
+{
+public:
+ ADT* Source;
+ std::vector<Vector3> Vertices;
+ std::vector<Triangle<uint32> > Triangles;
+ std::vector<MCNKLiquidData> MCNKData;
+};
+#endif \ No newline at end of file
diff --git a/src/tools/mesh_extractor/MPQ.cpp b/src/tools/mesh_extractor/MPQ.cpp
new file mode 100644
index 00000000000..fbae56cd7a0
--- /dev/null
+++ b/src/tools/mesh_extractor/MPQ.cpp
@@ -0,0 +1,117 @@
+#include "MPQ.h"
+#include "MPQManager.h"
+#include <deque>
+#include <cstdio>
+
+MPQArchive::MPQArchive(const char* filename)
+{
+ int result = libmpq__archive_open(&mpq_a, filename, -1);
+ printf("Opening %s\n", filename);
+ if (result)
+ {
+ switch (result)
+ {
+ case LIBMPQ_ERROR_OPEN :
+ printf("Error opening archive '%s': Does file really exist?\n", filename);
+ break;
+ case LIBMPQ_ERROR_FORMAT : /* bad file format */
+ printf("Error opening archive '%s': Bad file format\n", filename);
+ break;
+ case LIBMPQ_ERROR_SEEK : /* seeking in file failed */
+ printf("Error opening archive '%s': Seeking in file failed\n", filename);
+ break;
+ case LIBMPQ_ERROR_READ : /* Read error in archive */
+ printf("Error opening archive '%s': Read error in archive\n", filename);
+ break;
+ case LIBMPQ_ERROR_MALLOC : /* maybe not enough memory? :) */
+ printf("Error opening archive '%s': Maybe not enough memory\n", filename);
+ break;
+ default:
+ printf("Error opening archive '%s': Unknown error\n", filename);
+ break;
+ }
+ }
+}
+
+void MPQArchive::close()
+{
+ libmpq__archive_close(mpq_a);
+}
+
+MPQFile::MPQFile(const char* filename):
+eof(false), buffer(0), pointer(0), size(0)
+{
+ for (std::deque<MPQArchive*>::iterator i = MPQHandler->Archives.begin(); i != MPQHandler->Archives.end();++i)
+ {
+ mpq_archive* mpq_a = (*i)->mpq_a;
+
+ uint32_t filenum;
+ if(libmpq__file_number(mpq_a, filename, &filenum))
+ continue;
+ libmpq__off_t transferred;
+ libmpq__file_unpacked_size(mpq_a, filenum, &size);
+
+ // HACK: in patch.mpq some files don't want to open and give 1 for filesize
+ if (size<=1) {
+ // printf("warning: file %s has size %d; cannot Read.\n", filename, size);
+ eof = true;
+ buffer = 0;
+ return;
+ }
+ buffer = new char[size];
+
+ //libmpq_file_getdata
+ libmpq__file_read(mpq_a, filenum, (unsigned char*)buffer, size, &transferred);
+ /*libmpq_file_getdata(&mpq_a, hash, fileno, (unsigned char*)buffer);*/
+ return;
+
+ }
+ eof = true;
+ buffer = 0;
+}
+
+size_t MPQFile::Read(void* dest, size_t bytes)
+{
+ if (eof)
+ return 0;
+
+ size_t rpos = pointer + bytes;
+ if (rpos > size) {
+ bytes = size - pointer;
+ eof = true;
+ }
+
+ memcpy(dest, &(buffer[pointer]), bytes);
+
+ pointer = rpos;
+
+ return bytes;
+}
+
+void MPQFile::seek(int offset)
+{
+ pointer = offset;
+ eof = (pointer >= size);
+}
+
+void MPQFile::seekRelative(int offset)
+{
+ pointer += offset;
+ eof = (pointer >= size);
+}
+
+void MPQFile::close()
+{
+ if (buffer)
+ delete[] buffer;
+ buffer = 0;
+ eof = true;
+}
+
+FILE* MPQFile::GetFileStream()
+{
+ FILE* file = tmpfile();
+ fwrite(buffer, sizeof(char), size, file);
+ fseek(file, 0, SEEK_SET);
+ return file;
+}
diff --git a/src/tools/mesh_extractor/MPQ.h b/src/tools/mesh_extractor/MPQ.h
new file mode 100644
index 00000000000..dd566bccff5
--- /dev/null
+++ b/src/tools/mesh_extractor/MPQ.h
@@ -0,0 +1,88 @@
+#ifndef MPQ_H
+#define MPQ_H
+
+#include "libmpq/mpq.h"
+#include "Common.h"
+#include <string.h>
+#include <ctype.h>
+#include <vector>
+#include <iostream>
+#include <deque>
+
+using namespace std;
+
+class MPQArchive
+{
+
+public:
+ mpq_archive_s *mpq_a;
+
+ MPQArchive(const char* filename);
+ void close();
+
+ void GetFileListTo(vector<string>& filelist) {
+ uint32_t filenum;
+ if(libmpq__file_number(mpq_a, "(listfile)", &filenum)) return;
+ libmpq__off_t size, transferred;
+ libmpq__file_unpacked_size(mpq_a, filenum, &size);
+
+ char *buffer = new char[size];
+
+ libmpq__file_read(mpq_a, filenum, (unsigned char*)buffer, size, &transferred);
+
+ char seps[] = "\n";
+ char* token;
+
+ token = strtok( buffer, seps );
+ uint32 counter = 0;
+ while ((token != NULL) && (counter < size)) {
+ //cout << token << endl;
+ token[strlen(token) - 1] = 0;
+ string s = token;
+ filelist.push_back(s);
+ counter += strlen(token) + 2;
+ token = strtok(NULL, seps);
+ }
+
+ delete[] buffer;
+ }
+};
+
+class MPQFile
+{
+ //MPQHANDLE handle;
+ bool eof;
+ char *buffer;
+ libmpq__off_t pointer,size;
+
+ // disable copying
+ MPQFile(const MPQFile& /*f*/) {}
+ void operator=(const MPQFile& /*f*/) {}
+
+public:
+ MPQFile(const char* filename); // filenames are not case sensitive
+ ~MPQFile() { close(); }
+ size_t Read(void* dest, size_t bytes);
+ FILE* GetFileStream();
+ size_t getSize() { return size; }
+ size_t getPos() { return pointer; }
+ char* getBuffer() { return buffer; }
+ char* getPointer() { return buffer + pointer; }
+ bool isEof() { return eof; }
+ void seek(int offset);
+ void seekRelative(int offset);
+ void close();
+};
+
+inline void flipcc(char *fcc)
+{
+ char t;
+ t=fcc[0];
+ fcc[0]=fcc[3];
+ fcc[3]=t;
+ t=fcc[1];
+ fcc[1]=fcc[2];
+ fcc[2]=t;
+}
+
+#endif
diff --git a/src/tools/mesh_extractor/MPQManager.cpp b/src/tools/mesh_extractor/MPQManager.cpp
new file mode 100644
index 00000000000..429b09ffe02
--- /dev/null
+++ b/src/tools/mesh_extractor/MPQManager.cpp
@@ -0,0 +1,58 @@
+#include "MPQManager.h"
+#include "MPQ.h"
+
+char* MPQManager::Files[] = {
+ "common.MPQ",
+ "common-2.MPQ",
+ "expansion.MPQ",
+ "lichking.MPQ",
+ "patch.MPQ",
+ "patch-2.MPQ",
+ "patch-3.MPQ"
+};
+
+void MPQManager::Initialize()
+{
+ LoadMPQs();
+}
+
+void MPQManager::LoadMaps()
+{
+
+}
+
+void MPQManager::LoadMPQs()
+{
+ // Load the locale MPQ files first
+ char filename[512];
+
+ /*sprintf(filename,"Data/%s/locale-%s.MPQ", langs[locale], langs[locale]);*/
+ Archives.push_front(new MPQArchive("Data/enUS/locale-enUS.MPQ"));
+
+ for(int i = 0; i < 3; ++i)
+ {
+ char ext[3] = "";
+ if (i)
+ sprintf(ext, "-%i", i + 1);
+
+ sprintf(filename, "Data/enUS/patch-enUS%s.MPQ", ext);
+ Archives.push_front(new MPQArchive(filename));
+ }
+
+ // Now load the common MPQ files
+ int count = sizeof(Files) / sizeof(char*);
+ for (int i = 0; i < count; ++i)
+ {
+ sprintf(filename, "Data/%s", Files[i]);
+ Archives.push_front(new MPQArchive(filename));
+ }
+ printf("Loaded %u MPQ files succesfully", Archives.size());
+}
+
+FILE* MPQManager::GetFile( std::string path )
+{
+ MPQFile file(path.c_str());
+ if (file.isEof())
+ return NULL;
+ return file.GetFileStream();
+}
diff --git a/src/tools/mesh_extractor/MPQManager.h b/src/tools/mesh_extractor/MPQManager.h
new file mode 100644
index 00000000000..e10066ae4a6
--- /dev/null
+++ b/src/tools/mesh_extractor/MPQManager.h
@@ -0,0 +1,24 @@
+#ifndef MPQ_MANAGER_H
+#define MPQ_MANAGER_H
+
+#include "MPQ.h"
+
+class MPQManager
+{
+public:
+ MPQManager() {}
+ ~MPQManager() {}
+
+ void Initialize();
+ void LoadMaps();
+ FILE* GetFile(std::string path);
+
+ std::deque<MPQArchive*> Archives;
+
+ static char* Files[];
+protected:
+ void LoadMPQs();
+};
+
+extern MPQManager* MPQHandler;
+#endif \ No newline at end of file
diff --git a/src/tools/mesh_extractor/MapChunk.cpp b/src/tools/mesh_extractor/MapChunk.cpp
new file mode 100644
index 00000000000..95f8c2e56c0
--- /dev/null
+++ b/src/tools/mesh_extractor/MapChunk.cpp
@@ -0,0 +1,77 @@
+#include "MapChunk.h"
+#include "ADT.h"
+
+MapChunk::MapChunk( ADT* _adt, Chunk* chunk ) : adt(_adt), Source(chunk), Vertices(NULL)
+{
+ FILE* stream = chunk->GetStream();
+ Header.Read(stream);
+ fseek(stream, chunk->Offset, SEEK_SET);
+ GenerateVertices(stream);
+}
+
+void MapChunk::GenerateTriangles()
+{
+ Triangles.reserve(256);
+ for (int y = 0; y < 8; y++)
+ {
+ for (int x = 0; x < 8; x++)
+ {
+ if (HasHole(Header.Holes, x / 2, y / 2))
+ continue;
+
+ uint32 topLeft = (17 * y) + x;
+ uint32 topRight = (17 * y) + x + 1;
+ uint32 bottomLeft = (17 * (y + 1)) + x;
+ uint32 bottomRight = (17 * (y + 1)) + x + 1;
+ uint32 center = (17 * y) + 9 + x;
+
+ uint8 triangleType = Constants::TRIANGLE_TYPE_TERRAIN;
+ if (ADT.LiquidHandler && ADT.LiquidHandler.MCNKData)
+ {
+ var data = ADT.LiquidHandler.MCNKData[Index];
+ uint32 maxHeight = std::max(
+ std::max(
+ std::max(std::max(Vertices[topLeft].z, Vertices[topRight].z), Vertices[bottomLeft].z),
+ Vertices[bottomRight].z), Vertices[center].z);
+ if (data && data->IsWater(x, y, maxHeight))
+ triangleType = Constants::TRIANGLE_TYPE_WATER;
+ }
+
+ Triangles.push_back(new Triangle<uint8>(triangleType, topRight, topLeft, center));
+ Triangles.push_back(new Triangle<uint8>(triangleType, topLeft, bottomLeft, center));
+ Triangles.push_back(new Triangle<uint8>(triangleType, bottomLeft, bottomRight, center));
+ Triangles.push_back(new Triangle<uint8>(triangleType, bottomRight, topRight, center));
+ }
+ }
+}
+
+MapChunk::~MapChunk()
+{
+ delete[] Vertices;
+}
+
+void MapChunk::GenerateVertices( FILE* stream )
+{
+ fseek(stream, Header.OffsetMCVT, SEEK_CUR);
+ int32 vertIndex = 0;
+ Vertices = new Vector3[125];
+
+ for (int j = 0; j < 17; j++)
+ {
+ int values = j % 2 ? 8 : 9;
+ for (int i = 0; i < values; i++)
+ {
+ float tmp;
+ fread(&tmp, sizeof(float), 1, stream);
+ Vector3 vert(Header.Position.x - (j * (Constants::UnitSize * 0.5f)), Header.Position.y - (i * Constants::UnitSize), Header.Position.z + tmp);
+ if (values == 8)
+ vert.y -= Constants::UnitSize * 0.5f;
+ Vertices[vertIndex++] = vert;
+ }
+ }
+}
+
+bool MapChunk::HasHole( uint32 map, int x, int y )
+{
+ return (map & 0x0000FFFF) & ((1 << x) << (y << 2));
+}
diff --git a/src/tools/mesh_extractor/MapChunk.h b/src/tools/mesh_extractor/MapChunk.h
new file mode 100644
index 00000000000..034429a81ec
--- /dev/null
+++ b/src/tools/mesh_extractor/MapChunk.h
@@ -0,0 +1,25 @@
+#ifndef MAPCHUNK_H
+#define MAPCHUNK_H
+#include "Chunk.h"
+#include "Utils.h"
+#include "Constants.h"
+#include <vector>
+class ADT;
+
+class MapChunk
+{
+public:
+ MapChunk(ADT* _adt, Chunk* chunk);
+ ~MapChunk();
+
+ void GenerateTriangles();
+ void GenerateVertices(FILE* stream);
+ static bool HasHole(uint32 map, int x, int y);
+ ADT* adt;
+ Chunk* Source;
+ MapChunkHeader Header;
+ Vector3* Vertices;
+ std::vector<Triangle<uint8> > Triangles;
+ int32 Index;
+};
+#endif \ No newline at end of file
diff --git a/src/tools/mesh_extractor/MeshExtractor.cpp b/src/tools/mesh_extractor/MeshExtractor.cpp
new file mode 100644
index 00000000000..0220627cbcc
--- /dev/null
+++ b/src/tools/mesh_extractor/MeshExtractor.cpp
@@ -0,0 +1,34 @@
+#include "MPQManager.h"
+#include "WDT.h"
+#include "ContinentBuilder.h"
+#include "Cache.h"
+
+MPQManager* MPQHandler;
+CacheClass* Cache;
+
+void ExtractAllMaps()
+{
+ WDT wdt("World\\maps\\DalaranPrison\\DalaranPrison.wdt");
+ if (!wdt.IsValid)
+ return;
+ printf("Model valid!");
+ if (wdt.IsGlobalModel)
+ {
+ printf("Unsupported");
+ return;
+ }
+ ContinentBuilder builder("DalaranPrison", &wdt);
+ builder.Build();
+}
+
+int main(int argc, char* argv[])
+{
+ system("pause");
+ Cache = new CacheClass();
+ MPQHandler = new MPQManager();
+ MPQHandler->Initialize();
+ MPQHandler->LoadMaps();
+ ExtractAllMaps();
+ return 0;
+}
+
diff --git a/src/tools/mesh_extractor/Model.cpp b/src/tools/mesh_extractor/Model.cpp
new file mode 100644
index 00000000000..a1c67d23f28
--- /dev/null
+++ b/src/tools/mesh_extractor/Model.cpp
@@ -0,0 +1,49 @@
+#include "Model.h"
+#include "MPQManager.h"
+#include "Utils.h"
+
+Model::Model( std::string path )
+{
+ Stream = MPQHandler->GetFile(Utils::FixModelPath(path));
+ Header.Read(Stream);
+ if (Header.OffsetBoundingNormals > 0 && Header.OffsetBoundingVertices > 0 &&
+ Header.OffsetBoundingTriangles > 0 && Header.BoundingRadius > 0.0f)
+ {
+ IsCollidable = true;
+ ReadVertices(Stream);
+ ReadBoundingNormals(Stream);
+ ReadBoundingTriangles(Stream);
+ }
+}
+
+void Model::ReadVertices( FILE* stream )
+{
+ fseek(stream, Header.OffsetBoundingVertices, SEEK_SET);
+ Vertices.reserve(Header.CountBoundingVertices);
+ for (int i = 0; i < Header.CountBoundingVertices; ++i)
+ Vertices[i] = Vector3::Read(stream);
+}
+
+void Model::ReadBoundingTriangles( FILE* stream )
+{
+ fseek(stream, Header.OffsetBoundingTriangles, SEEK_SET);
+ Triangles.reserve(Header.CountBoundingTriangles / 3);
+ for (int i = 0; i < Header.CountBoundingTriangles / 3; i++)
+ {
+ Triangle<uint16> tri;
+ tri.Type = Constants::TRIANGLE_TYPE_DOODAD;
+ fread(&tri.V0, sizeof(uint16), 1, stream);
+ fread(&tri.V1, sizeof(uint16), 1, stream);
+ fread(&tri.V2, sizeof(uint16), 1, stream);
+ Triangles[i] = tri;
+ }
+}
+
+void Model::ReadBoundingNormals( FILE* stream )
+{
+ fseek(stream, Header.OffsetBoundingNormals, SEEK_SET);
+ Normals.reserve(Header.CountBoundingNormals);
+ for (int i = 0; i < Header.CountBoundingNormals; i++)
+ Normals[i] = Vector3::Read(stream);
+}
+
diff --git a/src/tools/mesh_extractor/Model.h b/src/tools/mesh_extractor/Model.h
new file mode 100644
index 00000000000..a5b8338461d
--- /dev/null
+++ b/src/tools/mesh_extractor/Model.h
@@ -0,0 +1,21 @@
+#ifndef MODEL_H
+#define MODEL_H
+#include <vector>
+#include "Utils.h"
+
+class Model
+{
+public:
+ Model(std::string path);
+
+ void ReadVertices(FILE* stream);
+ void ReadBoundingTriangles(FILE* stream);
+ void ReadBoundingNormals(FILE* stream);
+ ModelHeader Header;
+ std::vector<Vector3> Vertices;
+ std::vector<Vector3> Normals;
+ std::vector<Triangle<uint16> > Triangles;
+ bool IsCollidable;
+ FILE* Stream;
+};
+#endif \ No newline at end of file
diff --git a/src/tools/mesh_extractor/ObjectDataHandler.cpp b/src/tools/mesh_extractor/ObjectDataHandler.cpp
new file mode 100644
index 00000000000..3ed2ee49604
--- /dev/null
+++ b/src/tools/mesh_extractor/ObjectDataHandler.cpp
@@ -0,0 +1,21 @@
+#include "ObjectDataHandler.h"
+#include "Chunk.h"
+#include "ADT.h"
+#include "ChunkedData.h"
+
+void ObjectDataHandler::ProcessMapChunk( MapChunk* chunk )
+{
+ if (!Source->HasObjectData)
+ return;
+ // fuck it blizzard, why is this crap necessary?
+ int32 firstIndex = Source->ObjectData->GetFirstIndex("MCNK");
+ if (firstIndex == -1)
+ return;
+ if (firstIndex + chunk->Index > Source->ObjectData->Chunks.size())
+ return;
+ Chunk* ourChunk = Source->ObjectData->Chunks[firstIndex + chunk->Index];
+ if (ourChunk->Length == 0)
+ return;
+ ChunkedData* subChunks = new ChunkedData(ourChunk->GetStream(), ourChunk->Length, 2);
+ ProcessInternal(subChunks);
+}
diff --git a/src/tools/mesh_extractor/ObjectDataHandler.h b/src/tools/mesh_extractor/ObjectDataHandler.h
new file mode 100644
index 00000000000..834bf66bcfb
--- /dev/null
+++ b/src/tools/mesh_extractor/ObjectDataHandler.h
@@ -0,0 +1,15 @@
+#ifndef ODATA_HNDL_H
+#define ODATA_HNDL_H
+#include "ADT.h"
+#include "MapChunk.h"
+
+class ObjectDataHandler
+{
+public:
+ ObjectDataHandler(ADT* _adt) : Source(_adt) {}
+
+ void ProcessMapChunk(MapChunk* chunk);
+ virtual void ProcessInternal(ChunkedData* data) = 0;
+ ADT* Source;
+};
+#endif \ No newline at end of file
diff --git a/src/tools/mesh_extractor/TileBuilder.cpp b/src/tools/mesh_extractor/TileBuilder.cpp
new file mode 100644
index 00000000000..070b9d70050
--- /dev/null
+++ b/src/tools/mesh_extractor/TileBuilder.cpp
@@ -0,0 +1,37 @@
+#include "TileBuilder.h"
+#include "Constants.h"
+
+TileBuilder::TileBuilder(std::string world, int x, int y) : _Geometry(NULL), World(world), X(x), Y(y), MapId(608)
+{
+ // Cell Size = TileSize / TileVoxelSize
+ // 1800 = TileVoxelSize
+ Config.cs = Constants::TileSize / 1800;
+ // Cell Height
+ Config.ch = 0.3f;
+ // Min Region Area = 6^2
+ Config.minRegionArea = 36;
+ // Merge Region Area = 12^2
+ Config.mergeRegionArea = 144;
+ Config.walkableSlopeAngle = 50.0f;
+ Config.detailSampleDist = 3.0f;
+ Config.detailSampleMaxError = 1.25f;
+ Config.walkableClimb = 1.0f / Config.ch;
+ Config.walkableHeight = 2.1f / Config.ch;
+ Config.walkableRadius = 0.6f / Config.cs;
+ Config.maxEdgeLen = Config.walkableRadius * 8;
+ Config.borderSize = Config.walkableRadius + 8;
+ Config.width = 1800;
+ Config.maxVertsPerPoly = 6;
+ Config.maxSimplificationError = 1.3f;
+}
+
+void TileBuilder::CalculateTileBounds( float*& bmin, float*& bmax )
+{
+ float origin[3] = Constants::Origin;
+ bmin = new float[3];
+ bmax = new float[3];
+ bmin[0] = origin[0] + (Constants::TileSize * X);
+ bmin[2] = origin[2] + (Constants::TileSize * Y);
+ bmax[0] = origin[0] + (Constants::TileSize * (X + 1));
+ bmax[2] = origin[2] + (Constants::TileSize * (Y + 1));
+} \ No newline at end of file
diff --git a/src/tools/mesh_extractor/TileBuilder.h b/src/tools/mesh_extractor/TileBuilder.h
new file mode 100644
index 00000000000..badb05295e5
--- /dev/null
+++ b/src/tools/mesh_extractor/TileBuilder.h
@@ -0,0 +1,22 @@
+#ifndef TILE_BUILD_H
+#define TILE_BUILD_H
+#include <string>
+#include "Recast.h"
+
+#include "Geometry.h"
+
+class TileBuilder
+{
+public:
+ TileBuilder(std::string world, int x, int y);
+ void CalculateTileBounds(float*& bmin, float*& bmax);
+ uint8* Build();
+
+ std::string World;
+ int X;
+ int Y;
+ int MapId;
+ rcConfig Config;
+ Geometry* _Geometry;
+};
+#endif \ No newline at end of file
diff --git a/src/tools/mesh_extractor/Utils.cpp b/src/tools/mesh_extractor/Utils.cpp
new file mode 100644
index 00000000000..03473eb0794
--- /dev/null
+++ b/src/tools/mesh_extractor/Utils.cpp
@@ -0,0 +1,163 @@
+#include "Utils.h"
+#include "Constants.h"
+#include <cstring>
+#include "g3d/Matrix4.h"
+
+const float Constants::TileSize = 533.0f + (1/3.0f);
+const float Constants::MaxXY = 32.0f * Constants::TileSize;
+const float Constants::ChunkSize = Constants::TileSize / 16.0f;
+const float Constants::UnitSize = Constants::ChunkSize / 8.0f;
+const float Constants::Origin[] = { -Constants::MaxXY, 0.0f, -Constants::MaxXY };
+const float Constants::PI = 3.1415926f;
+
+void Utils::Reverse(char word[])
+{
+ int len = strlen(word);
+ for (int i = 0;i < len / 2; i++)
+ {
+ word[i] ^= word[len-i-1];
+ word[len-i-1] ^= word[i];
+ word[i] ^= word[len-i-1];
+ }
+}
+
+std::string Utils::ReadString( FILE* file )
+{
+ std::string ret;
+ int i = 0;
+ while (true)
+ {
+ char b;
+ fread(&b, sizeof(char), 1, file);
+ if (b == 0)
+ break;
+ ret[i++] = b;
+ }
+ return ret;
+}
+
+uint32 Utils::Size( FILE* file )
+{
+ // store the old position
+ uint32 offset = ftell(file);
+ // Get file size
+ fseek(file, 0, SEEK_END);
+ uint32 size = ftell(file);
+ // reset back to the old position
+ fseek(file, offset, SEEK_SET);
+ return size;
+}
+
+Vector3 Utils::ToRecast( Vector3 val )
+{
+ return Vector3(-val.y, val.z, -val.x);
+}
+
+std::string Utils::GetAdtPath( std::string world, int x, int y )
+{
+ return "World\\Maps\\" + world + "\\" + world + "_" + Utils::ToString(x) + "_" + Utils::ToString(y) + ".adt";
+}
+
+std::string Utils::FixModelPath( std::string path )
+{
+ std::string::size_type idx = path.rfind(".");
+ // Bizarre way of changing extension but...
+ if (idx != std::string::npos)
+ {
+ path[idx + 1] = "M";
+ path[idx + 2] = "2";
+ }
+ return path;
+}
+
+G3D::Matrix4 Utils::RotationX(float angle)
+{
+ float _cos = cos(angle);
+ float _sin = sin(angle);
+ Matrix4 ret = G3D::Matrix4::identity();
+ ret[2][2] = _cos;
+ ret[2][3] = _sin;
+ ret[3][2] = -_sin;
+ ret[3][3] = _cos;
+ return ret;
+}
+
+G3D::Matrix4 Utils::GetTransformation( IDefinition def )
+{
+ G3D::Matrix4 translation;
+ if (def.Position.x == 0.0f && def.Position.y == 0.0f && def.Position.z == 0.0f)
+ translation = G3D::Matrix4::identity();
+ else
+ translation = G3D::Matrix4::translation(-(def.Position.z - Constants::MaxXY),
+ -(def.Position.x - Constants::MaxXY), def.Position.y);
+
+ G3D::Matrix4 rotation = RotationX(ToRadians(def.Rotation.z)) * RotationY(ToRadians(def.Rotation.x)) * RotationZ(ToRadians(def.Rotation.y + 180));
+ if (def.Scale() < 1.0f || def.Scale() > 1.0f)
+ return G3D::Matrix4::scale(def.Scale()) * rotation * translation;
+ return rotation * translation;
+}
+
+G3D::Matrix4 Utils::RotationY( float angle )
+{
+ float _cos = cos(angle);
+ float _sin = sin(angle);
+ Matrix4 ret = G3D::Matrix4::identity();
+ ret[1][1] = _cos;
+ ret[1][3] = -_sin;
+ ret[3][1] = _sin;
+ ret[3][3] = _cos;
+ return ret;
+}
+
+G3D::Matrix4 Utils::RotationZ( float angle )
+{
+ float _cos = cos(angle);
+ float _sin = sin(angle);
+ Matrix4 ret = G3D::Matrix4::identity();
+ ret[1][1] = _cos;
+ ret[1][2] = _sin;
+ ret[2][1] = -_sin;
+ ret[2][2] = _cos;
+ return ret;
+}
+
+float Utils::ToRadians( float degrees )
+{
+ return Constants::PI * degrees / 180.0f;
+}
+
+Vector3 Utils::VectorTransform( Vector3 vec, G3D::Matrix matrix )
+{
+ Vector3 ret;
+ ret.x = vec.x * matrix[1][1] + vec.y * matrix[2][1] + vec.z * matrix[3][1] + matrix[4][1];
+ ret.y = vec.x * matrix[1][2] + vec.y * matrix[2][2] + vec.z * matrix[3][2] + matrix[4][2];
+ ret.z = vec.x * matrix[1][3] + vec.y * matrix[2][3] + vec.z * matrix[3][3] + matrix[4][3];
+ return ret;
+}
+
+std::string Utils::GetPathBase( std::string path )
+{
+ int lastIndex = path.find_last_of(".");
+ if (lastIndex != std::string::npos)
+ return path.substr(0, lastindex);
+ return path;
+}
+
+Vector3 Vector3::Read( FILE* file )
+{
+ Vector3 ret;
+ fread(&ret, sizeof(Vector3), 1, file);
+ return ret;
+}
+
+Vector3 Utils::GetLiquidVert(G3D::Matrix4 transformation, Vector3 basePosition, float height, int x, int y)
+{
+ if (Utils::Distance(height, 0.0f) > 0.5f)
+ basePosition.z = 0.0f;
+ return Utils::VectorTransform(basePosition + Vector3(basePosition.x * Constants::UnitSize, basePosition.y * Constants::UnitSize, height), transformation);
+}
+
+float Utils::Distance( float x, float y )
+{
+ return sqrt(x*x + y*y);
+}
diff --git a/src/tools/mesh_extractor/Utils.h b/src/tools/mesh_extractor/Utils.h
new file mode 100644
index 00000000000..cda5f4c7cd8
--- /dev/null
+++ b/src/tools/mesh_extractor/Utils.h
@@ -0,0 +1,497 @@
+#ifndef UTILS_H
+#define UTILS_H
+#include <cstdio>
+#include <string>
+#include <sstream>
+
+#include "g3d/Matrix4.h"
+
+#include "Common.h"
+#include "Constants.h"
+
+struct Vector3
+{
+ Vector3() {}
+ Vector3(float X, float Y, float Z) : x(X), y(Y), z(Z) {}
+ float x;
+ float y;
+ float z;
+ Vector3 operator+(Vector3 right, Vector3 left)
+ {
+ return Vector3(right.x+left.x, right.y+left.y, right.z+left.z);
+ }
+ static Vector3 Read(FILE* file);
+};
+
+struct TilePos
+{
+ TilePos(int x, int y) : X(x), Y(y) {}
+ int X;
+ int Y;
+};
+
+template<typename T>
+struct Triangle
+{
+ Triangle(Constants::TriangleType type, T v0, T v1, T v2) : Type(type), V0(v0), V1(v1), V2(v2) {}
+ T V0;
+ T V1;
+ T V2;
+ Constants::TriangleType Type;
+};
+
+class MapChunkHeader
+{
+public:
+ MapChunkHeader() {}
+ uint32 Flags;
+ uint32 IndexX;
+ uint32 IndexY;
+ uint32 Layers;
+ uint32 DoodadRefs;
+ uint32 OffsetMCVT;
+ uint32 OffsetMCNR;
+ uint32 OffsetMCLY;
+ uint32 OffsetMCRF;
+ uint32 OffsetMCAL;
+ uint32 SizeMCAL;
+ uint32 OffsetMCSH;
+ uint32 SizeMCSH;
+ uint32 AreaId;
+ uint32 MapObjectRefs;
+ uint32 Holes;
+ uint32* LowQualityTextureMap;
+ uint32 PredTex;
+ uint32 NumberEffectDoodad;
+ uint32 OffsetMCSE;
+ uint32 SoundEmitters;
+ uint32 OffsetMCLQ;
+ uint32 SizeMCLQ;
+ Vector3 Position;
+ uint32 OffsetMCCV;
+
+ void Read(FILE* stream)
+ {
+ fread(&Flags, sizeof(uint32), 1, stream);
+ fread(&IndexX, sizeof(uint32), 1, stream);
+ fread(&IndexY, sizeof(uint32), 1, stream);
+ fread(&Layers, sizeof(uint32), 1, stream);
+ fread(&DoodadRefs, sizeof(uint32), 1, stream);
+ fread(&OffsetMCVT, sizeof(uint32), 1, stream);
+ fread(&OffsetMCNR, sizeof(uint32), 1, stream);
+ fread(&OffsetMCLY, sizeof(uint32), 1, stream);
+ fread(&OffsetMCRF, sizeof(uint32), 1, stream);
+ fread(&OffsetMCAL, sizeof(uint32), 1, stream);
+ fread(&SizeMCAL, sizeof(uint32), 1, stream);
+ fread(&OffsetMCSH, sizeof(uint32), 1, stream);
+ fread(&SizeMCSH, sizeof(uint32), 1, stream);
+ fread(&AreaId, sizeof(uint32), 1, stream);
+ fread(&MapObjectRefs, sizeof(uint32), 1, stream);
+ fread(&Holes, sizeof(uint32), 1, stream);
+ LowQualityTextureMap = new uint32[4];
+ fread(LowQualityTextureMap, sizeof(uint32), 4, stream);
+ fread(&PredTex, sizeof(uint32), 1, stream);
+ fread(&NumberEffectDoodad, sizeof(uint32), 1, stream);
+ fread(&OffsetMCSE, sizeof(uint32), 1, stream);
+ fread(&SoundEmitters, sizeof(uint32), 1, stream);
+ fread(&OffsetMCLQ, sizeof(uint32), 1, stream);
+ fread(&SizeMCLQ, sizeof(uint32), 1, stream);
+ Position = Vector3::Read(stream);
+ fread(&OffsetMCCV, sizeof(uint32), 1, stream);
+ }
+};
+
+class MHDR
+{
+public:
+ MHDR() {}
+ uint32 Flags;
+ uint32 OffsetMCIN;
+ uint32 OffsetMTEX;
+ uint32 OffsetMMDX;
+ uint32 OffsetMMID;
+ uint32 OffsetMWMO;
+ uint32 OffsetMWID;
+ uint32 OffsetMDDF;
+ uint32 OffsetMODF;
+ uint32 OffsetMFBO;
+ uint32 OffsetMH2O;
+ uint32 OffsetMTFX;
+
+ void Read(FILE* stream)
+ {
+ fread(&Flags, sizeof(uint32), 1, stream);
+ fread(&OffsetMCIN, sizeof(uint32), 1, stream);
+ fread(&OffsetMTEX, sizeof(uint32), 1, stream);
+ fread(&OffsetMMDX, sizeof(uint32), 1, stream);
+ fread(&OffsetMMID, sizeof(uint32), 1, stream);
+ fread(&OffsetMWMO, sizeof(uint32), 1, stream);
+ fread(&OffsetMWID, sizeof(uint32), 1, stream);
+ fread(&OffsetMDDF, sizeof(uint32), 1, stream);
+ fread(&OffsetMODF, sizeof(uint32), 1, stream);
+ fread(&OffsetMFBO, sizeof(uint32), 1, stream);
+ fread(&OffsetMH2O, sizeof(uint32), 1, stream);
+ fread(&OffsetMTFX, sizeof(uint32), 1, stream);
+ }
+};
+
+class ModelHeader
+{
+public:
+ char Magic[5];
+ uint32 Version;
+ uint32 LengthModelName;
+ uint32 OffsetName;
+ uint32 ModelFlags;
+ uint32 CountGlobalSequences;
+ uint32 OffsetGlobalSequences;
+ uint32 CountAnimations;
+ uint32 OffsetAnimations;
+ uint32 CountAnimationLookup;
+ uint32 OffsetAnimationLookup;
+ uint32 CountBones;
+ uint32 OffsetBones;
+ uint32 CountKeyBoneLookup;
+ uint32 OffsetKeyBoneLookup;
+ uint32 CountVertices;
+ uint32 OffsetVertices;
+ uint32 CountViews;
+ uint32 CountColors;
+ uint32 OffsetColors;
+ uint32 CountTextures;
+ uint32 OffsetTextures;
+ uint32 CountTransparency;
+ uint32 OffsetTransparency;
+ uint32 CountUvAnimation;
+ uint32 OffsetUvAnimation;
+ uint32 CountTexReplace;
+ uint32 OffsetTexReplace;
+ uint32 CountRenderFlags;
+ uint32 OffsetRenderFlags;
+ uint32 CountBoneLookup;
+ uint32 OffsetBoneLookup;
+ uint32 CountTexLookup;
+ uint32 OffsetTexLookup;
+ uint32 CountTexUnits;
+ uint32 OffsetTexUnits;
+ uint32 CountTransLookup;
+ uint32 OffsetTransLookup;
+ uint32 CountUvAnimLookup;
+ uint32 OffsetUvAnimLookup;
+ Vector3 VertexBox[2];
+ float VertexRadius;
+ Vector3 BoundingBox[2];
+ float BoundingRadius;
+ uint32 CountBoundingTriangles;
+ uint32 OffsetBoundingTriangles;
+ uint32 CountBoundingVertices;
+ uint32 OffsetBoundingVertices;
+ uint32 CountBoundingNormals;
+ uint32 OffsetBoundingNormals;
+
+ void Read(FILE* stream)
+ {
+ fread(&Magic, sizeof(char), 4, stream);
+ Magic[4] = '\0'; // null-terminate it.
+ fread(&Version, sizeof(uint32), 1, stream);
+ fread(&LengthModelName, sizeof(uint32), 1, stream);
+ fread(&OffsetName, sizeof(uint32), 1, stream);
+ fread(&ModelFlags, sizeof(uint32), 1, stream);
+ fread(&CountGlobalSequences, sizeof(uint32), 1, stream);
+ fread(&OffsetGlobalSequences, sizeof(uint32), 1, stream);
+ fread(&CountAnimations, sizeof(uint32), 1, stream);
+ fread(&OffsetAnimations, sizeof(uint32), 1, stream);
+ fread(&CountAnimationLookup, sizeof(uint32), 1, stream);
+ fread(&OffsetAnimationLookup, sizeof(uint32), 1, stream);
+ fread(&CountBones, sizeof(uint32), 1, stream);
+ fread(&OffsetBones, sizeof(uint32), 1, stream);
+ fread(&CountKeyBoneLookup, sizeof(uint32), 1, stream);
+ fread(&OffsetKeyBoneLookup, sizeof(uint32), 1, stream);
+ fread(&CountVertices, sizeof(uint32), 1, stream);
+ fread(&OffsetVertices, sizeof(uint32), 1, stream);
+ fread(&CountViews, sizeof(uint32), 1, stream);
+ fread(&CountColors, sizeof(uint32), 1, stream);
+ fread(&OffsetColors, sizeof(uint32), 1, stream);
+ fread(&CountTextures, sizeof(uint32), 1, stream);
+ fread(&OffsetTextures, sizeof(uint32), 1, stream);
+ fread(&CountTransparency, sizeof(uint32), 1, stream);
+ fread(&OffsetTransparency, sizeof(uint32), 1, stream);
+ fread(&CountUvAnimation, sizeof(uint32), 1, stream);
+ fread(&OffsetUvAnimation, sizeof(uint32), 1, stream);
+ fread(&CountTexReplace, sizeof(uint32), 1, stream);
+ fread(&OffsetTexReplace, sizeof(uint32), 1, stream);
+ fread(&CountRenderFlags, sizeof(uint32), 1, stream);
+ fread(&OffsetRenderFlags, sizeof(uint32), 1, stream);
+ fread(&CountBoneLookup, sizeof(uint32), 1, stream);
+ fread(&OffsetBoneLookup, sizeof(uint32), 1, stream);
+ fread(&CountTexLookup, sizeof(uint32), 1, stream);
+ fread(&OffsetTexLookup, sizeof(uint32), 1, stream);
+ fread(&CountTexUnits, sizeof(uint32), 1, stream);
+ fread(&OffsetTexUnits, sizeof(uint32), 1, stream);
+ fread(&CountTransLookup, sizeof(uint32), 1, stream);
+ fread(&OffsetTransLookup, sizeof(uint32), 1, stream);
+ fread(&CountUvAnimLookup, sizeof(uint32), 1, stream);
+ fread(&OffsetUvAnimLookup, sizeof(uint32), 1, stream);
+ fread(&CountColors, sizeof(uint32), 1, stream);
+ fread(&OffsetColors, sizeof(uint32), 1, stream);
+ fread(&CountTextures, sizeof(uint32), 1, stream);
+ fread(&OffsetTextures, sizeof(uint32), 1, stream);
+ fread(&CountTransparency, sizeof(uint32), 1, stream);
+ fread(&OffsetTransparency, sizeof(uint32), 1, stream);
+ fread(&CountUvAnimation, sizeof(uint32), 1, stream);
+ fread(&OffsetUvAnimation, sizeof(uint32), 1, stream);
+ fread(&CountTexReplace, sizeof(uint32), 1, stream);
+ fread(&OffsetTexReplace, sizeof(uint32), 1, stream);
+ VertexBox[0] = Vector3::Read(stream);
+ VertexBox[1] = Vector3::Read(stream);
+ fread(&VertexRadius, sizeof(float), 1, stream);
+ BoundingBox[0] = Vector3::Read(stream);
+ BoundingBox[1] = Vector3::Read(stream);
+ fread(&BoundingRadius, sizeof(float), 1, stream);
+ fread(&CountBoundingTriangles, sizeof(uint32), 1, stream);
+ fread(&OffsetBoundingTriangles, sizeof(uint32), 1, stream);
+ fread(&CountBoundingVertices, sizeof(uint32), 1, stream);
+ fread(&OffsetBoundingVertices, sizeof(uint32), 1, stream);
+ fread(&CountBoundingNormals, sizeof(uint32), 1, stream);
+ fread(&OffsetBoundingNormals, sizeof(uint32), 1, stream);
+ }
+};
+
+class WorldModelHeader
+{
+public:
+ WorldModelHeader() {}
+ uint32 CountMaterials;
+ uint32 CountGroups;
+ uint32 CountPortals;
+ uint32 CountLights;
+ uint32 CountModels;
+ uint32 CountDoodads;
+ uint32 CountSets;
+ uint32 AmbientColorUnk;
+ uint32 WmoId;
+ Vector3 BoundingBox[2];
+ uint32 LiquidTypeRelated;
+
+ static WorldModelHeader Read(FILE* stream)
+ {
+ WorldModelHeader ret;
+ fread(&ret.CountMaterials, sizeof(uint32), 1, stream);
+ fread(&ret.CountGroups, sizeof(uint32), 1, stream);
+ fread(&ret.CountPortals, sizeof(uint32), 1, stream);
+ fread(&ret.CountLights, sizeof(uint32), 1, stream);
+ fread(&ret.CountModels, sizeof(uint32), 1, stream);
+ fread(&ret.CountDoodads, sizeof(uint32), 1, stream);
+ fread(&ret.CountSets, sizeof(uint32), 1, stream);
+ fread(&ret.AmbientColorUnk, sizeof(uint32), 1, stream);
+ fread(&ret.WmoId, sizeof(uint32), 1, stream);
+ ret.BoundingBox[0] = Vector3::Read(stream);
+ ret.BoundingBox[1] = Vector3::Read(stream);
+ fread(&ret.LiquidTypeRelated, sizeof(uint32), 1, stream);
+ return ret;
+ }
+};
+
+class DoodadInstance
+{
+public:
+ DoodadInstance() {}
+ uint32 FileOffset;
+ std::string File;
+ Vector3 Position;
+ float QuatW;
+ float QuatX;
+ float QuatY;
+ float QuatZ;
+ float Scale;
+ uint32 LightColor;
+
+ static DoodadInstance Read(FILE* stream)
+ {
+ DoodadInstance ret;
+ fread(&ret.FileOffset, sizeof(uint32), 1, stream);
+ ret.Position = Vector3::Read(stream);
+ fread(&ret.QuatW, sizeof(float), 1, stream);
+ fread(&ret.QuatX, sizeof(float), 1, stream);
+ fread(&ret.QuatY, sizeof(float), 1, stream);
+ fread(&ret.QuatZ, sizeof(float), 1, stream);
+ fread(&ret.Scale, sizeof(float), 1, stream);
+ fread(&ret.LightColor, sizeof(uint32), 1, stream);
+ return ret;
+ }
+};
+
+class DoodadSet
+{
+public:
+ DoodadSet() {}
+ std::string Name;
+ uint32 FirstInstanceIndex;
+ uint32 CountInstances;
+ uint32 UnknownZero;
+
+ static DoodadSet Read(FILE* stream)
+ {
+ DoodadSet ret;
+ char name[21];
+ fread(&name, sizeof(char), 20, stream);
+ name[20] = '\0';
+ ret.Name = name;
+ fread(&ret.FirstInstanceIndex, sizeof(uint32), 1, stream);
+ fread(&ret.CountInstances, sizeof(uint32), 1, stream);
+ fread(&ret.UnknownZero, sizeof(uint32), 1, stream);
+ return ret;
+ }
+}
+
+class LiquidHeader
+{
+public:
+ LiquidHeader() {}
+ uint32 CountXVertices;
+ uint32 CountYVertices;
+ uint32 Width;
+ uint32 Height;
+ Vector3 BaseLocation;
+ uint16 MaterialId;
+
+ static LiquidHeader Read(FILE* stream)
+ {
+ LiquidHeader ret;
+ fread(&ret.CountXVertices, sizeof(uint32), 1, stream);
+ fread(&ret.CountYVertices, sizeof(uint32), 1, stream);
+ fread(&ret.Width, sizeof(uint32), 1, stream);
+ fread(&ret.Height, sizeof(uint32), 1, stream);
+ ret.BaseLocation = Vector3::Read(stream);
+ fread(&ret.MaterialId, sizeof(uint16), 1, stream);
+ return ret;
+ }
+};
+
+class LiquidData
+{
+public:
+ LiquidData() {}
+ float** HeightMap;
+ uint8** RenderFlags;
+
+ bool ShouldRender(int x, int y)
+ {
+ return RenderFlags[x][y] != 0x0F;
+ }
+
+ public static LiquidData Read(FILE* stream, LiquidHeader header)
+ {
+ LiquidData ret;
+ ret.HeightMap = new float*[header.CountXVertices];
+ for (int i = 0; i < header.CountXVertices; ++i)
+ ret.HeightMap[i] = new float[header.CountYVertices];
+
+ ret.RenderFlags = new uint8*[header.Width];
+ for (int i = 0; i < header.Width; ++i)
+ ret.RenderFlags[i] = new uint8[header.Height];
+
+ for (int y = 0; y < header.CountYVertices; y++)
+ {
+ for (int x = 0; x < header.CountXVertices; x++)
+ {
+ uint32 discard;
+ fread(&discard, sizeof(uint32), 1, stream);
+ float tmp;
+ fread(&tmp, sizeof(float), 1, stream);
+ ret.HeightMap[x][y] = tmp;
+ }
+ }
+
+ for (int y = 0; y < header.Height; y++)
+ {
+ for (int x = 0; x < header.Width; x++)
+ {
+ uint8 tmp;
+ fread(&tmp, sizeof(uint8), 1, stream);
+ ret.RenderFlags[x][y] = tmp;
+ }
+ }
+
+ return ret;
+ }
+};
+
+class H2ORenderMask
+{
+public:
+ H2ORenderMask() {}
+ uint8 Mask[8];
+
+ bool ShouldRender(int x, int y)
+ {
+ return (Mask[y] >> x & 1) != 0;
+ }
+
+ static H2ORenderMask Read(FILE* stream)
+ {
+ H2ORenderMask ret;
+ fread(&ret.Mask, sizeof(uint8), 8, stream);
+ return ret;
+ }
+};
+
+class MCNKLiquidData
+{
+public:
+ MCNKLiquidData() {}
+ const float MaxStandableHeight = 1.5f;
+
+ float** Heights;
+ H2ORenderMask* Mask;
+
+ bool IsWater(int x, int y, float height)
+ {
+ if (!Heights || !Mask)
+ return false;
+ if (!Mask->ShouldRender(x, y))
+ return false;
+ float diff = Heights[x][y] - height;
+ if (diff > MaxStandableHeight)
+ return true;
+ return false;
+ }
+};
+
+// Dummy class to act as an interface.
+class IDefinition
+{
+public:
+ Vector3 Position;
+ Vector3 Rotation;
+ virtual float Scale() = 0;
+};
+
+class Utils
+{
+public:
+ static void Reverse(char word[]);
+ static std::string ReadString(FILE* file);
+ static uint32 Size(FILE* file);
+ static Vector3 ToRecast( Vector3 val );
+ static std::string GetAdtPath(std::string world, int x, int y);
+ static std::string FixModelPath(std::string path);
+ static G3D::Matrix4 GetTransformation(IDefinition def);
+ /// They say its better to declare template functions in the header files.
+ template <typename T>
+ static std::string ToString(T val)
+ {
+ std::stringstream ss;
+ ss << val;
+ return ss.str();
+ }
+ static G3D::Matrix4 RotationX(float angle);
+ static G3D::Matrix4 RotationY(float angle);
+ static G3D::Matrix4 RotationZ(float angle);
+ static float ToRadians(float degrees);
+ static Vector3 VectorTransform(Vector3 vec, G3D::Matrix matrix);
+ static std::string GetPathBase(std::string path);
+ static Vector3 GetLiquidVert(G3D::Matrix4 transformation, Vector3 basePosition, float height, int x, int y);
+ static float Distance(float x, float y);
+};
+#endif \ No newline at end of file
diff --git a/src/tools/mesh_extractor/WDT.cpp b/src/tools/mesh_extractor/WDT.cpp
new file mode 100644
index 00000000000..c06d8ac9677
--- /dev/null
+++ b/src/tools/mesh_extractor/WDT.cpp
@@ -0,0 +1,55 @@
+#include "WDT.h"
+#include "Chunk.h"
+#include "ChunkedData.h"
+#include "Utils.h"
+#include "WorldModelHandler.h"
+
+WDT::WDT(std::string file) : IsValid(false), IsGlobalModel(false)
+{
+ Data = new ChunkedData(file, 2);
+ ReadTileTable();
+ ReadGlobalModel();
+}
+
+void WDT::ReadGlobalModel()
+{
+ Chunk* fileChunk = Data->GetChunkByName("MWMO");
+ Chunk* defChunk = Data->GetChunkByName("MODF");
+ if (!fileChunk || !defChunk)
+ return;
+
+ IsGlobalModel = true;
+ ModelDefinition = WorldModelDefinition::Read(defChunk->GetStream());
+ ModelFile = Utils::ReadString(fileChunk->GetStream());
+}
+
+void WDT::ReadTileTable()
+{
+ Chunk* chunk = Data->GetChunkByName("MAIN");
+ if (!chunk)
+ return;
+ IsValid = true;
+ FILE* stream = chunk->GetStream();
+ for (int y = 0; y < 64; ++y)
+ {
+ for (int x = 0; x < 64; ++x)
+ {
+ const uint32 hasTileFlag = 0x1;
+ uint32 flags;
+ uint32 discard;
+ fread(&flags, sizeof(uint32), 1, stream);
+ fread(&discard, sizeof(uint32), 1, stream);
+ if (flags & hasTileFlag)
+ TileTable.push_back(TilePos(x, y));
+
+ }
+ }
+}
+
+bool WDT::HasTile( int x, int y )
+{
+ for (std::vector<TilePos>::iterator itr = TileTable.begin(); itr != TileTable.end(); ++itr)
+ if (itr->X == x && itr->Y == y)
+ return true;
+ return false;
+}
diff --git a/src/tools/mesh_extractor/WDT.h b/src/tools/mesh_extractor/WDT.h
new file mode 100644
index 00000000000..a12aa65218b
--- /dev/null
+++ b/src/tools/mesh_extractor/WDT.h
@@ -0,0 +1,27 @@
+#ifndef WDT_H
+#define WDT_H
+#include <string>
+#include <vector>
+
+#include "ChunkedData.h"
+#include "WorldModelHandler.h"
+#include "Utils.h"
+
+class WDT
+{
+public:
+ WDT(std::string file);
+
+ ChunkedData* Data;
+ std::vector<TilePos> TileTable;
+ bool IsGlobalModel;
+ bool IsValid;
+ std::string ModelFile;
+ WorldModelDefinition ModelDefinition;
+ bool HasTile(int x, int y);
+private:
+ void ReadGlobalModel();
+ void ReadTileTable();
+};
+
+#endif \ No newline at end of file
diff --git a/src/tools/mesh_extractor/WorldModelGroup.cpp b/src/tools/mesh_extractor/WorldModelGroup.cpp
new file mode 100644
index 00000000000..308c7ff7b86
--- /dev/null
+++ b/src/tools/mesh_extractor/WorldModelGroup.cpp
@@ -0,0 +1,122 @@
+#include "WorldModelGroup.h"
+#include "ChunkedData.h"
+#include "Chunk.h"
+
+WorldModelGroup::WorldModelGroup( std::string path, int groupIndex ) : GroupIndex(groupIndex), IsBad(false)
+{
+ Data = new ChunkedData(path);
+ if (!Data->Stream)
+ {
+ IsBad = true;
+ return;
+ }
+ Chunk* mainChunk = Data->GetChunkByName("MOGP");
+ int32 firstSub = mainChunk->FindSubChunkOffset("MOPY");
+ if (firstSub == -1)
+ return;
+ FILE* stream = mainChunk->GetStream();
+ fseek(stream, firstSub, SEEK_SET);
+ SubData = new ChunkedData(stream, mainChunk->Length - firstSub);
+
+ ReadBoundingBox();
+ ReadMaterials();
+ ReadTriangles();
+ ReadVertices();
+ ReadNormals();
+ ReadLiquid();
+}
+
+void WorldModelGroup::ReadNormals()
+{
+ Chunk* chunk = SubData->GetChunkByName("MONR");
+ if (!chunk)
+ return;
+
+ uint32 normalCount = chunk->Length / 12;
+ Assert(normalCount == Vertices.size(), "normalCount is different than the Vertices count");
+ Normals.reserve(normalCount);
+ FILE* stream = chunk->GetStream();
+ for (int i = 0; i < normalCount; i++)
+ Normals[i] = Vector3::Read(stream);
+}
+
+void WorldModelGroup::ReadLiquid()
+{
+ Chunk* chunk = SubData->GetChunkByName("MLIQ");
+ if (!chunk)
+ return;
+
+ HasLiquidData = true;
+ FILE* stream = chunk->GetStream();
+ LiquidDataHeader = LiquidHeader.Read(stream);
+ LiquidDataGeometry = LiquidData.Read(stream, LiquidDataHeader);
+}
+
+void WorldModelGroup::ReadVertices()
+{
+ Chunk* chunk = SubData->GetChunkByName("MOVT");
+ if (!chunk)
+ return;
+
+ uint32 verticeCount = chunk.Length / 12;
+ Vertices.reserve(verticeCount);
+ FILE* stream = chunk->GetStream();
+ for (int i = 0; i < verticeCount; i++)
+ Vertices.push_back(Vector3::Read(stream));
+}
+
+void WorldModelGroup::ReadTriangles()
+{
+ Chunk* chunk = SubData->GetChunkByName("MOVI");
+ if (!chunk)
+ return;
+
+ uint32 triangleCount = chunk.Length / 6;
+ Assert(triangleCount == TriangleFlags.size(), "triangleCount != TriangleFlags.size()");
+ FILE* stream = chunk.GetStream();
+ Triangles.reserve(triangleCount);
+ for (int i = 0; i < triangleCount; i++)
+ {
+ uint16 v0;
+ uint16 v1;
+ uint16 v2;
+ fread(&v0, sizeof(uint16), 1, stream);
+ fread(&v1, sizeof(uint16), 1, stream);
+ fread(&v2, sizeof(uint16), 1, stream);
+ Triangles.push_back(Triangle<uint16>(Constants::TRIANGLE_TYPE_WMO, v0, v1, v2));
+ }
+}
+
+void WorldModelGroup::ReadMaterials()
+{
+ Chunk* chunk = SubData->GetChunkByName("MOPY");
+ if (!chunk)
+ return;
+
+ FILE* stream = chunk->GetStream();
+ uint32 triangleCount = chunk.Length / 2;
+ TriangleFlags.reserve(triangleCount);
+ TriangleMaterials.reserve(triangleCount);
+ for (int i = 0; i < triangleCount; i++)
+ {
+ uint8 tmp;
+ fread(&tmp, sizeof(uint8), 1, stream);
+ TriangleFlags.push_back(tmp);
+ // Read again for material.
+ fread(&tmp, sizeof(uint8), 1, stream);
+ TriangleMaterials.push_back(tmp);
+ }
+}
+
+void WorldModelGroup::ReadBoundingBox()
+{
+ Chunk* chunk = Data->GetChunkByName("MOGP");
+ if (!chunk)
+ return;
+
+ FILE* stream = chunk->GetStream();
+ fseek(stream, 8, SEEK_CUR):
+ fread(&Flags, sizeof(uint32), 1, stream);
+ BoundingBox[0] = Vector3::Read(stream);
+ BoundingBox[1] = Vector3::Read(stream);
+}
diff --git a/src/tools/mesh_extractor/WorldModelGroup.h b/src/tools/mesh_extractor/WorldModelGroup.h
new file mode 100644
index 00000000000..1dc754d0221
--- /dev/null
+++ b/src/tools/mesh_extractor/WorldModelGroup.h
@@ -0,0 +1,33 @@
+#ifndef WMOGROUP_H
+#define WMOGROUP_H
+#include "ChunkedData.h"
+#include "Utils.h"
+
+class WorldModelGroup
+{
+public:
+ WorldModelGroup(std::string path, int groupIndex);
+ ChunkedData* Data;
+ ChunkedData* SubData;
+ int GroupIndex;
+ Vector3 BoundingBox[2];
+ uint32 Flags;
+ std::vector<uint8> TriangleFlags;
+ std::vector<uint8> TriangleMaterials;
+ std::vector<Triangle<uint16> > Triangles;
+ std::vector<Vector3> Vertices;
+ std::vector<Vector3> Normals;
+
+ bool HasLiquidData;
+ bool IsBad;
+ LiquidHeader LiquidDataHeader;
+ LiquidData LiquidDataGeometry;
+private:
+ void ReadNormals();
+ void ReadLiquid();
+ void ReadVertices();
+ void ReadTriangles();
+ void ReadMaterials();
+ void ReadBoundingBox();
+};
+#endif \ No newline at end of file
diff --git a/src/tools/mesh_extractor/WorldModelHandler.cpp b/src/tools/mesh_extractor/WorldModelHandler.cpp
new file mode 100644
index 00000000000..e895e2bfde8
--- /dev/null
+++ b/src/tools/mesh_extractor/WorldModelHandler.cpp
@@ -0,0 +1,190 @@
+#include "WorldModelHandler.h"
+#include "Chunk.h"
+#include "Cache.h"
+#include "Common.h"
+#include "g3d/Matrix4.h"
+#include <cstdio>
+
+WorldModelDefinition WorldModelDefinition::Read( FILE* file )
+{
+ WorldModelDefinition ret;
+ fread(&ret.MwidIndex, sizeof(uint32), 1, file);
+ fread(&ret.UniqueId, sizeof(uint32), 1, file);
+ ret.Position = Vector3::Read(file);
+ ret.Rotation = Vector3::Read(file);
+ ret.UpperExtents = Vector3::Read(file);
+ ret.LowerExtents = Vector3::Read(file);
+ fread(&ret.Flags, sizeof(uint16), 1, file);
+ fread(&ret.DooadSet, sizeof(uint16), 1, file);
+ uint32 discard;
+ fread(&discard, sizeof(uint32), 1, file);
+ return ret;
+}
+
+
+WorldModelHandler::WorldModelHandler( ADT* adt ) : ObjectDataHandler(adt)
+{
+ if (!adt->HasObjectData)
+ return;
+ ReadModelPaths();
+ ReadDefinitions();
+}
+
+void WorldModelHandler::ProcessInternal( ChunkedData* subChunks )
+{
+ if (!IsSane())
+ return;
+ Chunk* wmoReferencesChunk = subChunks->GetChunkByName("MCRW");
+ if (!wmoReferencesChunk)
+ return;
+ FILE* stream = wmoReferencesChunk->GetStream();
+ uint32 refCount = wmoReferencesChunk->Length / 4;
+ for (int i = 0; i < refCount; i++)
+ {
+ int32 index;
+ fread(&index, sizeof(int32), 1, stream);
+
+ if (index < 0 || index >= _definitions->size())
+ continue;
+
+ WorldModelDefinition wmo = (*_definitions)[index];
+
+ if (_drawn.find(wmo.UniqueId) != _drawn.end())
+ continue;
+ _drawn.insert(wmo.UniqueId);
+
+ if (wmo.MwidIndex >= _paths->size())
+ continue;
+
+ std::string path = _paths[(int) wmo.MwidIndex];
+ WorldModelRoot* model = Cache->WorldModelCache.Get(path);
+ if (!model)
+ {
+ model = new WorldModelRoot(path);
+ Cache->WorldModelCache.Insert(path, model);
+ }
+
+ Vertices.reserve(1000);
+ Triangles.reserve(1000);
+
+ InsertModelGeometry(Vertices, Triangles, wmo, model);
+ }
+}
+
+void WorldModelHandler::InsertModelGeometry( std::vector<Vector3> verts, std::vector<Triangle<uint32> > tris, WorldModelDefinition& def, WorldModelRoot* root )
+{
+ G3D::Matrix4 transformation = Utils::GetTransformation();
+ for (std::vector<WorldModelGroup>::iterator group = root->Groups.begin(); group != root->Groups.end(); ++group)
+ {
+ uint32 vertOffset = verts.size();
+ for (std::vector<Vector3>::iterator itr2 = group->Vertices.begin(); itr2 != group->Vertices.end(); ++itr2)
+ verts.push_back(Utils::VectorTransform(*itr2, transformation));
+
+ for (int i = 0; i < group->Triangles.size(); ++i)
+ {
+ // only include collidable tris
+ if ((group->TriangleFlags[i] & 0x04) != 0 && group->TriangleMaterials[i] != 0xFF)
+ continue;
+ Triangle<uint16> tri = group->Triangles[i];
+ tris.push_back(Triangle<uint32>(Constants::TRIANGLE_TYPE_WMO, tri.V0 + vertOffset, tri.V1 + vertOffset, tri.V2 + vertOffset));
+ }
+ }
+
+ if (def.DoodadSet >= 0 && def.DoodadSet < root->DoodadSets.size())
+ {
+ DoodadSet set = root->DoodadSets[def.DoodadSet];
+ std::vector<DoodadInstance> instances = new std::vector<DoodadInstance>;
+ instances.reserve(set.CountInstances);
+ for (uint32 i = set.FirstInstanceIndex; i < (set.CountInstances + set.FirstInstanceIndex); i++)
+ {
+ if (i >= root->DoodadInstances.size())
+ break;
+ instances.push_back(root->DoodadInstances[i]);
+ }
+
+ for (std::vector<DoodadInstance>::iterator instance = instances.begin(); instance != instances.end(); ++instance)
+ {
+ Model* model = Cache.ModelCache.Get(instance->File);
+ if (!model)
+ {
+ model = new Model(instance->File);
+ Cache.ModelCache.Insert(instance->File, model);
+ }
+
+ if (!model.IsCollidable)
+ continue;
+ G3D::Matrix4 doodadTransformation = Utils::GetWmoDoodadTransformation(instance, def);
+ int vertOffset = verts.size();
+ for (std::vector<Vector3>::iterator itr2 = model->Vertices.begin(); itr2 != model->Vertices.end(); ++itr2)
+ verts.push_back(Utils::VectorTransform(*itr2, doodadTransformation));
+ for (std::vector<Triangle<uint16> >::iterator itr2 = model->Triangles.begin(); itr2 != model->Triangles.end(); ++itr2)
+ tris.push_back(Triangle<uint32>(Constants::TRIANGLE_TYPE_WMO, itr2->V0 + vertOffset, itr2->V1 + vertOffset, itr2->V2 + vertOffset));
+ }
+
+ for (std::vector<WorldModelGroup>::iterator group = root->Groups.begin(); group != root->Groups.end(); ++group)
+ {
+ if (!group->HasLiquidData)
+ continue;
+
+ for (int y = 0; y < group->LiquidDataHeader.Height; y++)
+ {
+ for (int x = 0; x < group->LiquidDataHeader.Width; x++)
+ {
+ if (!group->LiquidDataGeometry.ShouldRender(x, y))
+ continue;
+
+ uint32 vertOffset = verts.size();
+ verts.push_back(Utils::GetLiquidVert(transformation, group->LiquidDataHeader.BaseLocation,
+ group->LiquidDataGeometry.HeightMap[x][y], x, y));
+ verts.push_back(Utils::GetLiquidVert(transformation, group->LiquidDataHeader.BaseLocation,
+ group->LiquidDataGeometry.HeightMap[x + 1][y], x + 1, y));
+ verts.push_back(Utils::GetLiquidVert(transformation, group->LiquidDataHeader.BaseLocation,
+ group->LiquidDataGeometry.HeightMap[x][y + 1], x, y + 1));
+ verts.push_back(Utils::GetLiquidVert(transformation, group->LiquidDataHeader.BaseLocation,
+ group->LiquidDataGeometry.HeightMap[x + 1][y + 1], x + 1, y + 1));
+
+ tris.push_back(Triangle<uint32>(Constants::TRIANGLE_TYPE_WATER, vertOffset, vertOffset + 2, vertOffset + 1));
+ tris.push_back(new Triangle<uint32>(Constants::TRIANGLE_TYPE_WATER, vertOffset + 2, vertOffset + 3, vertOffset + 1));
+
+ }
+ }
+ }
+ }
+}
+
+void WorldModelHandler::ReadDefinitions()
+{
+ Chunk* chunk = Source->ObjectData->GetChunkByName("MODF");
+ if (!chunk)
+ return;
+
+ const int32 definitionSize = 64;
+ uint32 definitionCount = chunk.Length / definitionSize;
+ _definitions = new new std::vector<WorldModelDefinition>;
+ _definitions->reserve(definitionCount);
+ FILE* stream = chunk->GetStream();
+ for (int i = 0; i < definitionCount; i++)
+ _definitions->push_back(WorldModelDefinition::Read(stream));
+}
+
+void WorldModelHandler::ReadModelPaths()
+{
+ Chunk* mwid = Source->ObjectData->GetChunkByName("MWID");
+ Chunk* mwmo = Source->ObjectData->GetChunkByName("MWMO");
+ if (!mwid || !mwmo)
+ return;
+
+ uint32 paths = mwid.Length / 4;
+ _paths = new std::vector<std::string>;
+ _paths->reserve(paths);
+ for (int i = 0; i < paths; i++)
+ {
+ FILE* stream = mwid->GetStream();
+ fseek(stream, i * 4, SEEK_CUR);
+ uint32 offset;
+ fread(&offset, sizeof(uint32), 1, stream);
+ FILE* dataStream = mwmo->GetStream();
+ fseek(dataStream, offset + mwmo->Offset, SEEK_SET);
+ _paths->push_back(Utils::ReadString(dataStream));
+ }
+}
diff --git a/src/tools/mesh_extractor/WorldModelHandler.h b/src/tools/mesh_extractor/WorldModelHandler.h
new file mode 100644
index 00000000000..2236339de9d
--- /dev/null
+++ b/src/tools/mesh_extractor/WorldModelHandler.h
@@ -0,0 +1,42 @@
+#ifndef WMODEL_HNDL_H
+#define WMODEL_HNDL_H
+#include "Common.h"
+#include "Utils.h"
+#include "ObjectDataHandler.h"
+#include "ADT.h"
+
+#include <set>
+#include <vector>
+
+struct WorldModelDefinition : IDefinition
+{
+ WorldModelDefinition() {}
+ uint32 MwidIndex;
+ uint32 UniqueId;
+ Vector3 UpperExtents;
+ Vector3 LowerExtents;
+ uint16 Flags;
+ uint16 DoodadSet;
+ float Scale() { return 1.0f; }
+ static WorldModelDefinition Read(FILE* file);
+};
+
+class WorldModelHandler : public ObjectDataHandler
+{
+public:
+ WorldModelHandler(ADT* adt);
+
+ std::vector<Vector3> Vertices;
+ std::vector<Triangle<uint32> > Triangles;
+ bool IsSane() { return _definitions && _paths; }
+ void InsertModelGeometry(std::vector<Vector3> verts, std::vector<Triangle<uint32> > tris, WorldModelDefinition& def, WorldModelRoot* root);
+protected:
+ void ProcessInternal(ChunkedData* data);
+private:
+ void ReadDefinitions();
+ void ReadModelPaths();
+ std::set<uint32> _drawn;
+ std::vector<WorldModelDefinition>* _definitions;
+ std::vector<std::string>* _paths;
+};
+#endif \ No newline at end of file
diff --git a/src/tools/mesh_extractor/WorldModelRoot.cpp b/src/tools/mesh_extractor/WorldModelRoot.cpp
new file mode 100644
index 00000000000..9764e339242
--- /dev/null
+++ b/src/tools/mesh_extractor/WorldModelRoot.cpp
@@ -0,0 +1,74 @@
+#include "WorldModelRoot.h"
+#include "ChunkedData.h"
+#include "Utils.h"
+
+WorldModelRoot::WorldModelRoot( std::string path )
+{
+ Data = new ChunkedData(path);
+ Path = path;
+ ReadHeader();
+ ReadGroups();
+ ReadDoodadInstances();
+ ReadDoodadSets();
+}
+
+void WorldModelRoot::ReadGroups()
+{
+ std::string pathBase = Utils::GetPathBase(Path);
+ Groups.reserve(Header.CountGroups);
+ for (int i = 0; i < Header.CountGroups; i++)
+ {
+ char name[200];
+ sprintf(name, "%s_%03u.wmo", pathBase.c_str(), i);
+ WorldModelGroup group(name, i);
+ if (!group.IsBad)
+ Groups.push_back(group);
+ }
+}
+
+void WorldModelRoot::ReadDoodadSets()
+{
+ Chunk* chunk = Data->GetChunkByName("MODS");
+ if (!chunk)
+ return;
+
+ FILE* stream = chunk->GetStream();
+ Assert(chunk.Length / 32 == Header.CountSets, "chunk.Length / 32 == Header.CountSets");
+ DoodadSets.reserve(Header.CountSets);
+ for (int i = 0; i < Header.CountSets; i++)
+ DoodadSets.push_back(DoodadSet::Read(stream));
+}
+
+void WorldModelRoot::ReadDoodadInstances()
+{
+ Chunk* chunk = Data->GetChunkByName("MODD");
+ Chunk* nameChunk = Data->GetChunkByName("MODN");
+ if (!chunk || !nameChunk)
+ return;
+
+ const uint32 instanceSize = 40;
+ uint32 countInstances = chunk->Length / instanceSize;
+ DoodadInstances.reserve(countInstances);
+ for (int i = 0; i < countInstances; i++)
+ {
+ FILE* stream = chunk->GetStream();
+ fseek(stream, instanceSize * i, SEEK_CUR);
+ DoodadInstance instance = DoodadInstance::Read(stream);
+ FILE* nameStream = nameChunk->GetStream();
+ if (instance.FileOffset >= nameChunk->Length)
+ continue;
+ fseek(nameStream, instance.FileOffset, SEEK_CUR);
+ instance.File = Utils::ReadString(nameStream);
+ DoodadInstances.push_back(instance);
+ }
+}
+
+void WorldModelRoot::ReadHeader()
+{
+ Chunk* chunk = Data->GetChunkByName("MOHD");
+ if (!chunk)
+ return;
+
+ FILE* stream = chunk->GetStream();
+ Header = WorldModelHeader::Read(stream);
+}
diff --git a/src/tools/mesh_extractor/WorldModelRoot.h b/src/tools/mesh_extractor/WorldModelRoot.h
new file mode 100644
index 00000000000..c06ff3d5d2b
--- /dev/null
+++ b/src/tools/mesh_extractor/WorldModelRoot.h
@@ -0,0 +1,26 @@
+#ifndef WMOROOT_H
+#define WMOROOT_H
+#include <string>
+#include <vector>
+
+#include "ChunkedData.h"
+#include "Utils.h"
+#include "WorldModelGroup.h"
+
+class WorldModelRoot
+{
+public:
+ WorldModelRoot(std::string path);
+ std::string Path;
+ ChunkedData* Data;
+ WorldModelHeader Header;
+ std::vector<DoodadInstance> DoodadInstances;
+ std::vector<DoodadSet> DoodadSets;
+ std::vector<WorldModelGroup> Groups;
+private:
+ void ReadGroups();
+ void ReadDoodadSets();
+ void ReadDoodadInstances();
+ void ReadHeader();
+};
+#endif \ No newline at end of file
diff --git a/src/tools/mesh_extractor/readme b/src/tools/mesh_extractor/readme
new file mode 100644
index 00000000000..85cd7cfc975
--- /dev/null
+++ b/src/tools/mesh_extractor/readme
@@ -0,0 +1,6 @@
+Experimental mesh extractor.
+Original work in C# by stschake
+Thanks to:
+Subv
+~
+For helping in the porting to C++ \ No newline at end of file