aboutsummaryrefslogtreecommitdiff
path: root/src/tools
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools')
-rw-r--r--src/tools/CMakeLists.txt2
-rw-r--r--src/tools/map_extractor/System.cpp42
-rw-r--r--src/tools/map_extractor/adt.cpp21
-rw-r--r--src/tools/map_extractor/loadlib.cpp4
-rw-r--r--src/tools/map_extractor/loadlib/loadlib.h6
-rw-r--r--src/tools/map_extractor/wdt.cpp12
-rw-r--r--src/tools/mesh_extractor/ADT.cpp53
-rw-r--r--src/tools/mesh_extractor/ADT.h29
-rw-r--r--src/tools/mesh_extractor/CMakeLists.txt81
-rw-r--r--src/tools/mesh_extractor/Cache.h62
-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.cpp74
-rw-r--r--src/tools/mesh_extractor/ChunkedData.h21
-rw-r--r--src/tools/mesh_extractor/Constants.h59
-rw-r--r--src/tools/mesh_extractor/ContinentBuilder.cpp144
-rw-r--r--src/tools/mesh_extractor/ContinentBuilder.h30
-rw-r--r--src/tools/mesh_extractor/DBC.cpp70
-rw-r--r--src/tools/mesh_extractor/DBC.h52
-rw-r--r--src/tools/mesh_extractor/DoodadHandler.cpp109
-rw-r--r--src/tools/mesh_extractor/DoodadHandler.h57
-rw-r--r--src/tools/mesh_extractor/Geometry.cpp123
-rw-r--r--src/tools/mesh_extractor/Geometry.h23
-rw-r--r--src/tools/mesh_extractor/LiquidHandler.cpp102
-rw-r--r--src/tools/mesh_extractor/LiquidHandler.h21
-rw-r--r--src/tools/mesh_extractor/MPQ.cpp118
-rw-r--r--src/tools/mesh_extractor/MPQ.h90
-rw-r--r--src/tools/mesh_extractor/MPQManager.cpp109
-rw-r--r--src/tools/mesh_extractor/MPQManager.h34
-rw-r--r--src/tools/mesh_extractor/MapChunk.cpp74
-rw-r--r--src/tools/mesh_extractor/MapChunk.h24
-rw-r--r--src/tools/mesh_extractor/MeshExtractor.cpp430
-rw-r--r--src/tools/mesh_extractor/Model.cpp67
-rw-r--r--src/tools/mesh_extractor/Model.h23
-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.cpp311
-rw-r--r--src/tools/mesh_extractor/TileBuilder.h30
-rw-r--r--src/tools/mesh_extractor/Utils.cpp568
-rw-r--r--src/tools/mesh_extractor/Utils.h377
-rw-r--r--src/tools/mesh_extractor/WDT.cpp60
-rw-r--r--src/tools/mesh_extractor/WDT.h27
-rw-r--r--src/tools/mesh_extractor/WorldModelGroup.cpp143
-rw-r--r--src/tools/mesh_extractor/WorldModelGroup.h38
-rw-r--r--src/tools/mesh_extractor/WorldModelHandler.cpp204
-rw-r--r--src/tools/mesh_extractor/WorldModelHandler.h47
-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
-rw-r--r--src/tools/mmaps_generator/CMakeLists.txt68
-rw-r--r--src/tools/mmaps_generator/Info/readme.txt66
-rw-r--r--src/tools/mmaps_generator/IntermediateValues.cpp277
-rw-r--r--src/tools/mmaps_generator/IntermediateValues.h53
-rw-r--r--src/tools/mmaps_generator/MapBuilder.cpp977
-rw-r--r--src/tools/mmaps_generator/MapBuilder.h154
-rw-r--r--src/tools/mmaps_generator/PathCommon.h128
-rw-r--r--src/tools/mmaps_generator/PathGenerator.cpp297
-rw-r--r--src/tools/mmaps_generator/TerrainBuilder.cpp880
-rw-r--r--src/tools/mmaps_generator/TerrainBuilder.h137
-rw-r--r--src/tools/mmaps_generator/VMapExtensions.cpp72
-rw-r--r--src/tools/vmap4_extractor/adtfile.cpp3
-rw-r--r--src/tools/vmap4_extractor/model.cpp4
-rw-r--r--src/tools/vmap4_extractor/vmapexport.cpp3
-rw-r--r--src/tools/vmap4_extractor/wmo.cpp4
64 files changed, 7264 insertions, 23 deletions
diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt
index 0386f27d3df..49c17913c64 100644
--- a/src/tools/CMakeLists.txt
+++ b/src/tools/CMakeLists.txt
@@ -11,3 +11,5 @@
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/map_extractor/System.cpp b/src/tools/map_extractor/System.cpp
index d0342717324..2e117934679 100644
--- a/src/tools/map_extractor/System.cpp
+++ b/src/tools/map_extractor/System.cpp
@@ -277,7 +277,7 @@ void ReadLiquidTypeTableDBC()
// Map file format data
static char const* MAP_MAGIC = "MAPS";
-static char const* MAP_VERSION_MAGIC = "v1.2";
+static char const* MAP_VERSION_MAGIC = "v1.3";
static char const* MAP_AREA_MAGIC = "AREA";
static char const* MAP_HEIGHT_MAGIC = "MHGT";
static char const* MAP_LIQUID_MAGIC = "MLIQ";
@@ -293,6 +293,8 @@ struct map_fileheader
uint32 heightMapSize;
uint32 liquidMapOffset;
uint32 liquidMapSize;
+ uint32 holesOffset;
+ uint32 holesSize;
};
#define MAP_AREA_NO_AREA 0x0001
@@ -827,9 +829,38 @@ bool ConvertADT(char *filename, char *filename2, int /*cell_y*/, int /*cell_x*/,
map.liquidMapSize += sizeof(float)*liquidHeader.width*liquidHeader.height;
}
+ // map hole info
+ uint16 holes[ADT_CELLS_PER_GRID][ADT_CELLS_PER_GRID];
+
+ if (map.liquidMapOffset)
+ map.holesOffset = map.liquidMapOffset + map.liquidMapSize;
+ else
+ map.holesOffset = map.heightMapOffset + map.heightMapSize;
+
+ memset(holes, 0, sizeof(holes));
+ bool hasHoles = false;
+
+ for (int i = 0; i < ADT_CELLS_PER_GRID; ++i)
+ {
+ for (int j = 0; j < ADT_CELLS_PER_GRID; ++j)
+ {
+ adt_MCNK * cell = cells->getMCNK(i,j);
+ if (!cell)
+ continue;
+ holes[i][j] = cell->holes;
+ if (!hasHoles && cell->holes != 0)
+ hasHoles = true;
+ }
+ }
+
+ if (hasHoles)
+ map.holesSize = sizeof(holes);
+ else
+ map.holesSize = 0;
+
// Ok all data prepared - store it
- FILE *output=fopen(filename2, "wb");
- if(!output)
+ FILE* output = fopen(filename2, "wb");
+ if (!output)
{
printf("Can't create the output file '%s'\n", filename2);
return false;
@@ -876,6 +907,11 @@ bool ConvertADT(char *filename, char *filename2, int /*cell_y*/, int /*cell_x*/,
fwrite(&liquid_height[y+liquidHeader.offsetY][liquidHeader.offsetX], sizeof(float), liquidHeader.width, output);
}
}
+
+ // store hole data
+ if (hasHoles)
+ fwrite(holes, map.holesSize, 1, output);
+
fclose(output);
return true;
diff --git a/src/tools/map_extractor/adt.cpp b/src/tools/map_extractor/adt.cpp
index fde70681113..30524dbd640 100644
--- a/src/tools/map_extractor/adt.cpp
+++ b/src/tools/map_extractor/adt.cpp
@@ -6,6 +6,13 @@
int holetab_h[4] = {0x1111, 0x2222, 0x4444, 0x8888};
int holetab_v[4] = {0x000F, 0x00F0, 0x0F00, 0xF000};
+u_map_fcc MHDRMagic = { {'R','D','H','M'} };
+u_map_fcc MCINMagic = { {'N','I','C','M'} };
+u_map_fcc MH2OMagic = { {'O','2','H','M'} };
+u_map_fcc MCNKMagic = { {'K','N','C','M'} };
+u_map_fcc MCVTMagic = { {'T','V','C','M'} };
+u_map_fcc MCLQMagic = { {'Q','L','C','M'} };
+
bool isHole(int holes, int i, int j)
{
int testi = i / 2;
@@ -53,7 +60,7 @@ bool ADT_file::prepareLoadedData()
bool adt_MHDR::prepareLoadedData()
{
- if (fcc != 'MHDR')
+ if (fcc != MHDRMagic.fcc)
return false;
if (size!=sizeof(adt_MHDR)-8)
@@ -72,7 +79,7 @@ bool adt_MHDR::prepareLoadedData()
bool adt_MCIN::prepareLoadedData()
{
- if (fcc != 'MCIN')
+ if (fcc != MCINMagic.fcc)
return false;
// Check cells data
@@ -86,7 +93,7 @@ bool adt_MCIN::prepareLoadedData()
bool adt_MH2O::prepareLoadedData()
{
- if (fcc != 'MH2O')
+ if (fcc != MH2OMagic.fcc)
return false;
// Check liquid data
@@ -98,7 +105,7 @@ bool adt_MH2O::prepareLoadedData()
bool adt_MCNK::prepareLoadedData()
{
- if (fcc != 'MCNK')
+ if (fcc != MCNKMagic.fcc)
return false;
// Check height map
@@ -113,7 +120,7 @@ bool adt_MCNK::prepareLoadedData()
bool adt_MCVT::prepareLoadedData()
{
- if (fcc != 'MCVT')
+ if (fcc != MCVTMagic.fcc)
return false;
if (size != sizeof(adt_MCVT)-8)
@@ -124,8 +131,8 @@ bool adt_MCVT::prepareLoadedData()
bool adt_MCLQ::prepareLoadedData()
{
- if (fcc != 'MCLQ')
+ if (fcc != MCLQMagic.fcc)
return false;
return true;
-} \ No newline at end of file
+}
diff --git a/src/tools/map_extractor/loadlib.cpp b/src/tools/map_extractor/loadlib.cpp
index 465eb04083f..5dcb479a11c 100644
--- a/src/tools/map_extractor/loadlib.cpp
+++ b/src/tools/map_extractor/loadlib.cpp
@@ -6,6 +6,8 @@
class MPQFile;
+u_map_fcc MverMagic = { {'R','E','V','M'} };
+
FileLoader::FileLoader()
{
data = 0;
@@ -49,7 +51,7 @@ bool FileLoader::prepareLoadedData()
{
// Check version
version = (file_MVER *) data;
- if (version->fcc != 'MVER')
+ if (version->fcc != MverMagic.fcc)
return false;
if (version->ver != FILE_FORMAT_VERSION)
return false;
diff --git a/src/tools/map_extractor/loadlib/loadlib.h b/src/tools/map_extractor/loadlib/loadlib.h
index bf6c0706d46..f32f46c63b9 100644
--- a/src/tools/map_extractor/loadlib/loadlib.h
+++ b/src/tools/map_extractor/loadlib/loadlib.h
@@ -29,6 +29,12 @@ typedef uint8_t uint8;
#define FILE_FORMAT_VERSION 18
+union u_map_fcc
+{
+ char fcc_txt[4];
+ uint32 fcc;
+};
+
//
// File version chunk
//
diff --git a/src/tools/map_extractor/wdt.cpp b/src/tools/map_extractor/wdt.cpp
index dedefbb64e5..ff255145583 100644
--- a/src/tools/map_extractor/wdt.cpp
+++ b/src/tools/map_extractor/wdt.cpp
@@ -2,23 +2,27 @@
#include "wdt.h"
+u_map_fcc MWMOMagic = { {'O', 'M', 'W', 'M'} };
+u_map_fcc MPHDMagic = { {'D', 'H', 'P', 'M'} };
+u_map_fcc MAINMagic = { {'N', 'I', 'A', 'M'} };
+
bool wdt_MWMO::prepareLoadedData()
{
- if (fcc != 'MWMO')
+ if (fcc != MWMOMagic.fcc)
return false;
return true;
}
bool wdt_MPHD::prepareLoadedData()
{
- if (fcc != 'MPHD')
+ if (fcc != MPHDMagic.fcc)
return false;
return true;
}
bool wdt_MAIN::prepareLoadedData()
{
- if (fcc != 'MAIN')
+ if (fcc != MAINMagic.fcc)
return false;
return true;
}
@@ -59,4 +63,4 @@ bool WDT_file::prepareLoadedData()
if (!wmo->prepareLoadedData())
return false;
return true;
-} \ No newline at end of file
+}
diff --git a/src/tools/mesh_extractor/ADT.cpp b/src/tools/mesh_extractor/ADT.cpp
new file mode 100644
index 00000000000..c2ac19d5be0
--- /dev/null
+++ b/src/tools/mesh_extractor/ADT.cpp
@@ -0,0 +1,53 @@
+#include "ADT.h"
+#include "DoodadHandler.h"
+#include "LiquidHandler.h"
+#include "WorldModelHandler.h"
+
+ADT::ADT( std::string file ) : ObjectData(NULL), Data(NULL), HasObjectData(false),
+ _DoodadHandler(NULL), _WorldModelHandler(NULL), _LiquidHandler(NULL)
+{
+ Data = new ChunkedData(file);
+ ObjectData = new ChunkedData(Utils::Replace(file, ".adt", "_obj0.adt"));
+ if (ObjectData->Stream)
+ HasObjectData = true;
+ else
+ ObjectData = NULL;
+}
+
+ADT::~ADT()
+{
+ delete ObjectData;
+ delete Data;
+
+ for (std::vector<MapChunk*>::iterator itr = MapChunks.begin(); itr != MapChunks.end(); ++itr)
+ delete *itr;
+
+ MapChunks.clear();
+ delete _DoodadHandler;
+ delete _WorldModelHandler;
+ delete _LiquidHandler;
+}
+
+void ADT::Read()
+{
+ Header.Read(Data->GetChunkByName("MHDR")->GetStream());
+ MapChunks.reserve(16 * 16);
+
+ for (std::vector<Chunk*>::iterator itr = Data->Chunks.begin(); itr != Data->Chunks.end(); ++itr)
+ if ((*itr)->Name == "MCNK")
+ MapChunks.push_back(new MapChunk(this, *itr));
+
+ _LiquidHandler = new LiquidHandler(this);
+
+ // do this separate from map chunk initialization to access liquid data
+ for (std::vector<MapChunk*>::iterator itr = MapChunks.begin(); itr != MapChunks.end(); ++itr)
+ (*itr)->GenerateTriangles();
+
+ _DoodadHandler = new DoodadHandler(this);
+ for (std::vector<MapChunk*>::iterator itr = MapChunks.begin(); itr != MapChunks.end(); ++itr)
+ _DoodadHandler->ProcessMapChunk(*itr);
+
+ _WorldModelHandler = new WorldModelHandler(this);
+ for (std::vector<MapChunk*>::iterator itr = MapChunks.begin(); itr != MapChunks.end(); ++itr)
+ _WorldModelHandler->ProcessMapChunk(*itr);
+}
diff --git a/src/tools/mesh_extractor/ADT.h b/src/tools/mesh_extractor/ADT.h
new file mode 100644
index 00000000000..133596eb024
--- /dev/null
+++ b/src/tools/mesh_extractor/ADT.h
@@ -0,0 +1,29 @@
+#ifndef ADT_H
+#define ADT_H
+#include "ChunkedData.h"
+#include "MapChunk.h"
+
+class DoodadHandler;
+class WorldModelHandler;
+class LiquidHandler;
+
+class ADT
+{
+public:
+ ADT(std::string file);
+ ~ADT();
+
+ void Read();
+
+ ChunkedData* ObjectData;
+ ChunkedData* Data;
+ std::vector<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..b8bd9f54331
--- /dev/null
+++ b/src/tools/mesh_extractor/CMakeLists.txt
@@ -0,0 +1,81 @@
+# Copyright (C) 2005-2009 MaNGOS project <http://getmangos.com/>
+# Copyright (C) 2008-2013 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_BINARY_DIR}
+ ${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/g3dlite/include
+ ${ACE_INCLUDE_DIR}
+ ${MYSQL_INCLUDE_DIR}
+ ${OPENSSL_INCLUDE_DIR}
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ )
+elseif( WIN32 )
+ include_directories (
+ ${CMAKE_BINARY_DIR}
+ ${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}
+ ${MYSQL_INCLUDE_DIR}
+ ${OPENSSL_INCLUDE_DIR}
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ )
+endif()
+
+add_executable(MeshExtractor
+ ${sources}
+)
+
+target_link_libraries(MeshExtractor
+ shared
+ g3dlib
+ mpq
+ Recast
+ Detour
+ ${MYSQL_LIBRARY}
+ ${OPENSSL_LIBRARIES}
+ ${OPENSSL_EXTRA_LIBRARIES}
+ ${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()
diff --git a/src/tools/mesh_extractor/Cache.h b/src/tools/mesh_extractor/Cache.h
new file mode 100644
index 00000000000..34293b14b54
--- /dev/null
+++ b/src/tools/mesh_extractor/Cache.h
@@ -0,0 +1,62 @@
+#ifndef CACHE_H
+#define CACHE_H
+#include <string>
+#include <map>
+#include "Common.h"
+#include "ace/Synch.h"
+
+class WorldModelRoot;
+class Model;
+
+template<class K, class T>
+class GenericCache
+{
+public:
+ GenericCache() {}
+
+ static const uint32 FlushLimit = 1000;
+
+ void Insert(K key, T* val)
+ {
+ ACE_GUARD(ACE_Thread_Mutex, g, mutex);
+
+ if (_items.size() > FlushLimit)
+ Clear();
+ _items[key] = val;
+ }
+
+ T* Get(K key)
+ {
+ ACE_GUARD_RETURN(ACE_Thread_Mutex, g, mutex, NULL);
+ typename std::map<K, T*>::iterator itr = _items.find(key);
+ if (itr != _items.end())
+ return itr->second;
+ return NULL;
+ }
+
+ void Clear()
+ {
+ for (typename std::map<K, T*>::iterator itr = _items.begin(); itr != _items.end(); ++itr)
+ delete itr->second;
+ _items.clear();
+ }
+private:
+ std::map<K, T*> _items;
+ ACE_Thread_Mutex mutex;
+};
+
+class CacheClass
+{
+public:
+ CacheClass() {}
+ GenericCache<std::string, Model> ModelCache;
+ GenericCache<std::string, WorldModelRoot> WorldModelCache;
+
+ void Clear()
+ {
+
+ }
+};
+
+extern CacheClass* Cache;
+#endif
diff --git a/src/tools/mesh_extractor/Chunk.cpp b/src/tools/mesh_extractor/Chunk.cpp
new file mode 100644
index 00000000000..9f2898a46e0
--- /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 (uint32(ftell(stream)) < Utils::Size(stream))
+ {
+ char b = 0;
+ if (fread(&b, sizeof(char), 1, stream) != 1 || b != name[matched])
+ matched = 0;
+ else
+ ++matched;
+
+ 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..e0db12a6be7
--- /dev/null
+++ b/src/tools/mesh_extractor/ChunkedData.cpp
@@ -0,0 +1,74 @@
+#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;
+ if (fread(&length, sizeof(uint32), 1, Stream) != 1)
+ continue;
+ 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 (uint32 i = 0; i < Chunks.size(); ++i)
+ if (Chunks[i]->Name == name)
+ return i;
+ return -1;
+}
+
+Chunk* ChunkedData::GetChunkByName( std::string name )
+{
+ for (uint32 i = 0; i < Chunks.size(); ++i)
+ if (Chunks[i]->Name == name)
+ return Chunks[i];
+ return NULL;
+}
+
+ChunkedData::~ChunkedData()
+{
+ for (std::vector<Chunk*>::iterator itr = Chunks.begin(); itr != Chunks.end(); ++itr)
+ delete *itr;
+
+ Chunks.clear();
+ if (Stream)
+ fclose(Stream);
+}
diff --git a/src/tools/mesh_extractor/ChunkedData.h b/src/tools/mesh_extractor/ChunkedData.h
new file mode 100644
index 00000000000..e23648c845e
--- /dev/null
+++ b/src/tools/mesh_extractor/ChunkedData.h
@@ -0,0 +1,21 @@
+#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);
+ ~ChunkedData();
+
+ 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..54fa073fbef
--- /dev/null
+++ b/src/tools/mesh_extractor/Constants.h
@@ -0,0 +1,59 @@
+#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,
+ };
+
+ enum PolyFlag
+ {
+ POLY_FLAG_WALK = 1,
+ POLY_FLAG_SWIM = 2,
+ POLY_FLAG_FLIGHTMASTER = 4
+ };
+
+ enum ExtractFlags
+ {
+ EXTRACT_FLAG_DBC = 0x01,
+ EXTRACT_FLAG_MAPS = 0x02,
+ EXTRACT_FLAG_VMAPS = 0x04,
+ EXTRACT_FLAG_GOB_MODELS = 0x08,
+ EXTRACT_FLAG_MMAPS = 0x10,
+ EXTRACT_FLAG_TEST = 0x20,
+ EXTRACT_FLAG_ALLOWED = EXTRACT_FLAG_DBC | EXTRACT_FLAG_MAPS | EXTRACT_FLAG_VMAPS | EXTRACT_FLAG_GOB_MODELS | EXTRACT_FLAG_MMAPS | EXTRACT_FLAG_TEST
+ };
+
+ static const float TileSize;
+ static const float MaxXY;
+ static const float ChunkSize;
+ static const float UnitSize;
+ static const float Origin[];
+ static const float PI;
+ static const float MaxStandableHeight;
+ static bool ToWoWCoords;
+ static const char* VMAPMagic;
+ static const float BaseUnitDim;
+ static const int VertexPerMap;
+ static const int VertexPerTile;
+ static const int TilesPerMap;
+};
+
+#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..9b5c9f9c77b
--- /dev/null
+++ b/src/tools/mesh_extractor/ContinentBuilder.cpp
@@ -0,0 +1,144 @@
+#include "ContinentBuilder.h"
+#include "TileBuilder.h"
+#include "WDT.h"
+#include "Utils.h"
+#include "DetourNavMesh.h"
+#include "Cache.h"
+#include "ace/Task.h"
+#include "Recast.h"
+
+class BuilderThread : public ACE_Task_Base
+{
+private:
+ int X, Y, MapId;
+ std::string Continent;
+ bool debug;
+ dtNavMeshParams Params;
+ ContinentBuilder* cBuilder;
+public:
+ BuilderThread(ContinentBuilder* _cBuilder, bool deb, dtNavMeshParams& params) : debug(deb), Params(params), cBuilder(_cBuilder), Free(true) {}
+ void SetData(int x, int y, int map, std::string cont) { X = x; Y = y; MapId = map; Continent = cont; }
+
+ int svc()
+ {
+ Free = false;
+ printf("[%02i,%02i] Building tile\n", X, Y);
+ TileBuilder builder(cBuilder, Continent, X, Y, MapId);
+ char buff[100];
+ sprintf(buff, "mmaps/%03u%02u%02u.mmtile", MapId, Y, X);
+ FILE* f = fopen(buff, "r");
+ if (f) // Check if file already exists.
+ {
+ printf("[%02i,%02i] Tile skipped, file already exists\n", X, Y);
+ fclose(f);
+ Free = true;
+ return 0;
+ }
+ uint8* nav = builder.Build(debug, Params);
+ if (nav)
+ {
+ f = fopen(buff, "wb");
+ if (!f)
+ {
+ printf("Could not create file %s. Check that you have write permissions to the destination folder and try again\n", buff);
+ return 0;
+ }
+ MmapTileHeader header;
+ header.size = builder.DataSize;
+ fwrite(&header, sizeof(MmapTileHeader), 1, f);
+ fwrite(nav, sizeof(unsigned char), builder.DataSize, f);
+ fclose(f);
+ }
+ dtFree(nav);
+ printf("[%02u,%02u] Tile Built!\n", X, Y);
+ Free = true;
+ return 0;
+ }
+
+ bool Free;
+};
+
+void ContinentBuilder::getTileBounds(uint32 tileX, uint32 tileY, float* verts, int vertCount, float* bmin, float* bmax)
+{
+ // this is for elevation
+ if (verts && vertCount)
+ rcCalcBounds(verts, vertCount, bmin, bmax);
+ else
+ {
+ bmin[1] = FLT_MIN;
+ bmax[1] = FLT_MAX;
+ }
+
+ // this is for width and depth
+ bmax[0] = (32 - int(tileX)) * Constants::TileSize;
+ bmax[2] = (32 - int(tileY)) * Constants::TileSize;
+ bmin[0] = bmax[0] - Constants::TileSize;
+ bmin[2] = bmax[2] - Constants::TileSize;
+}
+
+void ContinentBuilder::CalculateTileBounds()
+{
+ for (std::vector<TilePos>::iterator itr = TileMap->TileTable.begin(); itr != TileMap->TileTable.end(); ++itr)
+ {
+ tileXMax = std::max(itr->X, tileXMax);
+ tileXMin = std::min(itr->X, tileXMin);
+
+ tileYMax = std::max(itr->Y, tileYMax);
+ tileYMin = std::min(itr->Y, tileYMin);
+ }
+ getTileBounds(tileXMax, tileYMax, NULL, 0, bmin, bmax);
+}
+
+void ContinentBuilder::Build(bool debug)
+{
+ char buff[50];
+ sprintf(buff, "mmaps/%03u.mmap", MapId);
+ FILE* mmap = fopen(buff, "wb");
+ if (!mmap)
+ {
+ printf("Could not create file %s. Check that you have write permissions to the destination folder and try again\n", buff);
+ return;
+ }
+
+ CalculateTileBounds();
+
+ dtNavMeshParams params;
+ params.maxPolys = 1 << STATIC_POLY_BITS;
+ params.maxTiles = TileMap->TileTable.size();
+ rcVcopy(params.orig, bmin);
+ params.tileHeight = Constants::TileSize;
+ params.tileWidth = Constants::TileSize;
+ fwrite(&params, sizeof(dtNavMeshParams), 1, mmap);
+ fclose(mmap);
+ std::vector<BuilderThread*> Threads;
+ for (uint32 i = 0; i < NumberOfThreads; ++i)
+ Threads.push_back(new BuilderThread(this, debug, params));
+ printf("Map %s ( %i ) has %u tiles. Building them with %i threads\n", Continent.c_str(), MapId, uint32(TileMap->TileTable.size()), NumberOfThreads);
+ for (std::vector<TilePos>::iterator itr = TileMap->TileTable.begin(); itr != TileMap->TileTable.end(); ++itr)
+ {
+ bool next = false;
+ while (!next)
+ {
+ for (std::vector<BuilderThread*>::iterator _th = Threads.begin(); _th != Threads.end(); ++_th)
+ {
+ if ((*_th)->Free)
+ {
+ (*_th)->SetData(itr->X, itr->Y, MapId, Continent);
+ (*_th)->activate();
+ next = true;
+ break;
+ }
+ }
+ // Wait for 20 seconds
+ ACE_OS::sleep(ACE_Time_Value (0, 20000));
+ }
+ }
+ Cache->Clear();
+
+ // Free memory
+ for (std::vector<BuilderThread*>::iterator _th = Threads.begin(); _th != Threads.end(); ++_th)
+ {
+ (*_th)->wait();
+ delete *_th;
+ }
+}
diff --git a/src/tools/mesh_extractor/ContinentBuilder.h b/src/tools/mesh_extractor/ContinentBuilder.h
new file mode 100644
index 00000000000..3d7b879879e
--- /dev/null
+++ b/src/tools/mesh_extractor/ContinentBuilder.h
@@ -0,0 +1,30 @@
+#ifndef CONT_BUILDER_H
+#define CONT_BUILDER_H
+#include <string>
+#include "WDT.h"
+#include "Common.h"
+
+class ContinentBuilder
+{
+public:
+ ContinentBuilder(std::string continent, uint32 mapId, WDT* wdt, uint32 tn) :
+ Continent(continent), TileMap(wdt), MapId(mapId),
+ NumberOfThreads(tn), tileXMin(64), tileYMin(64), tileXMax(0), tileYMax(0)
+ {}
+
+ void Build(bool debug);
+ void getTileBounds(uint32 tileX, uint32 tileY, float* verts, int vertCount, float* bmin, float* bmax);
+ void CalculateTileBounds();
+ float bmin[3];
+ float bmax[3];
+private:
+ std::string Continent;
+ WDT* TileMap;
+ uint32 MapId;
+ uint32 NumberOfThreads;
+ int tileXMin;
+ int tileYMin;
+ int tileXMax;
+ int tileYMax;
+};
+#endif
diff --git a/src/tools/mesh_extractor/DBC.cpp b/src/tools/mesh_extractor/DBC.cpp
new file mode 100644
index 00000000000..3c846363d06
--- /dev/null
+++ b/src/tools/mesh_extractor/DBC.cpp
@@ -0,0 +1,70 @@
+#include <cstdio>
+#include "DBC.h"
+#include "Common.h"
+
+DBC::DBC( FILE* stream ) : StringBlock(NULL), StringBlockSize(0), IsFaulty(true)
+{
+ char magic[5];
+ uint32 count = 0;
+ count += fread(&magic, sizeof(char), 4, stream);
+ magic[4] = '\0';
+ count += fread(&RecordCount, sizeof(uint32), 1, stream);
+ Records.reserve(RecordCount);
+ count += fread(&Fields, sizeof(uint32), 1, stream);
+ count += fread(&RecordSize, sizeof(uint32), 1, stream);
+ count += fread(&StringBlockSize, sizeof(uint32), 1, stream);
+ if (count != 8)
+ printf("DBC::DBC: Failed to read some data expected 8, read %u\n", count);
+
+ for (int i = 0; i < RecordCount; i++)
+ {
+ Record* rec = new Record(this);
+ Records.push_back(rec);
+ int size = 0;
+ for (int f = 0; f < Fields; f++)
+ {
+ if (size + 4 > RecordSize)
+ {
+ IsFaulty = true;
+ break;
+ }
+ uint32 tmp;
+ if (fread(&tmp, sizeof(uint32), 1, stream) != 1)
+ printf("DBC::DBC: Failed to read some data expected 1, read 0\n");
+ rec->Values.push_back(tmp);
+ size += 4;
+ }
+ }
+ StringBlock = new uint8[StringBlockSize];
+ count = fread(StringBlock, sizeof(uint8), StringBlockSize, stream);
+ if (count != StringBlockSize)
+ printf("DBC::DBC: Failed to read some data expected %u, read %u\n", StringBlockSize, count);
+}
+
+std::string DBC::GetStringByOffset( int offset )
+{
+ int len = 0;
+ for (uint32 i = offset; i < StringBlockSize; i++)
+ {
+ if (!StringBlock[i])
+ {
+ len = (i - offset);
+ break;
+ }
+ }
+ char* d = new char[len+1];
+ strcpy(d, (const char*)(StringBlock + offset));
+ d[len] = '\0';
+ std::string val = std::string(d);
+ delete d;
+ return val;
+}
+
+Record* DBC::GetRecordById( int id )
+{
+ // we assume Id is index 0
+ for (std::vector<Record*>::iterator itr = Records.begin(); itr != Records.end(); ++itr)
+ if ((*itr)->Values[0] == id)
+ return *itr;
+ return NULL;
+}
diff --git a/src/tools/mesh_extractor/DBC.h b/src/tools/mesh_extractor/DBC.h
new file mode 100644
index 00000000000..0c857bd40ed
--- /dev/null
+++ b/src/tools/mesh_extractor/DBC.h
@@ -0,0 +1,52 @@
+#ifndef DBC_H
+#define DBC_H
+#include <vector>
+#include "Common.h"
+
+class Record;
+
+class DBC
+{
+public:
+ DBC(FILE* stream);
+
+ std::string GetStringByOffset(int offset);
+
+ Record* GetRecordById(int id);
+
+ std::string Name;
+ std::vector<Record*> Records;
+ int RecordCount;
+ int Fields;
+ int RecordSize;
+ uint8* StringBlock;
+ uint32 StringBlockSize;
+ bool IsFaulty;
+};
+
+class Record
+{
+public:
+ Record(DBC* dbc) : Source(dbc) {}
+
+ DBC* Source;
+ std::vector<int> Values;
+
+ int operator[](int index)
+ {
+ return Values[index];
+ }
+
+ template <typename T>
+ T GetValue(int index)
+ {
+ return *(T*)(&Values[index]);
+ }
+
+ std::string GetString(int index)
+ {
+ return Source->GetStringByOffset(Values[index]);
+ }
+};
+
+#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..56c2a7986f8
--- /dev/null
+++ b/src/tools/mesh_extractor/DoodadHandler.cpp
@@ -0,0 +1,109 @@
+#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 (uint32 i = 0; i < refCount; i++)
+ {
+ int32 index;
+ if (int count = fread(&index, sizeof(int32), 1, stream) != 1)
+ printf("DoodadHandler::ProcessInternal: Failed to read some data expected 1, read %d\n", count);
+ if (index < 0 || uint32(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;
+ if (fread(&offset, sizeof(uint32), 1, idStream) != 1)
+ printf("DoodadHandler::ReadDoodadPaths: Failed to read some data expected 1, read 0\n");
+ FILE* dataStream = data->GetStream();
+ fseek(dataStream, offset + data->Offset, SEEK_SET);
+ _paths->push_back(Utils::ReadString(dataStream));
+ }
+}
+
+void DoodadHandler::InsertModelGeometry(const 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<uint32>(Constants::TRIANGLE_TYPE_DOODAD, itr->V0 + vertOffset, itr->V1 + vertOffset, itr->V2 + vertOffset));
+}
+
+DoodadHandler::~DoodadHandler()
+{
+ delete _definitions;
+ delete _paths;
+}
diff --git a/src/tools/mesh_extractor/DoodadHandler.h b/src/tools/mesh_extractor/DoodadHandler.h
new file mode 100644
index 00000000000..981834ec7ac
--- /dev/null
+++ b/src/tools/mesh_extractor/DoodadHandler.h
@@ -0,0 +1,57 @@
+#ifndef DOOADHNDL_H
+#define DOOADHNDL_H
+#include "ObjectDataHandler.h"
+#include "Utils.h"
+#include "Chunk.h"
+#include "Model.h"
+#include <set>
+#include <vector>
+
+class DoodadDefinition : public IDefinition
+{
+public:
+ uint32 MmidIndex;
+ uint32 UniqueId;
+ uint16 DecimalScale;
+ uint16 Flags;
+
+ virtual float Scale() const { return DecimalScale / 1024.0f; }
+
+ void Read(FILE* stream)
+ {
+ int count = 0;
+
+ count += fread(&MmidIndex, sizeof(uint32), 1, stream);
+ count += fread(&UniqueId, sizeof(uint32), 1, stream);
+ Position = Vector3::Read(stream);
+ Rotation = Vector3::Read(stream);
+ count += fread(&DecimalScale, sizeof(uint16), 1, stream);
+ count += fread(&Flags, sizeof(uint16), 1, stream);
+ if (count != 4)
+ printf("DoodadDefinition::Read: Failed to read some data expected 4, read %d\n", count);
+ }
+};
+
+class DoodadHandler : public ObjectDataHandler
+{
+public:
+ DoodadHandler(ADT* adt);
+ ~DoodadHandler();
+
+ 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(const DoodadDefinition& def, Model* model);
+ std::set<uint32> _drawn;
+ std::vector<DoodadDefinition>* _definitions;
+ std::vector<std::string>* _paths;
+};
+#endif
diff --git a/src/tools/mesh_extractor/Geometry.cpp b/src/tools/mesh_extractor/Geometry.cpp
new file mode 100644
index 00000000000..2fc470e8e9f
--- /dev/null
+++ b/src/tools/mesh_extractor/Geometry.cpp
@@ -0,0 +1,123 @@
+#include "Geometry.h"
+#include "Constants.h"
+#include "ADT.h"
+#include "WorldModelHandler.h"
+#include "DoodadHandler.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 (uint32 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 (uint32 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 (uint32 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;
+ }
+ }
+}
+
+void Geometry::AddAdt( ADT* adt )
+{
+ for (std::vector<MapChunk*>::iterator itr = adt->MapChunks.begin(); itr != adt->MapChunks.end(); ++itr)
+ {
+ std::vector<Triangle<uint32> > tmp;
+ tmp.reserve((*itr)->Triangles.size());
+ for (std::vector<Triangle<uint8> >::iterator itr2 = (*itr)->Triangles.begin(); itr2 != (*itr)->Triangles.end(); ++itr2)
+ tmp.push_back(Triangle<uint32>(itr2->Type, itr2->V0, itr2->V1, itr2->V2));
+ AddData((*itr)->Vertices, tmp);
+ }
+
+ if (!adt->_DoodadHandler->Triangles.empty())
+ AddData(adt->_DoodadHandler->Vertices, adt->_DoodadHandler->Triangles);
+
+ if (!adt->_WorldModelHandler->Triangles.empty())
+ AddData(adt->_WorldModelHandler->Vertices, adt->_WorldModelHandler->Triangles);
+}
+
diff --git a/src/tools/mesh_extractor/Geometry.h b/src/tools/mesh_extractor/Geometry.h
new file mode 100644
index 00000000000..e445234dd12
--- /dev/null
+++ b/src/tools/mesh_extractor/Geometry.h
@@ -0,0 +1,23 @@
+#ifndef GEOMETRY_H
+#define GEOMETRY_H
+#include <vector>
+
+#include "Utils.h"
+
+class ADT;
+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 AddAdt(ADT* adt);
+ 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..285ea1a5b74
--- /dev/null
+++ b/src/tools/mesh_extractor/LiquidHandler.cpp
@@ -0,0 +1,102 @@
+#include "LiquidHandler.h"
+#include "Utils.h"
+
+LiquidHandler::LiquidHandler( ADT* adt ) : Source(adt)
+{
+ HandleNewLiquid();
+}
+
+void LiquidHandler::HandleNewLiquid()
+{
+ Chunk* chunk = Source->Data->GetChunkByName("MH2O");
+ if (!chunk)
+ return;
+
+ Vertices.reserve(1000);
+ Triangles.reserve(1000);
+
+ FILE* stream = chunk->GetStream();
+ H2OHeader header[256];
+ MCNKData.reserve(256);
+ for (int i = 0; i < 256; i++)
+ header[i] = H2OHeader::Read(stream);
+
+ for (int i = 0; i < 256; i++)
+ {
+ H2OHeader h = header[i];
+ if (h.LayerCount == 0)
+ {
+ // Need to fill in missing data with dummies.
+ MCNKData.push_back(MCNKLiquidData(NULL, H2ORenderMask()));
+ continue;
+ }
+ fseek(stream, chunk->Offset + h.OffsetInformation, SEEK_SET);
+ H2OInformation information = H2OInformation::Read(stream);
+
+ float** heights = new float*[9];
+ for (int j = 0; j < 9; ++i)
+ {
+ heights[j] = new float[9];
+ memset(heights[j], 0, sizeof(float) * 9);
+ }
+
+ H2ORenderMask renderMask;
+ if (information.LiquidType != 2)
+ {
+ fseek(stream, chunk->Offset + h.OffsetRender, SEEK_SET);
+ renderMask = H2ORenderMask::Read(stream);
+ if ((Utils::IsAllZero(renderMask.Mask, 8) || (information.Width == 8 && information.Height == 8)) && information.OffsetMask2)
+ {
+ fseek(stream, chunk->Offset + information.OffsetMask2, SEEK_SET);
+ uint32 size = ceil(information.Width * information.Height / 8.0f);
+ uint8* altMask = new uint8[size];
+ if (fread(altMask, sizeof(uint8), size, stream) == size)
+ for (uint32 mi = 0; mi < size; mi++)
+ renderMask.Mask[mi + information.OffsetY] |= altMask[mi];
+ delete[] altMask;
+ }
+ fseek(stream, chunk->Offset + information.OffsetHeightmap, SEEK_SET);
+
+ for (int y = information.OffsetY; y < (information.OffsetY + information.Height); y++)
+ for (int x = information.OffsetX; x < (information.OffsetX + information.Width); x++)
+ if (fread(&heights[x][y], sizeof(float), 1, stream) != 1)
+ return;
+ }
+ else
+ {
+ // Fill with ocean data
+ for (uint32 i = 0; i < 8; ++i)
+ renderMask.Mask[i] = 0xFF;
+
+ for (uint32 y = 0; y < 9; ++y)
+ for (uint32 x = 0; x < 9; ++x)
+ heights[x][y] = information.HeightLevel1;
+ }
+
+ MCNKData.push_back(MCNKLiquidData(heights, renderMask));
+
+ for (int y = information.OffsetY; y < (information.OffsetY + information.Height); y++)
+ {
+ for (int x = information.OffsetX; x < (information.OffsetX + information.Width); x++)
+ {
+ if (!renderMask.ShouldRender(x, y))
+ continue;
+
+ MapChunk* mapChunk = Source->MapChunks[i];
+ Vector3 location = mapChunk->Header.Position;
+ location.y = location.y - (x * Constants::UnitSize);
+ location.x = location.x - (y * Constants::UnitSize);
+ location.z = heights[x][y];
+
+ uint32 vertOffset = Vertices.size();
+ Vertices.push_back(location);
+ Vertices.push_back(Vector3(location.x - Constants::UnitSize, location.y, location.z));
+ Vertices.push_back(Vector3(location.x, location.y - Constants::UnitSize, location.z));
+ Vertices.push_back(Vector3(location.x - Constants::UnitSize, location.y - Constants::UnitSize, location.z));
+
+ Triangles.push_back(Triangle<uint32>(Constants::TRIANGLE_TYPE_WATER, vertOffset, vertOffset+2, vertOffset + 1));
+ Triangles.push_back(Triangle<uint32>(Constants::TRIANGLE_TYPE_WATER, vertOffset + 2, vertOffset + 3, vertOffset + 1));
+ }
+ }
+ }
+}
diff --git a/src/tools/mesh_extractor/LiquidHandler.h b/src/tools/mesh_extractor/LiquidHandler.h
new file mode 100644
index 00000000000..6e8d0081adb
--- /dev/null
+++ b/src/tools/mesh_extractor/LiquidHandler.h
@@ -0,0 +1,21 @@
+#ifndef LIQUID_H
+#define LIQUID_H
+#include "ADT.h"
+#include "Utils.h"
+#include "Common.h"
+
+#include <vector>
+
+class LiquidHandler
+{
+public:
+ LiquidHandler(ADT* adt);
+
+ ADT* Source;
+ std::vector<Vector3> Vertices;
+ std::vector<Triangle<uint32> > Triangles;
+ std::vector<MCNKLiquidData> MCNKData;
+private:
+ void HandleNewLiquid();
+};
+#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..b7be181594d
--- /dev/null
+++ b/src/tools/mesh_extractor/MPQ.cpp
@@ -0,0 +1,118 @@
+#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;
+ }
+ }
+ GetFileListTo(Files);
+}
+
+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..15fce452726
--- /dev/null
+++ b/src/tools/mesh_extractor/MPQ.h
@@ -0,0 +1,90 @@
+#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;
+
+ vector<string> Files;
+
+ 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..90491dfb945
--- /dev/null
+++ b/src/tools/mesh_extractor/MPQManager.cpp
@@ -0,0 +1,109 @@
+#include "MPQManager.h"
+#include "MPQ.h"
+#include "DBC.h"
+#include "Utils.h"
+
+#include "ace/Synch.h"
+
+char* MPQManager::Files[] = {
+ "common.MPQ",
+ "common-2.MPQ",
+ "expansion.MPQ",
+ "lichking.MPQ",
+ "patch.MPQ",
+ "patch-2.MPQ",
+ "patch-3.MPQ"
+};
+
+char* MPQManager::Languages[] = { "enGB", "enUS", "deDE", "esES", "frFR", "koKR", "zhCN", "zhTW", "enCN", "enTW", "esMX", "ruRU" };
+
+void MPQManager::Initialize()
+{
+ InitializeDBC();
+ uint32 size = sizeof(Files) / sizeof(char*);
+ for (uint32 i = 0; i < size; ++i)
+ {
+ MPQArchive* arc = new MPQArchive(std::string("Data/" + std::string(Files[i])).c_str());
+ Archives.push_front(arc);
+ printf("Opened %s\n", Files[i]);
+ }
+}
+
+void MPQManager::InitializeDBC()
+{
+ BaseLocale = -1;
+ std::string fileName;
+ uint32 size = sizeof(Languages) / sizeof(char*);
+ MPQArchive* _baseLocale = NULL;
+ for (uint32 i = 0; i < size; ++i)
+ {
+ std::string _fileName = "Data/" + std::string(Languages[i]) + "/locale-" + std::string(Languages[i]) + ".MPQ";
+ FILE* file = fopen(_fileName.c_str(), "rb");
+ if (file)
+ {
+ if (BaseLocale == -1)
+ {
+ BaseLocale = i;
+ _baseLocale = new MPQArchive(_fileName.c_str());
+ fileName = _fileName;
+ LocaleFiles[i] = _baseLocale;
+ }
+ else
+ LocaleFiles[i] = new MPQArchive(_fileName.c_str());
+
+ AvailableLocales.insert(i);
+ printf("Detected locale: %s\n", Languages[i]);
+ }
+ }
+ Archives.push_front(_baseLocale);
+ if (BaseLocale == -1)
+ {
+ printf("No locale data detected\n");
+ ASSERT(false);
+ }
+ else
+ printf("Using default locale: %s\n", Languages[BaseLocale]);
+}
+
+FILE* MPQManager::GetFile( std::string path )
+{
+ ACE_GUARD_RETURN(ACE_Thread_Mutex, g, mutex, NULL);
+ MPQFile file(path.c_str());
+ if (file.isEof())
+ return NULL;
+ return file.GetFileStream();
+}
+
+DBC* MPQManager::GetDBC( std::string name )
+{
+ std::string path = "DBFilesClient\\" + name + ".dbc";
+ return new DBC(GetFile(path));
+}
+
+FILE* MPQManager::GetFileFrom( std::string path, MPQArchive* file )
+{
+ ACE_GUARD_RETURN(ACE_Thread_Mutex, g, mutex, NULL);
+ mpq_archive* mpq_a = file->mpq_a;
+
+ uint32_t filenum;
+ if(libmpq__file_number(mpq_a, path.c_str(), &filenum))
+ return NULL;
+
+ libmpq__off_t transferred;
+ libmpq__off_t size = 0;
+ 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)
+ return NULL;
+
+ uint8* buffer = new uint8[size];
+
+ //libmpq_file_getdata
+ libmpq__file_read(mpq_a, filenum, (unsigned char*)buffer, size, &transferred);
+
+ // Pack the return into a FILE stream
+ FILE* ret = tmpfile();
+ fwrite(buffer, sizeof(uint8), size, ret);
+ return ret;
+}
diff --git a/src/tools/mesh_extractor/MPQManager.h b/src/tools/mesh_extractor/MPQManager.h
new file mode 100644
index 00000000000..c23d7177825
--- /dev/null
+++ b/src/tools/mesh_extractor/MPQManager.h
@@ -0,0 +1,34 @@
+#ifndef MPQ_MANAGER_H
+#define MPQ_MANAGER_H
+
+#include "MPQ.h"
+#include "ace/Synch.h"
+
+class DBC;
+class MPQManager
+{
+public:
+ MPQManager() {}
+ ~MPQManager() {}
+
+ void Initialize();
+ FILE* GetFile(std::string path);
+ FILE* GetFileFrom(std::string path, MPQArchive* file);
+ DBC* GetDBC(std::string name);
+ std::vector<std::string> GetAllFiles(std::string extension);
+
+ std::deque<MPQArchive*> Archives;
+ int32 BaseLocale;
+ std::set<uint32> AvailableLocales;
+ std::map<uint32, MPQArchive*> LocaleFiles;
+
+ static char* Files[];
+ static char* Languages[];
+protected:
+ void InitializeDBC();
+private:
+ ACE_Thread_Mutex mutex;
+};
+
+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..8fe40773d43
--- /dev/null
+++ b/src/tools/mesh_extractor/MapChunk.cpp
@@ -0,0 +1,74 @@
+#include "MapChunk.h"
+#include "ADT.h"
+#include "LiquidHandler.h"
+
+MapChunk::MapChunk( ADT* _adt, Chunk* chunk ) : Adt(_adt), Source(chunk)
+{
+ FILE* stream = chunk->GetStream();
+ Header.Read(stream);
+ fseek(stream, chunk->Offset, SEEK_SET);
+ Index = Header.IndexX + Header.IndexY * 16;
+ 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;
+
+ Constants::TriangleType triangleType = Constants::TRIANGLE_TYPE_TERRAIN;
+ if (Adt->_LiquidHandler && !Adt->_LiquidHandler->MCNKData.empty())
+ {
+ MCNKLiquidData& data = Adt->_LiquidHandler->MCNKData[Index];
+ float 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.IsWater(x, y, maxHeight))
+ triangleType = Constants::TRIANGLE_TYPE_WATER;
+ }
+
+ Triangles.push_back(Triangle<uint8>(triangleType, topRight, topLeft, center));
+ Triangles.push_back(Triangle<uint8>(triangleType, topLeft, bottomLeft, center));
+ Triangles.push_back(Triangle<uint8>(triangleType, bottomLeft, bottomRight, center));
+ Triangles.push_back(Triangle<uint8>(triangleType, bottomRight, topRight, center));
+ }
+ }
+}
+
+void MapChunk::GenerateVertices( FILE* stream )
+{
+ fseek(stream, Header.OffsetMCVT, SEEK_CUR);
+ Vertices.reserve(125);
+
+ for (int j = 0; j < 17; j++)
+ {
+ int values = j % 2 ? 8 : 9;
+ for (int i = 0; i < values; i++)
+ {
+ float tmp;
+ if (fread(&tmp, sizeof(float), 1, stream) != 1)
+ printf("MapChunk::GenerateVertices: Failed to read some data expected 1, read 0\n");
+ 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.push_back(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..e7d835ae0e3
--- /dev/null
+++ b/src/tools/mesh_extractor/MapChunk.h
@@ -0,0 +1,24 @@
+#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);
+
+ void GenerateTriangles();
+ void GenerateVertices(FILE* stream);
+ static bool HasHole(uint32 map, int x, int y);
+ ADT* Adt;
+ Chunk* Source;
+ MapChunkHeader Header;
+ std::vector<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..b164ff5861d
--- /dev/null
+++ b/src/tools/mesh_extractor/MeshExtractor.cpp
@@ -0,0 +1,430 @@
+#include "MPQManager.h"
+#include "WDT.h"
+#include "ContinentBuilder.h"
+#include "Cache.h"
+#include "DBC.h"
+#include "Constants.h"
+#include "Model.h"
+
+#include "Recast.h"
+#include "DetourNavMesh.h"
+#include "DetourNavMeshQuery.h"
+
+#include <set>
+
+#include "Common.h"
+#include "LoginDatabase.h"
+#include "Util.h"
+LoginDatabaseWorkerPool LoginDatabase;
+
+MPQManager* MPQHandler;
+CacheClass* Cache;
+
+void ExtractMMaps(std::set<uint32>& mapIds, uint32 threads, bool debug)
+{
+ DBC* dbc = MPQHandler->GetDBC("Map");
+ for (std::vector<Record*>::iterator itr = dbc->Records.begin(); itr != dbc->Records.end(); ++itr)
+ {
+ uint32 mapId = (*itr)->Values[0];
+
+ // Skip this map if a list of specific maps was provided and this one is not contained in it.
+ if (!mapIds.empty() && mapIds.find(mapId) == mapIds.end())
+ continue;
+
+ std::string name = (*itr)->GetString(1);
+ WDT wdt("World\\maps\\" + name + "\\" + name + ".wdt");
+ if (!wdt.IsValid || wdt.IsGlobalModel)
+ continue;
+ printf("Building %s MapId %u\n", name.c_str(), mapId);
+ ContinentBuilder builder(name, mapId, &wdt, threads);
+ builder.Build(debug);
+ }
+}
+
+void ExtractDBCs()
+{
+ printf("Extracting DBCs\n");
+ // Create the filesystem structure
+ std::string baseDBCPath = "dbc/";
+ Utils::CreateDir(baseDBCPath);
+
+ // Populate list of DBC files
+ std::set<std::string> DBCFiles;
+ for (std::vector<std::string>::iterator itr = MPQHandler->LocaleFiles[MPQHandler->BaseLocale]->Files.begin(); itr != MPQHandler->LocaleFiles[MPQHandler->BaseLocale]->Files.end(); ++itr)
+ if (itr->rfind(".dbc") == itr->length() - strlen(".dbc"))
+ DBCFiles.insert(*itr);
+
+ // Iterate over all available locales
+ for (std::set<uint32>::iterator itr = MPQHandler->AvailableLocales.begin(); itr != MPQHandler->AvailableLocales.end(); ++itr)
+ {
+ printf("Extracting DBCs for locale %s\n", MPQManager::Languages[*itr]);
+ std::string path = baseDBCPath;
+ if (*itr != uint32(MPQHandler->BaseLocale))
+ {
+ path += std::string(MPQManager::Languages[*itr]) + "/";
+ Utils::CreateDir(path);
+ }
+
+ std::string component = "component.wow-" + std::string(MPQManager::Languages[*itr]) + ".txt";
+ // Extract the component file
+ Utils::SaveToDisk(MPQHandler->GetFile(component), path + component);
+ // Extract the DBC files for the given locale
+ for (std::set<std::string>::iterator itr2 = DBCFiles.begin(); itr2 != DBCFiles.end(); ++itr2)
+ Utils::SaveToDisk(MPQHandler->GetFileFrom(*itr2, MPQHandler->LocaleFiles[*itr]), path + (itr2->c_str() + strlen("DBFilesClient\\")));
+ }
+ printf("DBC extraction finished!\n");
+}
+
+void ExtractGameobjectModels()
+{
+ Constants::ToWoWCoords = true;
+ printf("Extracting GameObject models\n");
+
+ std::string baseBuildingsPath = "Buildings/";
+ std::string baseVmapsPath = "vmaps/";
+ Utils::CreateDir(baseVmapsPath);
+ Utils::CreateDir(baseBuildingsPath);
+
+ FILE* modelList = fopen((baseVmapsPath + "GameObjectModels.list").c_str(), "wb");
+ if (!modelList)
+ {
+ printf("Could not create file vmaps/GameObjectModels.list, please make sure that you have the write permissions in the folder\n");
+ return;
+ }
+
+ DBC* dbc = MPQHandler->GetDBC("GameObjectDisplayInfo");
+ for (std::vector<Record*>::iterator itr = dbc->Records.begin(); itr != dbc->Records.end(); ++itr)
+ {
+ std::string path = (*itr)->GetString(1);
+ std::string fileName = Utils::GetPlainName(path.c_str());
+ std::string extension = Utils::GetExtension(fileName);
+ // Convert the extension to lowercase
+ std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
+ if (extension == "mdx" || extension == "m2")
+ {
+ fileName = Utils::FixModelPath(fileName);
+ Model model(path);
+
+ if (model.IsBad)
+ continue;
+
+ FILE* output = fopen((baseBuildingsPath + fileName).c_str(), "wb");
+ if (!output)
+ {
+ printf("Could not create file %s, please check that you have write permissions\n", (baseBuildingsPath + fileName).c_str());
+ continue;
+ }
+ // Placeholder for 0 values
+ int Nop[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+ fwrite(Constants::VMAPMagic, 8, 1, output);
+ uint32 numVerts = model.Header.CountBoundingVertices;
+ fwrite(&numVerts, sizeof(uint32), 1, output);
+ uint32 numGroups = 1;
+ fwrite(&numGroups, sizeof(uint32), 1, output);
+ fwrite(Nop, 4 * 3 , 1, output); // rootwmoid, flags, groupid
+ fwrite(Nop, sizeof(float), 3 * 2, output);//bbox, only needed for WMO currently
+ fwrite(Nop, 4, 1, output);// liquidflags
+ fwrite("GRP ", 4, 1, output);
+
+ uint32 branches = 1;
+ uint32 wsize = sizeof(branches) + sizeof(uint32) * branches;
+ fwrite(&wsize, sizeof(uint32), 1, output);
+ fwrite(&branches, sizeof(branches), 1, output);
+ uint32 numTris = model.Header.CountBoundingTriangles;
+ fwrite(&numTris, sizeof(uint32), 1, output);
+ fwrite("INDX", 4, 1, output);
+ wsize = sizeof(uint32) + sizeof(unsigned short) * numTris;
+ fwrite(&wsize, sizeof(int), 1, output);
+ fwrite(&numTris, sizeof(uint32), 1, output);
+ uint16* indices = new uint16[numTris];
+
+ if (numTris > 0)
+ {
+ uint32 i = 0;
+ for (std::vector<Triangle<uint16> >::iterator itr2 = model.Triangles.begin(); itr2 != model.Triangles.end(); ++itr2, ++i)
+ {
+ indices[i * 3 + 0] = itr2->V0;
+ indices[i * 3 + 1] = itr2->V1;
+ indices[i * 3 + 2] = itr2->V2;
+ }
+ fwrite(indices, sizeof(uint16), numTris, output);
+ }
+
+
+ fwrite("VERT", 4, 1, output);
+ wsize = sizeof(int) + sizeof(float) * 3 * numVerts;
+ fwrite(&wsize, sizeof(int), 1, output);
+ fwrite(&numVerts, sizeof(int), 1, output);
+ float* vertices = new float[numVerts*3];
+
+ if (numVerts > 0)
+ {
+ uint32 i = 0;
+ for (std::vector<Vector3>::iterator itr2 = model.Vertices.begin(); itr2 != model.Vertices.end(); ++itr2, ++i)
+ {
+ vertices[i * 3 + 0] = itr2->x;
+ vertices[i * 3 + 1] = itr2->y;
+ vertices[i * 3 + 2] = itr2->z;
+ }
+
+ fwrite(vertices, sizeof(float), numVerts * 3, output);
+ }
+
+ fclose(output);
+ delete[] indices;
+ delete[] vertices;
+
+ uint32 displayId = (*itr)->Values[0];
+ uint32 pathLength = fileName.size();
+ fwrite(&displayId, sizeof(uint32), 1, modelList);
+ fwrite(&pathLength, sizeof(uint32), 1, modelList);
+ fwrite(fileName.c_str(), sizeof(char), pathLength, modelList);
+ }
+ else if (extension == "wmo")
+ {
+ WorldModelRoot model(path);
+
+ FILE* output = fopen((baseBuildingsPath + fileName).c_str(), "wb");
+ if (!output)
+ {
+ printf("Could not create file %s, please check that you have write permissions\n", (baseBuildingsPath + fileName).c_str());
+ continue;
+ }
+
+ fwrite(Constants::VMAPMagic, 1, 8, output);
+ uint32 numVertices = 0;
+ fwrite(&numVertices, sizeof(uint32), 1, output); // will be filled later
+ fwrite(&model.Header.CountGroups, sizeof(uint32), 1, output);
+ fwrite(&model.Header.WmoId, sizeof(uint32), 1, output);
+
+ for (std::vector<WorldModelGroup>::iterator itr2 = model.Groups.begin(); itr2 != model.Groups.end(); ++itr2)
+ {
+ fwrite(&itr2->Header.Flags, sizeof(uint32), 1, output);
+ fwrite(&itr2->Header.WmoId, sizeof(uint32), 1, output);
+ fwrite(&itr2->Header.BoundingBox[0], sizeof(uint32), 1, output);
+ fwrite(&itr2->Header.BoundingBox[1], sizeof(uint32), 1, output);
+ uint32 LiquidFlags = itr2->HasLiquidData ? 1 : 0;
+ fwrite(&LiquidFlags, sizeof(uint32), 1, output);
+
+ fwrite("GRP ", sizeof(char), 4, output);
+ uint32 k = 0;
+ uint32 mobaBatch = itr2->MOBALength / 12;
+ uint32* MobaEx = new uint32[mobaBatch*4];
+
+ for(uint32 i = 8; i < itr2->MOBALength; i += 12)
+ MobaEx[k++] = itr2->MOBA[i];
+
+ int mobaSizeGrp = mobaBatch * 4 + 4;
+ fwrite(&mobaSizeGrp, 4, 1, output);
+ fwrite(&mobaBatch, 4, 1, output);
+ fwrite(MobaEx, 4, k, output);
+ delete[] MobaEx;
+
+ // Note: still not finished
+ }
+
+ fclose(output);
+ }
+ }
+
+ fclose(modelList);
+ printf("GameObject models extraction finished!");
+ Constants::ToWoWCoords = false;
+}
+
+bool HandleArgs(int argc, char** argv, uint32& threads, std::set<uint32>& mapList, bool& debugOutput, uint32& extractFlags)
+{
+ char* param = NULL;
+ extractFlags = 0;
+
+ for (int i = 1; i < argc; ++i)
+ {
+ if (strcmp(argv[i], "--threads") == 0)
+ {
+ param = argv[++i];
+ if (!param)
+ return false;
+
+ threads = atoi(param);
+ printf("Using %i threads\n", threads);
+ }
+ else if (strcmp(argv[i], "--maps") == 0)
+ {
+ param = argv[++i];
+ if (!param)
+ return false;
+ std::string maps = std::string(param);
+ Tokenizer tokens(maps, ',');
+
+ for (Tokenizer::const_iterator itr = tokens.begin(); itr != tokens.end(); ++itr)
+ mapList.insert(atoi(*itr));
+
+ printf("Extracting only provided list of maps (%u).\n", uint32(mapList.size()));
+ }
+ else if (strcmp(argv[i], "--debug") == 0)
+ {
+ param = argv[++i];
+ if (!param)
+ return false;
+ debugOutput = atoi(param);
+ if (debugOutput)
+ printf("Output will contain debug information (.obj files)\n");
+ }
+ else if (strcmp(argv[i], "--extract") == 0)
+ {
+ param = argv[++i];
+ if (!param)
+ return false;
+
+ extractFlags = atoi(param);
+
+ if (!(extractFlags & Constants::EXTRACT_FLAG_ALLOWED)) // Tried to use an invalid flag
+ return false;
+
+ printf("Detected flags: \n");
+ printf("* Extract DBCs: %s\n", (extractFlags & Constants::EXTRACT_FLAG_DBC) ? "Yes" : "No");
+ printf("* Extract Maps: %s\n", (extractFlags & Constants::EXTRACT_FLAG_MAPS) ? "Yes" : "No");
+ printf("* Extract VMaps: %s\n", (extractFlags & Constants::EXTRACT_FLAG_VMAPS) ? "Yes" : "No");
+ printf("* Extract GameObject Models: %s\n", (extractFlags & Constants::EXTRACT_FLAG_GOB_MODELS) ? "Yes" : "No");
+ printf("* Extract MMaps: %s\n", (extractFlags & Constants::EXTRACT_FLAG_MMAPS) ? "Yes" : "No");
+ }
+ }
+ return true;
+}
+
+void PrintUsage()
+{
+ printf("MeshExtractor help.\n");
+ printf("* Use \"--threads <number>\" to specify <number> threads, default to 4 (Only available when extracting MMaps)\n");
+ printf("* Use \"--maps a,b,c,d,e\" to extract only the maps specified (Do not use spaces)\n");
+ printf("* Use \"--debug 1\" to generate debug information of the tiles (Only available when extracting MMaps)\n");
+ printf("* Use \"--extract X\" to extract the data specified by the flag X (Note: You can combine the flags with the bitwise OR operator |). Available flags are: \n");
+ {
+ printf("- %u to extract DBCs\n", Constants::EXTRACT_FLAG_DBC);
+ printf("- %u to extract Maps (Not yet implemented)\n", Constants::EXTRACT_FLAG_MAPS);
+ printf("- %u to extract VMaps (Not yet implemented)\n", Constants::EXTRACT_FLAG_VMAPS);
+ printf("- %u to extract GameObject models (Not yet finished, you need to run VMapAssembler on the extracted files)\n", Constants::EXTRACT_FLAG_GOB_MODELS);
+ printf("- %u to extract MMaps (Not yet finished)\n", Constants::EXTRACT_FLAG_MMAPS);
+ }
+}
+
+void LoadTile(dtNavMesh*& navMesh, const char* tile)
+{
+ FILE* f = fopen(tile, "rb");
+ MmapTileHeader header;
+
+ if (fread(&header, sizeof(MmapTileHeader), 1, f) != 1)
+ return;
+
+ uint8* nav = new uint8[header.size];
+ if (fread(nav, header.size, 1, f) != 1)
+ return;
+
+ navMesh->addTile(nav, header.size, DT_TILE_FREE_DATA, 0, NULL);
+
+ fclose(f);
+}
+
+int main(int argc, char* argv[])
+{
+ if (!system("pause"))
+ {
+ printf("main: Error in system call to pause\n");
+ return -1;
+ }
+
+ uint32 threads = 4, extractFlags = 0;
+ std::set<uint32> mapIds;
+ bool debug = false;
+
+ if (!HandleArgs(argc, argv, threads, mapIds, debug, extractFlags))
+ {
+ PrintUsage();
+ return -1;
+ }
+
+ Cache = new CacheClass();
+ MPQHandler = new MPQManager();
+ MPQHandler->Initialize();
+
+ if (extractFlags & Constants::EXTRACT_FLAG_DBC)
+ ExtractDBCs();
+
+ if (extractFlags & Constants::EXTRACT_FLAG_MMAPS)
+ ExtractMMaps(mapIds, threads, debug);
+
+ if (extractFlags & Constants::EXTRACT_FLAG_GOB_MODELS)
+ ExtractGameobjectModels();
+
+ if (extractFlags & Constants::EXTRACT_FLAG_TEST)
+ {
+ float start[] = { 0.0f, 0.0f, 0.0f };
+ float end[] = { 0.0f, 0.0f, 0.0f };
+
+ //
+ float m_spos[3];
+ m_spos[0] = -1.0f * start[1];
+ m_spos[1] = start[2];
+ m_spos[2] = -1.0f * start[0];
+
+ //
+ float m_epos[3];
+ m_epos[0] = -1.0f * end[1];
+ m_epos[1] = end[2];
+ m_epos[2] = -1.0f * end[0];
+
+ //
+ dtQueryFilter m_filter;
+ m_filter.setIncludeFlags(0xffff) ;
+ m_filter.setExcludeFlags(0);
+
+ //
+ float m_polyPickExt[3];
+ m_polyPickExt[0] = 2;
+ m_polyPickExt[1] = 4;
+ m_polyPickExt[2] = 2;
+
+ //
+ dtPolyRef m_startRef;
+ dtPolyRef m_endRef;
+
+ FILE* mmap = fopen(".mmap", "rb");
+ dtNavMeshParams params;
+ int count = fread(&params, sizeof(dtNavMeshParams), 1, mmap);
+ fclose(mmap);
+ if (count != 1)
+ {
+ printf("main: Error reading from .mmap\n");
+ return 0;
+ }
+
+ dtNavMesh* navMesh = new dtNavMesh();
+ dtNavMeshQuery* navMeshQuery = new dtNavMeshQuery();
+
+ navMesh->init(&params);
+ LoadTile(navMesh, ".mmtile");
+ LoadTile(navMesh, ".mmtile");
+ LoadTile(navMesh, ".mmtile");
+ LoadTile(navMesh, ".mmtile");
+ LoadTile(navMesh, ".mmtile");
+ LoadTile(navMesh, ".mmtile");
+
+ navMeshQuery->init(navMesh, 2048);
+
+ float nearestPt[3];
+
+ navMeshQuery->findNearestPoly(m_spos, m_polyPickExt, &m_filter, &m_startRef, nearestPt);
+ navMeshQuery->findNearestPoly(m_epos, m_polyPickExt, &m_filter, &m_endRef, nearestPt);
+
+ if ( !m_startRef || !m_endRef )
+ {
+ std::cerr << "Could not find any nearby poly's (" << m_startRef << "," << m_endRef << ")" << std::endl;
+ return 0;
+ }
+
+ printf("Found!");
+ }
+
+ return 0;
+}
diff --git a/src/tools/mesh_extractor/Model.cpp b/src/tools/mesh_extractor/Model.cpp
new file mode 100644
index 00000000000..77b1adbeaa0
--- /dev/null
+++ b/src/tools/mesh_extractor/Model.cpp
@@ -0,0 +1,67 @@
+#include "Model.h"
+#include "MPQManager.h"
+#include "Utils.h"
+
+Model::Model( std::string path ) : IsCollidable(false), IsBad(false)
+{
+ Stream = MPQHandler->GetFile(Utils::FixModelPath(path));
+ if (!Stream)
+ {
+ IsBad = true;
+ return;
+ }
+ 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);
+ }
+}
+
+Model::~Model()
+{
+ if (Stream)
+ fclose(Stream);
+}
+
+void Model::ReadVertices( FILE* stream )
+{
+ fseek(stream, Header.OffsetBoundingVertices, SEEK_SET);
+ Vertices.reserve(Header.CountBoundingVertices);
+ for (uint32 i = 0; i < Header.CountBoundingVertices; ++i)
+ {
+ Vertices.push_back(Vector3::Read(stream));
+ if (Constants::ToWoWCoords)
+ Vertices[i] = Utils::ToWoWCoords(Vertices[i]);
+ }
+}
+
+void Model::ReadBoundingTriangles( FILE* stream )
+{
+ fseek(stream, Header.OffsetBoundingTriangles, SEEK_SET);
+ Triangles.reserve(Header.CountBoundingTriangles / 3);
+ for (uint32 i = 0; i < Header.CountBoundingTriangles / 3; i++)
+ {
+ Triangle<uint16> tri;
+ tri.Type = Constants::TRIANGLE_TYPE_DOODAD;
+ int count = 0;
+ count += fread(&tri.V0, sizeof(uint16), 1, stream);
+ count += fread(&tri.V1, sizeof(uint16), 1, stream);
+ count += fread(&tri.V2, sizeof(uint16), 1, stream);
+ if (count != 3)
+ printf("Model::ReadBoundingTriangles: Error reading data, expected 3, read %d\n", count);
+ Triangles.push_back(tri);
+ }
+}
+
+void Model::ReadBoundingNormals( FILE* stream )
+{
+ fseek(stream, Header.OffsetBoundingNormals, SEEK_SET);
+ Normals.reserve(Header.CountBoundingNormals);
+ for (uint32 i = 0; i < Header.CountBoundingNormals; i++)
+ Normals.push_back(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..ea9331e7c30
--- /dev/null
+++ b/src/tools/mesh_extractor/Model.h
@@ -0,0 +1,23 @@
+#ifndef MODEL_H
+#define MODEL_H
+#include <vector>
+#include "Utils.h"
+
+class Model
+{
+public:
+ Model(std::string path);
+ ~Model();
+
+ 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;
+ bool IsBad;
+};
+#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..789efc6d62c
--- /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 (uint32(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..75b4e45700c
--- /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..4a14cbbd51d
--- /dev/null
+++ b/src/tools/mesh_extractor/TileBuilder.cpp
@@ -0,0 +1,311 @@
+#include "ContinentBuilder.h"
+#include "TileBuilder.h"
+#include "Geometry.h"
+#include "Constants.h"
+#include "Utils.h"
+#include "Cache.h"
+#include "ADT.h"
+#include "WDT.h"
+#include "Recast.h"
+#include "RecastAlloc.h"
+#include "DetourNavMeshBuilder.h"
+
+#include "ace/Synch.h"
+
+TileBuilder::TileBuilder(ContinentBuilder* _cBuilder, std::string world, int x, int y, uint32 mapId) :
+ World(world), X(x), Y(y), MapId(mapId), _Geometry(NULL), DataSize(0), cBuilder(_cBuilder)
+{
+ /*
+ Test, non-working values
+ // Cell Size = TileSize / TileVoxelSize
+ // 1800 = TileVoxelSize
+ Config.cs = Constants::TileSize / 1800;
+ // Cell Height
+ Config.ch = 0.4f;
+ // Min Region Area = 20^2
+ Config.minRegionArea = 20*20;
+ // Merge Region Area = 40^2
+ Config.mergeRegionArea = 40*40;
+ Config.tileSize = Constants::TileSize / 4;
+ Config.walkableSlopeAngle = 50.0f;
+ Config.detailSampleDist = 3.0f;
+ Config.detailSampleMaxError = 1.25f;
+ Config.walkableClimb = floorf(1.0f / Config.ch);
+ Config.walkableHeight = ceilf(1.652778f / Config.ch);
+ Config.walkableRadius = ceilf(0.2951389f / Config.cs);
+ Config.maxEdgeLen = Config.walkableRadius * 8;
+ Config.borderSize = Config.walkableRadius + 4;
+ Config.width = 1800 + Config.borderSize * 2;
+ Config.height = 1800 + Config.borderSize * 2;
+ Config.maxVertsPerPoly = 6;
+ Config.maxSimplificationError = 1.3f;
+ */
+
+ // All are in UNIT metrics!
+ memset(&Config, 0, sizeof(rcConfig));
+
+ Config.maxVertsPerPoly = DT_VERTS_PER_POLYGON;
+ Config.cs = Constants::BaseUnitDim;
+ Config.ch = Constants::BaseUnitDim;
+ Config.walkableSlopeAngle = 60.0f;
+ Config.tileSize = Constants::VertexPerTile;
+ Config.walkableRadius = 1;
+ Config.borderSize = Config.walkableRadius + 3;
+ Config.maxEdgeLen = Constants::VertexPerTile + 1; //anything bigger than tileSize
+ Config.walkableHeight = 3;
+ Config.walkableClimb = 2; // keep less than walkableHeight
+ Config.minRegionArea = rcSqr(60);
+ Config.mergeRegionArea = rcSqr(50);
+ Config.maxSimplificationError = 2.0f; // eliminates most jagged edges (tinny polygons)
+ Config.detailSampleDist = Config.cs * 64;
+ Config.detailSampleMaxError = Config.ch * 2;
+
+ Context = new rcContext;
+}
+
+void TileBuilder::CalculateTileBounds( float*& bmin, float*& bmax, dtNavMeshParams& /*navMeshParams*/ )
+{
+ bmin = new float[3];
+ bmax = new float[3];
+ bmin[0] = Constants::Origin[0] /*navMeshParams.orig[0]*/ + (Constants::TileSize * X);
+ bmin[2] = Constants::Origin[2] /*navMeshParams.orig[2]*/ + (Constants::TileSize * Y);
+ bmax[0] = Constants::Origin[0] /*navMeshParams.orig[0]*/ + (Constants::TileSize * (X + 1));
+ bmax[2] = Constants::Origin[2] /*navMeshParams.orig[2]*/ + (Constants::TileSize * (Y + 1));
+}
+
+uint8* TileBuilder::Build(bool dbg, dtNavMeshParams& navMeshParams)
+{
+ _Geometry = new Geometry();
+ _Geometry->Transform = true;
+ ADT* adt = new ADT(Utils::GetAdtPath(World, X, Y));
+ adt->Read();
+ _Geometry->AddAdt(adt);
+ delete adt;
+
+ if (_Geometry->Vertices.empty() && _Geometry->Triangles.empty())
+ return NULL;
+
+ // again, we load everything - wasteful but who cares
+ for (int ty = Y - 2; ty <= Y + 2; ty++)
+ {
+ for (int tx = X - 2; tx <= X + 2; tx++)
+ {
+ // don't load main tile again
+ if (tx == X && ty == Y)
+ continue;
+
+ ADT* _adt = new ADT(Utils::GetAdtPath(World, tx, ty));
+ // If this condition is met, it means that this wdt does not contain the ADT
+ if (!_adt->Data->Stream)
+ {
+ delete _adt;
+ continue;
+ }
+ _adt->Read();
+ _Geometry->AddAdt(_adt);
+ delete _adt;
+ }
+ }
+
+ if (dbg)
+ {
+ char buff[100];
+ sprintf(buff, "mmaps/%s_%02u%02u.obj", World.c_str(), Y, X);
+ FILE* debug = fopen(buff, "wb");
+ for (uint32 i = 0; i < _Geometry->Vertices.size(); ++i)
+ fprintf(debug, "v %f %f %f\n", _Geometry->Vertices[i].x, _Geometry->Vertices[i].y, _Geometry->Vertices[i].z);
+ for (uint32 i = 0; i < _Geometry->Triangles.size(); ++i)
+ fprintf(debug, "f %i %i %i\n", _Geometry->Triangles[i].V0 + 1, _Geometry->Triangles[i].V1 + 1, _Geometry->Triangles[i].V2 + 1);
+ fclose(debug);
+ }
+
+ uint32 numVerts = _Geometry->Vertices.size();
+ uint32 numTris = _Geometry->Triangles.size();
+ float* vertices;
+ int* triangles;
+ uint8* areas;
+ _Geometry->GetRawData(vertices, triangles, areas);
+ _Geometry->Vertices.clear();
+ _Geometry->Triangles.clear();
+
+
+ rcVcopy(Config.bmin, cBuilder->bmin);
+ rcVcopy(Config.bmax, cBuilder->bmax);
+
+ // this sets the dimensions of the heightfield - should maybe happen before border padding
+ rcCalcGridSize(Config.bmin, Config.bmax, Config.cs, &Config.width, &Config.height);
+
+ // Initialize per tile config.
+ rcConfig tileCfg = Config;
+ tileCfg.width = Config.tileSize + Config.borderSize * 2;
+ tileCfg.height = Config.tileSize + Config.borderSize * 2;
+
+ // merge per tile poly and detail meshes
+ rcPolyMesh** pmmerge = new rcPolyMesh*[Constants::TilesPerMap * Constants::TilesPerMap];
+ rcPolyMeshDetail** dmmerge = new rcPolyMeshDetail*[Constants::TilesPerMap * Constants::TilesPerMap];
+
+ int nmerge = 0;
+ for (int y = 0; y < Constants::TilesPerMap; ++y)
+ {
+ for (int x = 0; x < Constants::TilesPerMap; ++x)
+ {
+ // Calculate the per tile bounding box.
+ tileCfg.bmin[0] = Config.bmin[0] + float(x * Config.tileSize - Config.borderSize) * Config.cs;
+ tileCfg.bmin[2] = Config.bmin[2] + float(y * Config.tileSize - Config.borderSize) * Config.cs;
+ tileCfg.bmax[0] = Config.bmin[0] + float((x + 1) * Config.tileSize + Config.borderSize) * Config.cs;
+ tileCfg.bmax[2] = Config.bmin[2] + float((y + 1) * Config.tileSize + Config.borderSize) * Config.cs;
+
+
+ rcHeightfield* hf = rcAllocHeightfield();
+ rcCreateHeightfield(Context, *hf, tileCfg.width, tileCfg.height, tileCfg.bmin, tileCfg.bmax, tileCfg.cs, tileCfg.ch);
+ rcClearUnwalkableTriangles(Context, tileCfg.walkableSlopeAngle, vertices, numVerts, triangles, numTris, areas);
+ rcRasterizeTriangles(Context, vertices, numVerts, triangles, areas, numTris, *hf, Config.walkableClimb);
+
+ // Once all geometry is rasterized, we do initial pass of filtering to
+ // remove unwanted overhangs caused by the conservative rasterization
+ // as well as filter spans where the character cannot possibly stand.
+ rcFilterLowHangingWalkableObstacles(Context, Config.walkableClimb, *hf);
+ rcFilterLedgeSpans(Context, tileCfg.walkableHeight, tileCfg.walkableClimb, *hf);
+ rcFilterWalkableLowHeightSpans(Context, tileCfg.walkableHeight, *hf);
+
+ // Compact the heightfield so that it is faster to handle from now on.
+ // This will result in more cache coherent data as well as the neighbours
+ // between walkable cells will be calculated.
+ rcCompactHeightfield* chf = rcAllocCompactHeightfield();
+ rcBuildCompactHeightfield(Context, tileCfg.walkableHeight, tileCfg.walkableClimb, *hf, *chf);
+
+ rcFreeHeightField(hf);
+
+ // Erode the walkable area by agent radius.
+ rcErodeWalkableArea(Context, Config.walkableRadius, *chf);
+ // Prepare for region partitioning, by calculating distance field along the walkable surface.
+ rcBuildDistanceField(Context, *chf);
+ // Partition the walkable surface into simple regions without holes.
+ rcBuildRegions(Context, *chf, tileCfg.borderSize, tileCfg.minRegionArea, tileCfg.mergeRegionArea);
+
+ // Create contours.
+ rcContourSet* cset = rcAllocContourSet();
+ rcBuildContours(Context, *chf, tileCfg.maxSimplificationError, tileCfg.maxEdgeLen, *cset);
+
+ // Build polygon navmesh from the contours.
+ rcPolyMesh* pmesh = rcAllocPolyMesh();
+ rcBuildPolyMesh(Context, *cset, tileCfg.maxVertsPerPoly, *pmesh);
+
+ // Build detail mesh.
+ rcPolyMeshDetail* dmesh = rcAllocPolyMeshDetail();
+ rcBuildPolyMeshDetail(Context, *pmesh, *chf, tileCfg.detailSampleDist, tileCfg.detailSampleMaxError, *dmesh);
+
+ // Free memory
+ rcFreeCompactHeightfield(chf);
+ rcFreeContourSet(cset);
+
+ pmmerge[nmerge] = pmesh;
+ dmmerge[nmerge] = dmesh;
+ ++nmerge;
+ }
+ }
+
+ rcPolyMesh* pmesh = rcAllocPolyMesh();
+ rcMergePolyMeshes(Context, pmmerge, nmerge, *pmesh);
+
+ rcPolyMeshDetail* dmesh = rcAllocPolyMeshDetail();
+ rcMergePolyMeshDetails(Context, dmmerge, nmerge, *dmesh);
+
+ delete[] pmmerge;
+ delete[] dmmerge;
+
+ printf("[%02i,%02i] Meshes merged!\n", X, Y);
+
+ // Remove padding from the polymesh data. (Remove this odditity)
+ for (int i = 0; i < pmesh->nverts; ++i)
+ {
+ unsigned short* v = &pmesh->verts[i * 3];
+ v[0] -= (unsigned short)Config.borderSize;
+ v[2] -= (unsigned short)Config.borderSize;
+ }
+
+ // Set flags according to area types (e.g. Swim for Water)
+ for (int i = 0; i < pmesh->npolys; i++)
+ {
+ if (pmesh->areas[i] == Constants::POLY_AREA_ROAD || pmesh->areas[i] == Constants::POLY_AREA_TERRAIN)
+ pmesh->flags[i] = Constants::POLY_FLAG_WALK;
+ else if (pmesh->areas[i] == Constants::POLY_AREA_WATER)
+ pmesh->flags[i] = Constants::POLY_FLAG_SWIM;
+ }
+
+ dtNavMeshCreateParams params;
+ memset(&params, 0, sizeof(params));
+ // PolyMesh data
+ params.verts = pmesh->verts;
+ params.vertCount = pmesh->nverts;
+ params.polys = pmesh->polys;
+ params.polyAreas = pmesh->areas;
+ params.polyFlags = pmesh->flags;
+ params.polyCount = pmesh->npolys;
+ params.nvp = pmesh->nvp;
+ // PolyMeshDetail data
+ params.detailMeshes = dmesh->meshes;
+ params.detailVerts = dmesh->verts;
+ params.detailVertsCount = dmesh->nverts;
+ params.detailTris = dmesh->tris;
+ params.detailTriCount = dmesh->ntris;
+ rcVcopy(params.bmin, pmesh->bmin);
+ rcVcopy(params.bmax, pmesh->bmax);
+ // General settings
+ params.ch = Config.ch;
+ params.cs = Config.cs;
+ params.walkableClimb = Constants::BaseUnitDim * Config.walkableClimb;
+ params.walkableHeight = Constants::BaseUnitDim * Config.walkableHeight;
+ params.walkableRadius = Constants::BaseUnitDim * Config.walkableRadius;
+ params.tileX = (((cBuilder->bmin[0] + cBuilder->bmax[0]) / 2) - navMeshParams.orig[0]) / Constants::TileSize;
+ params.tileY = (((cBuilder->bmin[2] + cBuilder->bmax[2]) / 2) - navMeshParams.orig[2]) / Constants::TileSize;
+
+ rcVcopy(params.bmin, cBuilder->bmin);
+ rcVcopy(params.bmax, cBuilder->bmax);
+
+ // Offmesh-connection settings
+ params.offMeshConCount = 0; // none for now
+
+ params.tileSize = Constants::VertexPerMap;
+
+ if (!params.polyCount || !params.polys || Constants::TilesPerMap * Constants::TilesPerMap == params.polyCount)
+ {
+ // we have flat tiles with no actual geometry - don't build those, its useless
+ // keep in mind that we do output those into debug info
+ // drop tiles with only exact count - some tiles may have geometry while having less tiles
+ printf("[%02i,%02i] No polygons to build on tile, skipping.\n", X, Y);
+ rcFreePolyMesh(pmesh);
+ rcFreePolyMeshDetail(dmesh);
+ delete areas;
+ delete triangles;
+ delete vertices;
+ return NULL;
+ }
+
+ int navDataSize;
+ uint8* navData;
+ printf("[%02i,%02i] Creating the navmesh with %i vertices, %i polys, %i triangles!\n", X, Y, pmesh->nverts, pmesh->npolys, dmesh->ntris);
+ bool result = dtCreateNavMeshData(&params, &navData, &navDataSize);
+
+ // Free some memory
+ rcFreePolyMesh(pmesh);
+ rcFreePolyMeshDetail(dmesh);
+ delete areas;
+ delete triangles;
+ delete vertices;
+
+ if (result)
+ {
+ printf("[%02i,%02i] NavMesh created, size %i!\n", X, Y, navDataSize);
+ DataSize = navDataSize;
+ return navData;
+ }
+
+ return NULL;
+}
+
+TileBuilder::~TileBuilder()
+{
+ delete Context;
+ delete _Geometry;
+}
diff --git a/src/tools/mesh_extractor/TileBuilder.h b/src/tools/mesh_extractor/TileBuilder.h
new file mode 100644
index 00000000000..40c96f6ec42
--- /dev/null
+++ b/src/tools/mesh_extractor/TileBuilder.h
@@ -0,0 +1,30 @@
+#ifndef TILE_BUILD_H
+#define TILE_BUILD_H
+#include <string>
+#include "Recast.h"
+
+#include "Geometry.h"
+
+class ContinentBuilder;
+class WDT;
+
+class TileBuilder
+{
+public:
+ TileBuilder(ContinentBuilder* _cBuilder, std::string world, int x, int y, uint32 mapId);
+ ~TileBuilder();
+
+ void CalculateTileBounds(float*& bmin, float*& bmax, dtNavMeshParams& navMeshParams);
+ uint8* Build(bool dbg, dtNavMeshParams& navMeshParams);
+
+ std::string World;
+ int X;
+ int Y;
+ int MapId;
+ rcConfig Config;
+ rcContext* Context;
+ Geometry* _Geometry;
+ uint32 DataSize;
+ ContinentBuilder* cBuilder;
+};
+#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..119ac94f94f
--- /dev/null
+++ b/src/tools/mesh_extractor/Utils.cpp
@@ -0,0 +1,568 @@
+#include "Utils.h"
+#include "WorldModelHandler.h"
+#include "Constants.h"
+#include <cstring>
+#include "G3D/Matrix4.h"
+#include "G3D/Quat.h"
+
+#ifdef _WIN32
+ #include "direct.h"
+#else
+ #include <sys/stat.h>
+ #include <unistd.h>
+#endif
+
+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;
+const float Constants::MaxStandableHeight = 1.5f;
+const char* Constants::VMAPMagic = "VMAP041";
+bool Constants::ToWoWCoords = false;
+const float Constants::BaseUnitDim = 0.533333f;
+const int Constants::VertexPerMap = (Constants::TileSize / Constants::BaseUnitDim) + 0.5f;
+const int Constants::VertexPerTile = 40;
+const int Constants::TilesPerMap = Constants::VertexPerMap / Constants::VertexPerTile;
+
+void Utils::CreateDir( const std::string& Path )
+{
+#ifdef _WIN32
+ _mkdir( Path.c_str());
+#else
+ mkdir( Path.c_str(), 0777 );
+#endif
+}
+
+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;
+ if (fread(&b, sizeof(char), 1, file) != 1 || 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 )
+{
+ return Utils::GetPathBase(path) + ".M2";
+}
+
+G3D::Matrix4 Utils::RotationX(float angle)
+{
+ float _cos = cos(angle);
+ float _sin = sin(angle);
+ G3D::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);
+ G3D::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);
+ G3D::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::Matrix4 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 )
+{
+ size_t lastIndex = path.find_last_of(".");
+ if (lastIndex != std::string::npos)
+ return path.substr(0, lastIndex);
+ return path;
+}
+
+Vector3 Vector3::Read( FILE* file )
+{
+ Vector3 ret;
+ if (fread(&ret, sizeof(Vector3), 1, file) != 1)
+ printf("Vector3::Read: Failed to read some data expected 1, read 0\n");
+ 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);
+}
+
+std::string Utils::Replace( std::string str, const std::string& oldStr, const std::string& newStr )
+{
+ size_t pos = 0;
+ while((pos = str.find(oldStr, pos)) != std::string::npos)
+ {
+ str.replace(pos, oldStr.length(), newStr);
+ pos += newStr.length();
+ }
+ return str;
+}
+
+G3D::Matrix4 Utils::GetWmoDoodadTransformation( DoodadInstance inst, WorldModelDefinition root )
+{
+ G3D::Matrix4 rootTransformation = Utils::GetTransformation(root);
+ G3D::Matrix4 translation = G3D::Matrix4::translation(inst.Position.x, inst.Position.y, inst.Position.z);
+ G3D::Matrix4 scale = G3D::Matrix4::scale(inst.Scale);
+ G3D::Matrix4 rotation = Utils::RotationY(Constants::PI);
+ G3D::Quat quat(-inst.QuatY, inst.QuatZ, -inst.QuatX, inst.QuatW);
+ G3D::Matrix4 quatRotation = quat.toRotationMatrix();
+
+ return scale * rotation * quatRotation ** translation * rootTransformation;
+}
+
+void Utils::SaveToDisk( FILE* stream, std::string path )
+{
+ FILE* disk = fopen(path.c_str(), "wb");
+ if (!disk)
+ {
+ printf("SaveToDisk: Could not save file %s to disk, please verify that you have write permissions on that directory\n", path.c_str());
+ return;
+ }
+
+ uint32 size = Utils::Size(stream);
+ uint8* data = new uint8[size];
+ // Read the data to an array
+ if (fread(data, 1, size, stream) != 1)
+ {
+ printf("SaveToDisk: Error reading from Stream while trying to save file %s to disck.\n", path.c_str());
+ return;
+ }
+ // And write it in the file
+ fwrite(data, 1, size, disk);
+
+ // Close the filestream
+ fclose(disk);
+ // Free the used memory
+ delete data;
+}
+
+Vector3 Utils::ToWoWCoords( Vector3 vec )
+{
+ return Vector3(vec.x, -vec.z, vec.y);
+}
+
+std::string Utils::GetExtension( std::string path )
+{
+ std::string::size_type idx = path.rfind('.');
+ std::string extension = "";
+
+ if(idx != std::string::npos)
+ extension = path.substr(idx+1);
+ return extension;
+}
+
+void MapChunkHeader::Read(FILE* stream)
+{
+ int count = 0;
+
+ count += fread(&Flags, sizeof(uint32), 1, stream);
+ count += fread(&IndexX, sizeof(uint32), 1, stream);
+ count += fread(&IndexY, sizeof(uint32), 1, stream);
+ count += fread(&Layers, sizeof(uint32), 1, stream);
+ count += fread(&DoodadRefs, sizeof(uint32), 1, stream);
+ count += fread(&OffsetMCVT, sizeof(uint32), 1, stream);
+ count += fread(&OffsetMCNR, sizeof(uint32), 1, stream);
+ count += fread(&OffsetMCLY, sizeof(uint32), 1, stream);
+ count += fread(&OffsetMCRF, sizeof(uint32), 1, stream);
+ count += fread(&OffsetMCAL, sizeof(uint32), 1, stream);
+ count += fread(&SizeMCAL, sizeof(uint32), 1, stream);
+ count += fread(&OffsetMCSH, sizeof(uint32), 1, stream);
+ count += fread(&SizeMCSH, sizeof(uint32), 1, stream);
+ count += fread(&AreaId, sizeof(uint32), 1, stream);
+ count += fread(&MapObjectRefs, sizeof(uint32), 1, stream);
+ count += fread(&Holes, sizeof(uint32), 1, stream);
+ LowQualityTextureMap = new uint32[4];
+ count += fread(LowQualityTextureMap, sizeof(uint32), 4, stream);
+ count += fread(&PredTex, sizeof(uint32), 1, stream);
+ count += fread(&NumberEffectDoodad, sizeof(uint32), 1, stream);
+ count += fread(&OffsetMCSE, sizeof(uint32), 1, stream);
+ count += fread(&SoundEmitters, sizeof(uint32), 1, stream);
+ count += fread(&OffsetMCLQ, sizeof(uint32), 1, stream);
+ count += fread(&SizeMCLQ, sizeof(uint32), 1, stream);
+ Position = Vector3::Read(stream);
+ count += fread(&OffsetMCCV, sizeof(uint32), 1, stream);
+
+ if (count != 27)
+ printf("MapChunkHeader::Read: Failed to read some data expected 27, read %d\n", count);
+}
+
+void MHDR::Read(FILE* stream)
+{
+ int count = 0;
+
+ count += fread(&Flags, sizeof(uint32), 1, stream);
+ count += fread(&OffsetMCIN, sizeof(uint32), 1, stream);
+ count += fread(&OffsetMTEX, sizeof(uint32), 1, stream);
+ count += fread(&OffsetMMDX, sizeof(uint32), 1, stream);
+ count += fread(&OffsetMMID, sizeof(uint32), 1, stream);
+ count += fread(&OffsetMWMO, sizeof(uint32), 1, stream);
+ count += fread(&OffsetMWID, sizeof(uint32), 1, stream);
+ count += fread(&OffsetMDDF, sizeof(uint32), 1, stream);
+ count += fread(&OffsetMODF, sizeof(uint32), 1, stream);
+ count += fread(&OffsetMFBO, sizeof(uint32), 1, stream);
+ count += fread(&OffsetMH2O, sizeof(uint32), 1, stream);
+ count += fread(&OffsetMTFX, sizeof(uint32), 1, stream);
+
+ if (count != 12)
+ printf("MHDR::Read: Failed to read some data expected 12, read %d\n", count);
+}
+
+void ModelHeader::Read(FILE* stream)
+{
+ int count = 0;
+
+ count += fread(&Magic, sizeof(char), 4, stream);
+ Magic[4] = '\0'; // null-terminate it.
+ count += fread(&Version, sizeof(uint32), 1, stream);
+ count += fread(&LengthModelName, sizeof(uint32), 1, stream);
+ count += fread(&OffsetName, sizeof(uint32), 1, stream);
+ count += fread(&ModelFlags, sizeof(uint32), 1, stream);
+ count += fread(&CountGlobalSequences, sizeof(uint32), 1, stream);
+ count += fread(&OffsetGlobalSequences, sizeof(uint32), 1, stream);
+ count += fread(&CountAnimations, sizeof(uint32), 1, stream);
+ count += fread(&OffsetAnimations, sizeof(uint32), 1, stream);
+ count += fread(&CountAnimationLookup, sizeof(uint32), 1, stream);
+ count += fread(&OffsetAnimationLookup, sizeof(uint32), 1, stream);
+ count += fread(&CountBones, sizeof(uint32), 1, stream);
+ count += fread(&OffsetBones, sizeof(uint32), 1, stream);
+ count += fread(&CountKeyBoneLookup, sizeof(uint32), 1, stream);
+ count += fread(&OffsetKeyBoneLookup, sizeof(uint32), 1, stream);
+ count += fread(&CountVertices, sizeof(uint32), 1, stream);
+ count += fread(&OffsetVertices, sizeof(uint32), 1, stream);
+ count += fread(&CountViews, sizeof(uint32), 1, stream);
+ count += fread(&CountColors, sizeof(uint32), 1, stream);
+ count += fread(&OffsetColors, sizeof(uint32), 1, stream);
+ count += fread(&CountTextures, sizeof(uint32), 1, stream);
+ count += fread(&OffsetTextures, sizeof(uint32), 1, stream);
+ count += fread(&CountTransparency, sizeof(uint32), 1, stream);
+ count += fread(&OffsetTransparency, sizeof(uint32), 1, stream);
+ count += fread(&CountUvAnimation, sizeof(uint32), 1, stream);
+ count += fread(&OffsetUvAnimation, sizeof(uint32), 1, stream);
+ count += fread(&CountTexReplace, sizeof(uint32), 1, stream);
+ count += fread(&OffsetTexReplace, sizeof(uint32), 1, stream);
+ count += fread(&CountRenderFlags, sizeof(uint32), 1, stream);
+ count += fread(&OffsetRenderFlags, sizeof(uint32), 1, stream);
+ count += fread(&CountBoneLookup, sizeof(uint32), 1, stream);
+ count += fread(&OffsetBoneLookup, sizeof(uint32), 1, stream);
+ count += fread(&CountTexLookup, sizeof(uint32), 1, stream);
+ count += fread(&OffsetTexLookup, sizeof(uint32), 1, stream);
+ count += fread(&CountTexUnits, sizeof(uint32), 1, stream);
+ count += fread(&OffsetTexUnits, sizeof(uint32), 1, stream);
+ count += fread(&CountTransLookup, sizeof(uint32), 1, stream);
+ count += fread(&OffsetTransLookup, sizeof(uint32), 1, stream);
+ count += fread(&CountUvAnimLookup, sizeof(uint32), 1, stream);
+ count += fread(&OffsetUvAnimLookup, sizeof(uint32), 1, stream);
+ VertexBox[0] = Vector3::Read(stream);
+ VertexBox[1] = Vector3::Read(stream);
+ count += fread(&VertexRadius, sizeof(float), 1, stream);
+ BoundingBox[0] = Vector3::Read(stream);
+ BoundingBox[1] = Vector3::Read(stream);
+ count += fread(&BoundingRadius, sizeof(float), 1, stream);
+ count += fread(&CountBoundingTriangles, sizeof(uint32), 1, stream);
+ count += fread(&OffsetBoundingTriangles, sizeof(uint32), 1, stream);
+ count += fread(&CountBoundingVertices, sizeof(uint32), 1, stream);
+ count += fread(&OffsetBoundingVertices, sizeof(uint32), 1, stream);
+ count += fread(&CountBoundingNormals, sizeof(uint32), 1, stream);
+ count += fread(&OffsetBoundingNormals, sizeof(uint32), 1, stream);
+
+ if (count != 51)
+ printf("ModelHeader::Read: Failed to read some data expected 51, read %d\n", count);
+
+}
+
+WorldModelHeader WorldModelHeader::Read(FILE* stream)
+{
+ WorldModelHeader ret;
+ int count = 0;
+
+ count += fread(&ret.CountMaterials, sizeof(uint32), 1, stream);
+ count += fread(&ret.CountGroups, sizeof(uint32), 1, stream);
+ count += fread(&ret.CountPortals, sizeof(uint32), 1, stream);
+ count += fread(&ret.CountLights, sizeof(uint32), 1, stream);
+ count += fread(&ret.CountModels, sizeof(uint32), 1, stream);
+ count += fread(&ret.CountDoodads, sizeof(uint32), 1, stream);
+ count += fread(&ret.CountSets, sizeof(uint32), 1, stream);
+ count += fread(&ret.AmbientColorUnk, sizeof(uint32), 1, stream);
+ count += fread(&ret.WmoId, sizeof(uint32), 1, stream);
+ ret.BoundingBox[0] = Vector3::Read(stream);
+ ret.BoundingBox[1] = Vector3::Read(stream);
+ count += fread(&ret.LiquidTypeRelated, sizeof(uint32), 1, stream);
+
+ if (count != 10)
+ printf("WorldModelHeader::Read: Failed to read some data expected 10, read %d\n", count);
+
+ return ret;
+}
+
+DoodadInstance DoodadInstance::Read(FILE* stream)
+{
+ DoodadInstance ret;
+ int count = 0;
+
+ count += fread(&ret.FileOffset, sizeof(uint32), 1, stream);
+ ret.Position = Vector3::Read(stream);
+ count += fread(&ret.QuatW, sizeof(float), 1, stream);
+ count += fread(&ret.QuatX, sizeof(float), 1, stream);
+ count += fread(&ret.QuatY, sizeof(float), 1, stream);
+ count += fread(&ret.QuatZ, sizeof(float), 1, stream);
+ count += fread(&ret.Scale, sizeof(float), 1, stream);
+ count += fread(&ret.LightColor, sizeof(uint32), 1, stream);
+
+ if (count != 7)
+ printf("DoodadInstance::Read: Failed to read some data expected 7, read %d\n", count);
+
+ return ret;
+}
+
+DoodadSet DoodadSet::Read(FILE* stream)
+{
+ DoodadSet ret;
+ char name[21];
+ int count = 0;
+
+ count += fread(&name, sizeof(char), 20, stream);
+ name[20] = '\0';
+ ret.Name = name;
+ count += fread(&ret.FirstInstanceIndex, sizeof(uint32), 1, stream);
+ count += fread(&ret.CountInstances, sizeof(uint32), 1, stream);
+ count += fread(&ret.UnknownZero, sizeof(uint32), 1, stream);
+
+ if (count != 23)
+ printf("DoodadSet::Read: Failed to read some data expected 23, read %d\n", count);
+
+ return ret;
+}
+
+LiquidHeader LiquidHeader::Read(FILE* stream)
+{
+ LiquidHeader ret;
+ int count = 0;
+ count += fread(&ret.CountXVertices, sizeof(uint32), 1, stream);
+ count += fread(&ret.CountYVertices, sizeof(uint32), 1, stream);
+ count += fread(&ret.Width, sizeof(uint32), 1, stream);
+ count += fread(&ret.Height, sizeof(uint32), 1, stream);
+ ret.BaseLocation = Vector3::Read(stream);
+ count += fread(&ret.MaterialId, sizeof(uint16), 1, stream);
+
+ if (count != 5)
+ printf("LiquidHeader::Read: Failed to read some data expected 5, read %d\n", count);
+
+ return ret;
+}
+
+LiquidData LiquidData::Read(FILE* stream, LiquidHeader& header)
+{
+ LiquidData ret;
+ ret.HeightMap = new float*[header.CountXVertices];
+ for (uint32 i = 0; i < header.CountXVertices; ++i)
+ ret.HeightMap[i] = new float[header.CountYVertices];
+
+ ret.RenderFlags = new uint8*[header.Width];
+ for (uint32 i = 0; i < header.Width; ++i)
+ ret.RenderFlags[i] = new uint8[header.Height];
+
+ for (uint32 y = 0; y < header.CountYVertices; y++)
+ {
+ for (uint32 x = 0; x < header.CountXVertices; x++)
+ {
+ uint32 discard;
+ float tmp;
+ if (fread(&discard, sizeof(uint32), 1, stream) == 1 &&
+ fread(&tmp, sizeof(float), 1, stream) == 1)
+ {
+ ret.HeightMap[x][y] = tmp;
+ }
+ }
+ }
+
+ for (uint32 y = 0; y < header.Height; y++)
+ {
+ for (uint32 x = 0; x < header.Width; x++)
+ {
+ uint8 tmp = 0;
+ if (fread(&tmp, sizeof(uint8), 1, stream) == 1)
+ ret.RenderFlags[x][y] = tmp;
+ }
+ }
+
+ return ret;
+}
+
+H2ORenderMask H2ORenderMask::Read(FILE* stream)
+{
+ H2ORenderMask ret;
+ if (int count = fread(&ret.Mask, sizeof(uint8), 8, stream) != 8)
+ printf("H2OHeader::Read: Failed to read some data expected 8, read %d\n", count);
+ return ret;
+}
+
+bool MCNKLiquidData::IsWater(int x, int y, float height)
+{
+ if (!Heights)
+ return false;
+ if (!Mask.ShouldRender(x, y))
+ return false;
+ float diff = Heights[x][y] - height;
+ if (diff > Constants::MaxStandableHeight)
+ return true;
+ return false;
+}
+
+H2OHeader H2OHeader::Read(FILE* stream)
+{
+ H2OHeader ret;
+ int count = 0;
+ count += fread(&ret.OffsetInformation, sizeof(uint32), 1, stream);
+ count += fread(&ret.LayerCount, sizeof(uint32), 1, stream);
+ count += fread(&ret.OffsetRender, sizeof(uint32), 1, stream);
+
+ if (count != 3)
+ printf("H2OHeader::Read: Failed to read some data expected 3, read %d\n", count);
+
+ return ret;
+}
+
+H2OInformation H2OInformation::Read(FILE* stream)
+{
+ H2OInformation ret;
+ int count = 0;
+ count += fread(&ret.LiquidType, sizeof(uint16), 1, stream);
+ count += fread(&ret.Flags, sizeof(uint16), 1, stream);
+ count += fread(&ret.HeightLevel1, sizeof(float), 1, stream);
+ count += fread(&ret.HeightLevel2, sizeof(float), 1, stream);
+ count += fread(&ret.OffsetX, sizeof(uint8), 1, stream);
+ count += fread(&ret.OffsetY, sizeof(uint8), 1, stream);
+ count += fread(&ret.Width, sizeof(uint8), 1, stream);
+ count += fread(&ret.Height, sizeof(uint8), 1, stream);
+ count += fread(&ret.OffsetMask2, sizeof(uint32), 1, stream);
+ count += fread(&ret.OffsetHeightmap, sizeof(uint32), 1, stream);
+
+ if (count != 10)
+ printf("H2OInformation::Read: Failed to read some data expected 10, read %d\n", count);
+
+ return ret;
+}
+
+char* Utils::GetPlainName(const char* FileName)
+{
+ char* temp;
+
+ if((temp = (char*)strrchr(FileName, '\\')) != NULL)
+ FileName = temp + 1;
+ return (char*)FileName;
+}
+
+WMOGroupHeader WMOGroupHeader::Read( FILE* stream )
+{
+ WMOGroupHeader ret;
+ int count = 0;
+ count += fread(&ret.OffsetGroupName, sizeof(uint32), 1, stream);
+ count += fread(&ret.OffsetDescriptiveName, sizeof(uint32), 1, stream);
+ count += fread(&ret.Flags, sizeof(uint32), 1, stream);
+ ret.BoundingBox[0] = Vector3::Read(stream);
+ ret.BoundingBox[1] = Vector3::Read(stream);
+ count += fread(&ret.OffsetPortals, sizeof(uint32), 1, stream);
+ count += fread(&ret.CountPortals, sizeof(uint32), 1, stream);
+ count += fread(&ret.CountBatches, sizeof(uint16), 4, stream);
+ count += fread(&ret.Fogs, sizeof(uint8), 4, stream);
+ count += fread(&ret.LiquidTypeRelated, sizeof(uint32), 1, stream);
+ count += fread(&ret.WmoId, sizeof(uint32), 1, stream);
+
+ if (count != 15)
+ printf("WMOGroupHeader::Read: Failed to read some data expected 15, read %d\n", count);
+
+ return ret;
+}
diff --git a/src/tools/mesh_extractor/Utils.h b/src/tools/mesh_extractor/Utils.h
new file mode 100644
index 00000000000..e7380ed8142
--- /dev/null
+++ b/src/tools/mesh_extractor/Utils.h
@@ -0,0 +1,377 @@
+#ifndef UTILS_H
+#define UTILS_H
+#include <cstdio>
+#include <string>
+#include <sstream>
+
+#include "G3D/Matrix4.h"
+#include "DetourNavMesh.h"
+
+#include "Common.h"
+#include "Constants.h"
+
+struct WorldModelDefinition;
+class DoodadInstance;
+
+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 const& other)
+ {
+ return Vector3(x + other.x, y + other.y, z + other.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() {}
+ Triangle(Constants::TriangleType type, T v0, T v1, T v2) : V0(v0), V1(v1), V2(v2), Type(type) {}
+ 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);
+};
+
+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);
+};
+
+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);
+};
+
+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);
+};
+
+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);
+};
+
+class DoodadSet
+{
+public:
+ DoodadSet() {}
+ std::string Name;
+ uint32 FirstInstanceIndex;
+ uint32 CountInstances;
+ uint32 UnknownZero;
+
+ static DoodadSet Read(FILE* stream);
+};
+
+class LiquidHeader
+{
+public:
+ LiquidHeader() {}
+ uint32 CountXVertices;
+ uint32 CountYVertices;
+ uint32 Width;
+ uint32 Height;
+ Vector3 BaseLocation;
+ uint16 MaterialId;
+
+ static LiquidHeader Read(FILE* stream);
+};
+
+class LiquidData
+{
+public:
+ LiquidData() {}
+ float** HeightMap;
+ uint8** RenderFlags;
+
+ bool ShouldRender(int x, int y)
+ {
+ return RenderFlags[x][y] != 0x0F;
+ }
+
+ static LiquidData Read(FILE* stream, LiquidHeader& header);
+};
+
+class H2ORenderMask
+{
+public:
+ H2ORenderMask() {}
+ uint8 Mask[8];
+
+ bool ShouldRender(int x, int y)
+ {
+ return (Mask[y] >> x & 1) != 0;
+ }
+
+ static H2ORenderMask Read(FILE* stream);
+};
+
+class MCNKLiquidData
+{
+public:
+ MCNKLiquidData() {}
+ MCNKLiquidData(float** heights, H2ORenderMask mask) : Heights(heights), Mask(mask) {}
+
+ float** Heights;
+ H2ORenderMask Mask;
+
+ bool IsWater(int x, int y, float height);
+};
+
+class H2OHeader
+{
+public:
+ H2OHeader() {}
+ uint32 OffsetInformation;
+ uint32 LayerCount;
+ uint32 OffsetRender;
+
+ static H2OHeader Read(FILE* stream);
+};
+
+class H2OInformation
+{
+public:
+ H2OInformation() {}
+ uint16 LiquidType;
+ uint16 Flags;
+ float HeightLevel1;
+ float HeightLevel2;
+ uint8 OffsetX;
+ uint8 OffsetY;
+ uint8 Width;
+ uint8 Height;
+ uint32 OffsetMask2;
+ uint32 OffsetHeightmap;
+
+ static H2OInformation Read(FILE* stream);
+};
+
+class WMOGroupHeader
+{
+public:
+ WMOGroupHeader() {}
+
+ uint32 OffsetGroupName;
+ uint32 OffsetDescriptiveName;
+ uint32 Flags;
+ Vector3 BoundingBox[2];
+ uint32 OffsetPortals;
+ uint32 CountPortals;
+ uint16 CountBatches[4];
+ uint8 Fogs[4];
+ uint32 LiquidTypeRelated;
+ uint32 WmoId;
+
+ static WMOGroupHeader Read(FILE* stream);
+};
+
+// Dummy class to act as an interface.
+class IDefinition
+{
+public:
+ Vector3 Position;
+ Vector3 Rotation;
+ virtual float Scale() const { return 1.0f; };
+};
+
+#define MMAP_MAGIC 0x4d4d4150 // 'MMAP'
+#define MMAP_VERSION 3
+
+struct MmapTileHeader
+{
+ uint32 mmapMagic;
+ uint32 dtVersion;
+ uint32 mmapVersion;
+ uint32 size;
+ bool usesLiquids;
+
+ MmapTileHeader() : mmapMagic(MMAP_MAGIC), dtVersion(DT_NAVMESH_VERSION),
+ mmapVersion(MMAP_VERSION), size(0), usesLiquids(true) {}
+};
+
+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::Matrix4 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);
+ template<typename T>
+ static bool IsAllZero(T* arr, uint32 size)
+ {
+ for (uint32 i = 0; i < size; ++i)
+ if (arr[i])
+ return false;
+ return true;
+ }
+ static std::string Replace( std::string str, const std::string& oldStr, const std::string& newStr );
+ static G3D::Matrix4 GetWmoDoodadTransformation( DoodadInstance inst, WorldModelDefinition root );
+ static void CreateDir( const std::string& Path );
+ static void SaveToDisk(FILE* stream, std::string path);
+ static Vector3 ToWoWCoords( Vector3 vec );
+ static std::string GetExtension( std::string path );
+ static char* GetPlainName(const char* FileName);
+};
+#endif
diff --git a/src/tools/mesh_extractor/WDT.cpp b/src/tools/mesh_extractor/WDT.cpp
new file mode 100644
index 00000000000..70d140e79ed
--- /dev/null
+++ b/src/tools/mesh_extractor/WDT.cpp
@@ -0,0 +1,60 @@
+#include "WDT.h"
+#include "Chunk.h"
+#include "ChunkedData.h"
+#include "Utils.h"
+#include "WorldModelHandler.h"
+
+WDT::WDT(std::string file) : IsGlobalModel(false), IsValid(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;
+ int count = 0;
+ count += fread(&flags, sizeof(uint32), 1, stream);
+ count += fread(&discard, sizeof(uint32), 1, stream);
+
+ if (count != 2)
+ printf("WDT::ReadTileTable: Failed to read some data expected 2, read %d\n", count);
+
+ 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..5eeef824b1a
--- /dev/null
+++ b/src/tools/mesh_extractor/WorldModelGroup.cpp
@@ -0,0 +1,143 @@
+#include "WorldModelGroup.h"
+#include "ChunkedData.h"
+#include "Chunk.h"
+#include "Utils.h"
+
+WorldModelGroup::WorldModelGroup( std::string path, int groupIndex ) : GroupIndex(groupIndex), MOBA(NULL), 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;
+
+ Name = Utils::GetPlainName(path.c_str());
+
+ FILE* stream = mainChunk->GetStream();
+ fseek(stream, firstSub, SEEK_SET);
+ SubData = new ChunkedData(stream, mainChunk->Length - firstSub);
+
+ ReadHeader();
+ ReadMaterials();
+ ReadTriangles();
+ ReadVertices();
+ ReadNormals();
+ ReadLiquid();
+ ReadBatches();
+}
+
+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 (uint32 i = 0; i < normalCount; i++)
+ Normals.push_back(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 (uint32 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 (uint32 i = 0; i < triangleCount; i++)
+ {
+ uint16 v0;
+ uint16 v1;
+ uint16 v2;
+ int count = 0;
+ count += fread(&v0, sizeof(uint16), 1, stream);
+ count += fread(&v1, sizeof(uint16), 1, stream);
+ count += fread(&v2, sizeof(uint16), 1, stream);
+ if (count != 3)
+ printf("WorldModelGroup::ReadMaterials: Error reading data, expected 3, read %d\n", count);
+
+ 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 (uint32 i = 0; i < triangleCount; i++)
+ {
+ uint8 tmp;
+ if (fread(&tmp, sizeof(uint8), 1, stream) != 1)
+ printf("WorldModelGroup::ReadMaterials: Error reading data, expected 1, read 0\n");
+ TriangleFlags.push_back(tmp);
+ // Read again for material.
+ if (fread(&tmp, sizeof(uint8), 1, stream) != 1)
+ printf("WorldModelGroup::ReadMaterials: Error reading data, expected 1, read 0\n");
+ TriangleMaterials.push_back(tmp);
+ }
+}
+
+void WorldModelGroup::ReadHeader()
+{
+ Chunk* chunk = Data->GetChunkByName("MOGP");
+ if (!chunk)
+ return;
+
+ FILE* stream = chunk->GetStream();
+ Header = WMOGroupHeader::Read(stream);
+}
+
+void WorldModelGroup::ReadBatches()
+{
+ Chunk* chunk = Data->GetChunkByName("MOBA");
+ if (!chunk)
+ return;
+
+ MOBALength = chunk->Length / 2;
+ MOBA = new uint16[MOBALength];
+ int count = fread(MOBA, sizeof(uint16), MOBALength, chunk->GetStream());
+ if (count != MOBALength)
+ printf("WorldModelGroup::ReadBatches: Error reading data, expected %d, read %d\n", MOBALength, count);
+}
diff --git a/src/tools/mesh_extractor/WorldModelGroup.h b/src/tools/mesh_extractor/WorldModelGroup.h
new file mode 100644
index 00000000000..e4fe34cbc75
--- /dev/null
+++ b/src/tools/mesh_extractor/WorldModelGroup.h
@@ -0,0 +1,38 @@
+#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;
+ std::string Name;
+ WMOGroupHeader Header;
+
+ std::vector<uint8> TriangleFlags;
+ std::vector<uint8> TriangleMaterials;
+ std::vector<Triangle<uint16> > Triangles;
+ std::vector<Vector3> Vertices;
+ std::vector<Vector3> Normals;
+ // @ToDo: Research.
+ uint16* MOBA;
+ uint32 MOBALength;
+
+ bool HasLiquidData;
+ bool IsBad;
+ LiquidHeader LiquidDataHeader;
+ LiquidData LiquidDataGeometry;
+private:
+ void ReadNormals();
+ void ReadLiquid();
+ void ReadVertices();
+ void ReadTriangles();
+ void ReadMaterials();
+ void ReadHeader();
+ void ReadBatches();
+};
+#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..6ce41807492
--- /dev/null
+++ b/src/tools/mesh_extractor/WorldModelHandler.cpp
@@ -0,0 +1,204 @@
+#include "WorldModelHandler.h"
+#include "WorldModelRoot.h"
+#include "Chunk.h"
+#include "Cache.h"
+#include "Model.h"
+#include "Common.h"
+#include "G3D/Matrix4.h"
+#include <cstdio>
+
+WorldModelDefinition WorldModelDefinition::Read( FILE* file )
+{
+ WorldModelDefinition ret;
+ int count = 0;
+ count += fread(&ret.MwidIndex, sizeof(uint32), 1, file);
+ count += 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);
+ count += fread(&ret.Flags, sizeof(uint16), 1, file);
+ count += fread(&ret.DoodadSet, sizeof(uint16), 1, file);
+ uint32 discard;
+ count += fread(&discard, sizeof(uint32), 1, file);
+
+ if (count != 5)
+ printf("WorldModelDefinition::Read: Error reading data, expected 5, read %d\n", count);
+ return ret;
+}
+
+
+WorldModelHandler::WorldModelHandler( ADT* adt ) : ObjectDataHandler(adt), _definitions(NULL), _paths(NULL)
+{
+ 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 (uint32 i = 0; i < refCount; i++)
+ {
+ int32 index;
+ if (fread(&index, sizeof(int32), 1, stream) != 1)
+ printf("WorldModelDefinition::Read: Error reading data, expected 1, read 0\n");
+
+ if (index < 0 || uint32(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)[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(def);
+ 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 (uint32 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 < root->DoodadSets.size())
+ {
+ DoodadSet set = root->DoodadSets[def.DoodadSet];
+ std::vector<DoodadInstance> instances;
+ 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 (uint32 y = 0; y < group->LiquidDataHeader.Height; y++)
+ {
+ for (uint32 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(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 std::vector<WorldModelDefinition>;
+ _definitions->reserve(definitionCount);
+ FILE* stream = chunk->GetStream();
+ for (uint32 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 (uint32 i = 0; i < paths; i++)
+ {
+ FILE* stream = mwid->GetStream();
+ fseek(stream, i * 4, SEEK_CUR);
+ uint32 offset;
+ if (fread(&offset, sizeof(uint32), 1, stream) != 1)
+ printf("WorldModelDefinition::Read: Error reading data, expected 1, read 0\n");
+ FILE* dataStream = mwmo->GetStream();
+ fseek(dataStream, offset + mwmo->Offset, SEEK_SET);
+ _paths->push_back(Utils::ReadString(dataStream));
+ }
+}
+
+WorldModelHandler::~WorldModelHandler()
+{
+ delete _definitions;
+ delete _paths;
+}
diff --git a/src/tools/mesh_extractor/WorldModelHandler.h b/src/tools/mesh_extractor/WorldModelHandler.h
new file mode 100644
index 00000000000..cccedfa60fb
--- /dev/null
+++ b/src/tools/mesh_extractor/WorldModelHandler.h
@@ -0,0 +1,47 @@
+#ifndef WMODEL_HNDL_H
+#define WMODEL_HNDL_H
+#include "Common.h"
+#include "Utils.h"
+#include "WorldModelRoot.h"
+#include "ObjectDataHandler.h"
+
+#include <set>
+#include <vector>
+
+class ADT;
+
+struct WorldModelDefinition : public IDefinition
+{
+public:
+ WorldModelDefinition() {}
+
+ uint32 MwidIndex;
+ uint32 UniqueId;
+ Vector3 UpperExtents;
+ Vector3 LowerExtents;
+ uint16 Flags;
+ uint16 DoodadSet;
+
+ static WorldModelDefinition Read(FILE* file);
+};
+
+class WorldModelHandler : public ObjectDataHandler
+{
+public:
+ WorldModelHandler(ADT* adt);
+ ~WorldModelHandler();
+
+ 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..c34a77e4531
--- /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 (uint32 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 (uint32 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 (uint32 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
diff --git a/src/tools/mmaps_generator/CMakeLists.txt b/src/tools/mmaps_generator/CMakeLists.txt
new file mode 100644
index 00000000000..229a2cd808f
--- /dev/null
+++ b/src/tools/mmaps_generator/CMakeLists.txt
@@ -0,0 +1,68 @@
+# Copyright (C) 2008-2011 Trinity <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)
+
+# definitions
+add_definitions(-DNO_CORE_FUNCS)
+add_definitions(-DDEBUG)
+add_definitions(-DNO_vsnprintf)
+
+include_directories(
+ ${CMAKE_BINARY_DIR}
+ ${ACE_INCLUDE_DIR}
+ ${MYSQL_INCLUDE_DIR}
+ ${CMAKE_SOURCE_DIR}/dep/libmpq
+ ${CMAKE_SOURCE_DIR}/dep/zlib
+ ${CMAKE_SOURCE_DIR}/dep/bzip2
+ ${CMAKE_SOURCE_DIR}/dep/g3dlite/include
+ ${CMAKE_SOURCE_DIR}/dep/recastnavigation/Recast
+ ${CMAKE_SOURCE_DIR}/dep/recastnavigation/Detour
+ ${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/Debugging
+ ${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}/src/server/game/Maps
+ ${CMAKE_SOURCE_DIR}/src/server/game/DataStores
+ ${CMAKE_SOURCE_DIR}/src/server/game/Movement/Waypoints
+ ${CMAKE_SOURCE_DIR}/src/server/game/Grids
+ ${CMAKE_SOURCE_DIR}/src/server/game/Grids/Cells
+ ${CMAKE_SOURCE_DIR}/src/server/game/Miscellaneous
+ ${CMAKE_SOURCE_DIR}/src/server/game/Conditions
+ ${CMAKE_SOURCE_DIR}/src/server/collision
+ ${CMAKE_SOURCE_DIR}/src/server/collision/Management
+ ${CMAKE_SOURCE_DIR}/src/server/collision/Maps
+ ${CMAKE_SOURCE_DIR}/src/server/collision/Models
+)
+
+add_executable(mmaps_generator ${sources})
+
+target_link_libraries(mmaps_generator
+ ${MYSQL_LIBRARY}
+ ${ACE_LIBRARY}
+ ${BZIP2_LIBRARIES}
+ ${ZLIB_LIBRARIES}
+ Recast
+ Detour
+ collision
+ g3dlib
+ shared
+)
+
+if( UNIX )
+ install(TARGETS mmaps_generator DESTINATION bin)
+elseif( WIN32 )
+ install(TARGETS mmaps_generator DESTINATION "${CMAKE_INSTALL_PREFIX}")
+endif()
diff --git a/src/tools/mmaps_generator/Info/readme.txt b/src/tools/mmaps_generator/Info/readme.txt
new file mode 100644
index 00000000000..8d7c4f9d2e0
--- /dev/null
+++ b/src/tools/mmaps_generator/Info/readme.txt
@@ -0,0 +1,66 @@
+Generator command line args
+
+--offMeshInput [file.*] Path to file containing off mesh connections data.
+ Format must be: (see offmesh_example.txt)
+ "map_id tile_x,tile_y (start_x start_y start_z) (end_x end_y end_z) size //optional comments"
+ Single mesh connection per line.
+
+--silent Make us script friendly. Do not wait for user input
+ on error or completion.
+
+--bigBaseUnit [true|false] Generate tile/map using bigger basic unit.
+ Use this option only if you have unexpected gaps.
+
+ false: use normal metrics (default)
+
+--maxAngle [#] Max walkable inclination angle
+
+ float between 45 and 90 degrees (default 60)
+
+--skipLiquid liquid data for maps
+
+ false: include liquid data (default)
+
+--skipContinents [true|false] continents are maps 0 (Eastern Kingdoms),
+ 1 (Kalimdor), 530 (Outlands), 571 (Northrend)
+
+ false: build continents (default)
+
+--skipJunkMaps [true|false] junk maps include some unused
+ maps, transport maps, and some other
+
+ true: skip junk maps (default)
+
+--skipBattlegrounds [true|false] does not include PVP arenas
+
+ false: skip battlegrounds (default)
+
+--debugOutput [true|false] create debugging files for use with RecastDemo
+ if you are only creating mmaps for use with MaNGOS,
+ you don't want debugging files
+
+ false: don't create debugging files (default)
+
+--tile [#,#] Build the specified tile
+ seperate number with a comma ','
+ must specify a map number (see below)
+ if this option is not used, all tiles are built
+
+ [#] Build only the map specified by #
+ this command will build the map regardless of --skip* option settings
+ if you do not specify a map number, builds all maps that pass the filters specified by --skip* options
+
+
+examples:
+
+movement_extractor
+builds maps using the default settings (see above for defaults)
+
+movement_extractor --skipContinents true
+builds the default maps, except continents
+
+movement_extractor 0
+builds all tiles of map 0
+
+movement_extractor 0 --tile 34,46
+builds only tile 34,46 of map 0 (this is the southern face of blackrock mountain) \ No newline at end of file
diff --git a/src/tools/mmaps_generator/IntermediateValues.cpp b/src/tools/mmaps_generator/IntermediateValues.cpp
new file mode 100644
index 00000000000..6fad96887a2
--- /dev/null
+++ b/src/tools/mmaps_generator/IntermediateValues.cpp
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2008-2013 TrinityCore <http://www.trinitycore.org/>
+ * Copyright (C) 2005-2011 MaNGOS <http://getmangos.com/>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "IntermediateValues.h"
+
+namespace MMAP
+{
+ IntermediateValues::~IntermediateValues()
+ {
+ rcFreeCompactHeightfield(compactHeightfield);
+ rcFreeHeightField(heightfield);
+ rcFreeContourSet(contours);
+ rcFreePolyMesh(polyMesh);
+ rcFreePolyMeshDetail(polyMeshDetail);
+ }
+
+ void IntermediateValues::writeIV(uint32 mapID, uint32 tileX, uint32 tileY)
+ {
+ char fileName[255];
+ char tileString[25];
+ sprintf(tileString, "[%02u,%02u]: ", tileX, tileY);
+
+ printf("%sWriting debug output... \r", tileString);
+
+ string name("meshes/%03u%02i%02i.");
+
+#define DEBUG_WRITE(fileExtension,data) \
+ do { \
+ sprintf(fileName, (name + fileExtension).c_str(), mapID, tileY, tileX); \
+ FILE* file = fopen(fileName, "wb"); \
+ if (!file) \
+ { \
+ char message[1024]; \
+ sprintf(message, "%sFailed to open %s for writing!\n", tileString, fileName); \
+ perror(message); \
+ } \
+ else \
+ debugWrite(file, data); \
+ if (file) fclose(file); \
+ printf("%sWriting debug output... \r", tileString); \
+ } while (false)
+
+ if (heightfield)
+ DEBUG_WRITE("hf", heightfield);
+ if (compactHeightfield)
+ DEBUG_WRITE("chf", compactHeightfield);
+ if (contours)
+ DEBUG_WRITE("cs", contours);
+ if (polyMesh)
+ DEBUG_WRITE("pmesh", polyMesh);
+ if (polyMeshDetail)
+ DEBUG_WRITE("dmesh", polyMeshDetail);
+
+#undef DEBUG_WRITE
+ }
+
+ void IntermediateValues::debugWrite(FILE* file, const rcHeightfield* mesh)
+ {
+ if (!file || !mesh)
+ return;
+
+ fwrite(&(mesh->cs), sizeof(float), 1, file);
+ fwrite(&(mesh->ch), sizeof(float), 1, file);
+ fwrite(&(mesh->width), sizeof(int), 1, file);
+ fwrite(&(mesh->height), sizeof(int), 1, file);
+ fwrite(mesh->bmin, sizeof(float), 3, file);
+ fwrite(mesh->bmax, sizeof(float), 3, file);
+
+ for (int y = 0; y < mesh->height; ++y)
+ for (int x = 0; x < mesh->width; ++x)
+ {
+ rcSpan* span = mesh->spans[x+y*mesh->width];
+
+ // first, count the number of spans
+ int spanCount = 0;
+ while (span)
+ {
+ spanCount++;
+ span = span->next;
+ }
+
+ // write the span count
+ fwrite(&spanCount, sizeof(int), 1, file);
+
+ // write the spans
+ span = mesh->spans[x+y*mesh->width];
+ while (span)
+ {
+ fwrite(span, sizeof(rcSpan), 1, file);
+ span = span->next;
+ }
+ }
+ }
+
+ void IntermediateValues::debugWrite(FILE* file, const rcCompactHeightfield* chf)
+ {
+ if (!file | !chf)
+ return;
+
+ fwrite(&(chf->width), sizeof(chf->width), 1, file);
+ fwrite(&(chf->height), sizeof(chf->height), 1, file);
+ fwrite(&(chf->spanCount), sizeof(chf->spanCount), 1, file);
+
+ fwrite(&(chf->walkableHeight), sizeof(chf->walkableHeight), 1, file);
+ fwrite(&(chf->walkableClimb), sizeof(chf->walkableClimb), 1, file);
+
+ fwrite(&(chf->maxDistance), sizeof(chf->maxDistance), 1, file);
+ fwrite(&(chf->maxRegions), sizeof(chf->maxRegions), 1, file);
+
+ fwrite(chf->bmin, sizeof(chf->bmin), 1, file);
+ fwrite(chf->bmax, sizeof(chf->bmax), 1, file);
+
+ fwrite(&(chf->cs), sizeof(chf->cs), 1, file);
+ fwrite(&(chf->ch), sizeof(chf->ch), 1, file);
+
+ int tmp = 0;
+ if (chf->cells) tmp |= 1;
+ if (chf->spans) tmp |= 2;
+ if (chf->dist) tmp |= 4;
+ if (chf->areas) tmp |= 8;
+
+ fwrite(&tmp, sizeof(tmp), 1, file);
+
+ if (chf->cells)
+ fwrite(chf->cells, sizeof(rcCompactCell), chf->width*chf->height, file);
+ if (chf->spans)
+ fwrite(chf->spans, sizeof(rcCompactSpan), chf->spanCount, file);
+ if (chf->dist)
+ fwrite(chf->dist, sizeof(unsigned short), chf->spanCount, file);
+ if (chf->areas)
+ fwrite(chf->areas, sizeof(unsigned char), chf->spanCount, file);
+ }
+
+ void IntermediateValues::debugWrite(FILE* file, const rcContourSet* cs)
+ {
+ if (!file || !cs)
+ return;
+
+ fwrite(&(cs->cs), sizeof(float), 1, file);
+ fwrite(&(cs->ch), sizeof(float), 1, file);
+ fwrite(cs->bmin, sizeof(float), 3, file);
+ fwrite(cs->bmax, sizeof(float), 3, file);
+ fwrite(&(cs->nconts), sizeof(int), 1, file);
+ for (int i = 0; i < cs->nconts; ++i)
+ {
+ fwrite(&cs->conts[i].area, sizeof(unsigned char), 1, file);
+ fwrite(&cs->conts[i].reg, sizeof(unsigned short), 1, file);
+ fwrite(&cs->conts[i].nverts, sizeof(int), 1, file);
+ fwrite(cs->conts[i].verts, sizeof(int), cs->conts[i].nverts*4, file);
+ fwrite(&cs->conts[i].nrverts, sizeof(int), 1, file);
+ fwrite(cs->conts[i].rverts, sizeof(int), cs->conts[i].nrverts*4, file);
+ }
+ }
+
+ void IntermediateValues::debugWrite(FILE* file, const rcPolyMesh* mesh)
+ {
+ if (!file || !mesh)
+ return;
+
+ fwrite(&(mesh->cs), sizeof(float), 1, file);
+ fwrite(&(mesh->ch), sizeof(float), 1, file);
+ fwrite(&(mesh->nvp), sizeof(int), 1, file);
+ fwrite(mesh->bmin, sizeof(float), 3, file);
+ fwrite(mesh->bmax, sizeof(float), 3, file);
+ fwrite(&(mesh->nverts), sizeof(int), 1, file);
+ fwrite(mesh->verts, sizeof(unsigned short), mesh->nverts*3, file);
+ fwrite(&(mesh->npolys), sizeof(int), 1, file);
+ fwrite(mesh->polys, sizeof(unsigned short), mesh->npolys*mesh->nvp*2, file);
+ fwrite(mesh->flags, sizeof(unsigned short), mesh->npolys, file);
+ fwrite(mesh->areas, sizeof(unsigned char), mesh->npolys, file);
+ fwrite(mesh->regs, sizeof(unsigned short), mesh->npolys, file);
+ }
+
+ void IntermediateValues::debugWrite(FILE* file, const rcPolyMeshDetail* mesh)
+ {
+ if (!file || !mesh)
+ return;
+
+ fwrite(&(mesh->nverts), sizeof(int), 1, file);
+ fwrite(mesh->verts, sizeof(float), mesh->nverts*3, file);
+ fwrite(&(mesh->ntris), sizeof(int), 1, file);
+ fwrite(mesh->tris, sizeof(char), mesh->ntris*4, file);
+ fwrite(&(mesh->nmeshes), sizeof(int), 1, file);
+ fwrite(mesh->meshes, sizeof(int), mesh->nmeshes*4, file);
+ }
+
+ void IntermediateValues::generateObjFile(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData)
+ {
+ char objFileName[255];
+ sprintf(objFileName, "meshes/map%03u%02u%02u.obj", mapID, tileY, tileX);
+
+ FILE* objFile = fopen(objFileName, "wb");
+ if (!objFile)
+ {
+ char message[1024];
+ sprintf(message, "Failed to open %s for writing!\n", objFileName);
+ perror(message);
+ return;
+ }
+
+ G3D::Array<float> allVerts;
+ G3D::Array<int> allTris;
+
+ allTris.append(meshData.liquidTris);
+ allVerts.append(meshData.liquidVerts);
+ TerrainBuilder::copyIndices(meshData.solidTris, allTris, allVerts.size() / 3);
+ allVerts.append(meshData.solidVerts);
+
+ float* verts = allVerts.getCArray();
+ int vertCount = allVerts.size() / 3;
+ int* tris = allTris.getCArray();
+ int triCount = allTris.size() / 3;
+
+ for (int i = 0; i < allVerts.size() / 3; i++)
+ fprintf(objFile, "v %f %f %f\n", verts[i*3], verts[i*3 + 1], verts[i*3 + 2]);
+
+ for (int i = 0; i < allTris.size() / 3; i++)
+ fprintf(objFile, "f %i %i %i\n", tris[i*3] + 1, tris[i*3 + 1] + 1, tris[i*3 + 2] + 1);
+
+ fclose(objFile);
+
+
+ char tileString[25];
+ sprintf(tileString, "[%02u,%02u]: ", tileY, tileX);
+ printf("%sWriting debug output... \r", tileString);
+
+ sprintf(objFileName, "meshes/%03u.map", mapID);
+
+ objFile = fopen(objFileName, "wb");
+ if (!objFile)
+ {
+ char message[1024];
+ sprintf(message, "Failed to open %s for writing!\n", objFileName);
+ perror(message);
+ return;
+ }
+
+ char b = '\0';
+ fwrite(&b, sizeof(char), 1, objFile);
+ fclose(objFile);
+
+ sprintf(objFileName, "meshes/%03u%02u%02u.mesh", mapID, tileY, tileX);
+ objFile = fopen(objFileName, "wb");
+ if (!objFile)
+ {
+ char message[1024];
+ sprintf(message, "Failed to open %s for writing!\n", objFileName);
+ perror(message);
+ return;
+ }
+
+ fwrite(&vertCount, sizeof(int), 1, objFile);
+ fwrite(verts, sizeof(float), vertCount*3, objFile);
+ fflush(objFile);
+
+ fwrite(&triCount, sizeof(int), 1, objFile);
+ fwrite(tris, sizeof(int), triCount*3, objFile);
+ fflush(objFile);
+
+ fclose(objFile);
+ }
+}
diff --git a/src/tools/mmaps_generator/IntermediateValues.h b/src/tools/mmaps_generator/IntermediateValues.h
new file mode 100644
index 00000000000..89a5c3ae4c2
--- /dev/null
+++ b/src/tools/mmaps_generator/IntermediateValues.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2008-2013 TrinityCore <http://www.trinitycore.org/>
+ * Copyright (C) 2005-2011 MaNGOS <http://getmangos.com/>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _INTERMEDIATE_VALUES_H
+#define _INTERMEDIATE_VALUES_H
+
+#include "PathCommon.h"
+#include "TerrainBuilder.h"
+#include "Recast.h"
+#include "DetourNavMesh.h"
+
+namespace MMAP
+{
+ // this class gathers all debug info holding and output
+ struct IntermediateValues
+ {
+ rcHeightfield* heightfield;
+ rcCompactHeightfield* compactHeightfield;
+ rcContourSet* contours;
+ rcPolyMesh* polyMesh;
+ rcPolyMeshDetail* polyMeshDetail;
+
+ IntermediateValues() : heightfield(NULL), compactHeightfield(NULL),
+ contours(NULL), polyMesh(NULL), polyMeshDetail(NULL) {}
+ ~IntermediateValues();
+
+ void writeIV(uint32 mapID, uint32 tileX, uint32 tileY);
+
+ void debugWrite(FILE* file, const rcHeightfield* mesh);
+ void debugWrite(FILE* file, const rcCompactHeightfield* chf);
+ void debugWrite(FILE* file, const rcContourSet* cs);
+ void debugWrite(FILE* file, const rcPolyMesh* mesh);
+ void debugWrite(FILE* file, const rcPolyMeshDetail* mesh);
+
+ void generateObjFile(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData);
+ };
+}
+#endif
diff --git a/src/tools/mmaps_generator/MapBuilder.cpp b/src/tools/mmaps_generator/MapBuilder.cpp
new file mode 100644
index 00000000000..9e6acd71a72
--- /dev/null
+++ b/src/tools/mmaps_generator/MapBuilder.cpp
@@ -0,0 +1,977 @@
+/*
+ * Copyright (C) 2008-2013 TrinityCore <http://www.trinitycore.org/>
+ * Copyright (C) 2005-2011 MaNGOS <http://getmangos.com/>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "PathCommon.h"
+#include "MapBuilder.h"
+
+#include "MapTree.h"
+#include "ModelInstance.h"
+#include "LoginDatabase.h"
+
+#include "DetourNavMeshBuilder.h"
+#include "DetourNavMesh.h"
+#include "DetourCommon.h"
+
+// These make the linker happy.
+LoginDatabaseWorkerPool LoginDatabase;
+uint32 GetLiquidFlags(uint32 /*liquidType*/)
+{
+ return 0;
+}
+
+#include "DisableMgr.h"
+namespace DisableMgr
+{
+ bool IsDisabledFor(DisableType /*type*/, uint32 /*entry*/, Unit const* /*unit*/, uint8 /*flags*/)
+ {
+ return 0;
+ }
+}
+
+using namespace VMAP;
+
+namespace MMAP
+{
+ MapBuilder::MapBuilder(float maxWalkableAngle, bool skipLiquid,
+ bool skipContinents, bool skipJunkMaps, bool skipBattlegrounds,
+ bool debugOutput, bool bigBaseUnit, const char* offMeshFilePath) :
+ m_terrainBuilder (NULL),
+ m_debugOutput (debugOutput),
+ m_offMeshFilePath (offMeshFilePath),
+ m_skipContinents (skipContinents),
+ m_skipJunkMaps (skipJunkMaps),
+ m_skipBattlegrounds (skipBattlegrounds),
+ m_maxWalkableAngle (maxWalkableAngle),
+ m_bigBaseUnit (bigBaseUnit),
+ m_rcContext (NULL)
+ {
+ m_terrainBuilder = new TerrainBuilder(skipLiquid);
+
+ m_rcContext = new rcContext(false);
+
+ discoverTiles();
+ }
+
+ /**************************************************************************/
+ MapBuilder::~MapBuilder()
+ {
+ for (TileList::iterator it = m_tiles.begin(); it != m_tiles.end(); ++it)
+ {
+ (*it).second->clear();
+ delete (*it).second;
+ }
+
+ delete m_terrainBuilder;
+ delete m_rcContext;
+ }
+
+ /**************************************************************************/
+ void MapBuilder::discoverTiles()
+ {
+ vector<string> files;
+ uint32 mapID, tileX, tileY, tileID, count = 0;
+ char filter[12];
+
+ printf("Discovering maps... ");
+ getDirContents(files, "maps");
+ for (uint32 i = 0; i < files.size(); ++i)
+ {
+ mapID = uint32(atoi(files[i].substr(0,3).c_str()));
+ if (m_tiles.find(mapID) == m_tiles.end())
+ {
+ m_tiles.insert(pair<uint32,set<uint32>*>(mapID, new set<uint32>));
+ count++;
+ }
+ }
+
+ files.clear();
+ getDirContents(files, "vmaps", "*.vmtree");
+ for (uint32 i = 0; i < files.size(); ++i)
+ {
+ mapID = uint32(atoi(files[i].substr(0,3).c_str()));
+ m_tiles.insert(pair<uint32,set<uint32>*>(mapID, new set<uint32>));
+ count++;
+ }
+ printf("found %u.\n", count);
+
+ count = 0;
+ printf("Discovering tiles... ");
+ for (TileList::iterator itr = m_tiles.begin(); itr != m_tiles.end(); ++itr)
+ {
+ set<uint32>* tiles = (*itr).second;
+ mapID = (*itr).first;
+
+ sprintf(filter, "%03u*.vmtile", mapID);
+ files.clear();
+ getDirContents(files, "vmaps", filter);
+ for (uint32 i = 0; i < files.size(); ++i)
+ {
+ tileX = uint32(atoi(files[i].substr(7,2).c_str()));
+ tileY = uint32(atoi(files[i].substr(4,2).c_str()));
+ tileID = StaticMapTree::packTileID(tileY, tileX);
+
+ tiles->insert(tileID);
+ count++;
+ }
+
+ sprintf(filter, "%03u*", mapID);
+ files.clear();
+ getDirContents(files, "maps", filter);
+ for (uint32 i = 0; i < files.size(); ++i)
+ {
+ tileY = uint32(atoi(files[i].substr(3,2).c_str()));
+ tileX = uint32(atoi(files[i].substr(5,2).c_str()));
+ tileID = StaticMapTree::packTileID(tileX, tileY);
+
+ if (tiles->insert(tileID).second)
+ count++;
+ }
+ }
+ printf("found %u.\n\n", count);
+ }
+
+ /**************************************************************************/
+ set<uint32>* MapBuilder::getTileList(uint32 mapID)
+ {
+ TileList::iterator itr = m_tiles.find(mapID);
+ if (itr != m_tiles.end())
+ return (*itr).second;
+
+ set<uint32>* tiles = new set<uint32>();
+ m_tiles.insert(pair<uint32, set<uint32>*>(mapID, tiles));
+ return tiles;
+ }
+
+ /**************************************************************************/
+ void MapBuilder::buildAllMaps(int threads)
+ {
+ std::vector<BuilderThread*> _threads;
+
+ for (int i = 0; i < threads; ++i)
+ _threads.push_back(new BuilderThread(this));
+
+ for (TileList::iterator it = m_tiles.begin(); it != m_tiles.end(); ++it)
+ {
+ uint32 mapID = it->first;
+ if (!shouldSkipMap(mapID))
+ {
+ if (threads > 1)
+ {
+ bool next = false;
+ while (!next)
+ {
+ for (std::vector<BuilderThread*>::iterator _th = _threads.begin(); _th != _threads.end(); ++_th)
+ {
+ if ((*_th)->Free)
+ {
+ (*_th)->SetMapId(mapID);
+ (*_th)->activate();
+ next = true;
+ break;
+ }
+ }
+ // Wait for 20 seconds
+ ACE_OS::sleep(ACE_Time_Value (0, 20000));
+ }
+ }
+ else
+ buildMap(mapID);
+ }
+ }
+
+ // Free memory
+ for (std::vector<BuilderThread*>::iterator _th = _threads.begin(); _th != _threads.end(); ++_th)
+ {
+ (*_th)->wait();
+ delete *_th;
+ }
+ }
+
+ /**************************************************************************/
+ void MapBuilder::getGridBounds(uint32 mapID, uint32 &minX, uint32 &minY, uint32 &maxX, uint32 &maxY)
+ {
+ maxX = INT_MAX;
+ maxY = INT_MAX;
+ minX = INT_MIN;
+ minY = INT_MIN;
+
+ float bmin[3], bmax[3], lmin[3], lmax[3];
+ MeshData meshData;
+
+ // make sure we process maps which don't have tiles
+ // initialize the static tree, which loads WDT models
+ if (!m_terrainBuilder->loadVMap(mapID, 64, 64, meshData))
+ return;
+
+ // get the coord bounds of the model data
+ if (meshData.solidVerts.size() + meshData.liquidVerts.size() == 0)
+ return;
+
+ // get the coord bounds of the model data
+ if (meshData.solidVerts.size() && meshData.liquidVerts.size())
+ {
+ rcCalcBounds(meshData.solidVerts.getCArray(), meshData.solidVerts.size() / 3, bmin, bmax);
+ rcCalcBounds(meshData.liquidVerts.getCArray(), meshData.liquidVerts.size() / 3, lmin, lmax);
+ rcVmin(bmin, lmin);
+ rcVmax(bmax, lmax);
+ }
+ else if (meshData.solidVerts.size())
+ rcCalcBounds(meshData.solidVerts.getCArray(), meshData.solidVerts.size() / 3, bmin, bmax);
+ else
+ rcCalcBounds(meshData.liquidVerts.getCArray(), meshData.liquidVerts.size() / 3, lmin, lmax);
+
+ // convert coord bounds to grid bounds
+ maxX = 32 - bmin[0] / GRID_SIZE;
+ maxY = 32 - bmin[2] / GRID_SIZE;
+ minX = 32 - bmax[0] / GRID_SIZE;
+ minY = 32 - bmax[2] / GRID_SIZE;
+ }
+
+ void MapBuilder::buildMeshFromFile(char* name)
+ {
+ FILE* file = fopen(name, "rb");
+ if (!file)
+ return;
+
+ printf("Building mesh from file\n");
+ int tileX, tileY, mapId;
+ if (fread(&mapId, sizeof(int), 1, file) != 1)
+ return;
+ if (fread(&tileX, sizeof(int), 1, file) != 1)
+ return;
+ if (fread(&tileY, sizeof(int), 1, file) != 1)
+ return;
+
+ dtNavMesh* navMesh = NULL;
+ buildNavMesh(mapId, navMesh);
+ if (!navMesh)
+ {
+ printf("Failed creating navmesh! \n");
+ fclose(file);
+ return;
+ }
+
+ uint32 verticesCount, indicesCount;
+ if (fread(&verticesCount, sizeof(uint32), 1, file) != 1)
+ return;
+ if (fread(&indicesCount, sizeof(uint32), 1, file) != 1)
+ return;
+
+ float* verts = new float[verticesCount];
+ int* inds = new int[indicesCount];
+
+ if (fread(verts, sizeof(float), verticesCount, file) != verticesCount)
+ return;
+ if (fread(inds, sizeof(int), indicesCount, file) != indicesCount)
+ return;
+
+ MeshData data;
+
+ for (uint32 i = 0; i < verticesCount; ++i)
+ data.solidVerts.append(verts[i]);
+
+ for (uint32 i = 0; i < indicesCount; ++i)
+ data.solidTris.append(inds[i]);
+
+ TerrainBuilder::cleanVertices(data.solidVerts, data.solidTris);
+ // get bounds of current tile
+ float bmin[3], bmax[3];
+ getTileBounds(tileX, tileY, data.solidVerts.getCArray(), data.solidVerts.size() / 3, bmin, bmax);
+
+ // build navmesh tile
+ buildMoveMapTile(mapId, tileX, tileY, data, bmin, bmax, navMesh);
+ fclose(file);
+ }
+
+ /**************************************************************************/
+ void MapBuilder::buildSingleTile(uint32 mapID, uint32 tileX, uint32 tileY)
+ {
+ dtNavMesh* navMesh = NULL;
+ buildNavMesh(mapID, navMesh);
+ if (!navMesh)
+ {
+ printf("Failed creating navmesh! \n");
+ return;
+ }
+
+ buildTile(mapID, tileX, tileY, navMesh);
+ dtFreeNavMesh(navMesh);
+ }
+
+ /**************************************************************************/
+ void MapBuilder::buildMap(uint32 mapID)
+ {
+ printf("[Thread %u] Building map %03u:\n", uint32(ACE_Thread::self()), mapID);
+
+ set<uint32>* tiles = getTileList(mapID);
+
+ // make sure we process maps which don't have tiles
+ if (!tiles->size())
+ {
+ // convert coord bounds to grid bounds
+ uint32 minX, minY, maxX, maxY;
+ getGridBounds(mapID, minX, minY, maxX, maxY);
+
+ // add all tiles within bounds to tile list.
+ for (uint32 i = minX; i <= maxX; ++i)
+ for (uint32 j = minY; j <= maxY; ++j)
+ tiles->insert(StaticMapTree::packTileID(i, j));
+ }
+
+ if (!tiles->empty())
+ {
+ // build navMesh
+ dtNavMesh* navMesh = NULL;
+ buildNavMesh(mapID, navMesh);
+ if (!navMesh)
+ {
+ printf("[Map %i] Failed creating navmesh!\n", mapID);
+ return;
+ }
+
+ // now start building mmtiles for each tile
+ printf("[Map %i] We have %u tiles. \n", mapID, (unsigned int)tiles->size());
+ for (set<uint32>::iterator it = tiles->begin(); it != tiles->end(); ++it)
+ {
+ uint32 tileX, tileY;
+
+ // unpack tile coords
+ StaticMapTree::unpackTileID((*it), tileX, tileY);
+
+ if (shouldSkipTile(mapID, tileX, tileY))
+ continue;
+
+ buildTile(mapID, tileX, tileY, navMesh);
+ }
+
+ dtFreeNavMesh(navMesh);
+ }
+
+ printf("[Map %i] Complete!\n", mapID);
+ }
+
+ /**************************************************************************/
+ void MapBuilder::buildTile(uint32 mapID, uint32 tileX, uint32 tileY, dtNavMesh* navMesh)
+ {
+ printf("[Map %i] Building tile [%02u,%02u]\n", mapID, tileX, tileY);
+
+ MeshData meshData;
+
+ // get heightmap data
+ m_terrainBuilder->loadMap(mapID, tileX, tileY, meshData);
+
+ // get model data
+ m_terrainBuilder->loadVMap(mapID, tileY, tileX, meshData);
+
+ // if there is no data, give up now
+ if (!meshData.solidVerts.size() && !meshData.liquidVerts.size())
+ return;
+
+ // remove unused vertices
+ TerrainBuilder::cleanVertices(meshData.solidVerts, meshData.solidTris);
+ TerrainBuilder::cleanVertices(meshData.liquidVerts, meshData.liquidTris);
+
+ // gather all mesh data for final data check, and bounds calculation
+ G3D::Array<float> allVerts;
+ allVerts.append(meshData.liquidVerts);
+ allVerts.append(meshData.solidVerts);
+
+ if (!allVerts.size())
+ return;
+
+ // get bounds of current tile
+ float bmin[3], bmax[3];
+ getTileBounds(tileX, tileY, allVerts.getCArray(), allVerts.size() / 3, bmin, bmax);
+
+ m_terrainBuilder->loadOffMeshConnections(mapID, tileX, tileY, meshData, m_offMeshFilePath);
+
+ // build navmesh tile
+ buildMoveMapTile(mapID, tileX, tileY, meshData, bmin, bmax, navMesh);
+ }
+
+ /**************************************************************************/
+ void MapBuilder::buildNavMesh(uint32 mapID, dtNavMesh* &navMesh)
+ {
+ set<uint32>* tiles = getTileList(mapID);
+
+ // old code for non-statically assigned bitmask sizes:
+ ///*** calculate number of bits needed to store tiles & polys ***/
+ //int tileBits = dtIlog2(dtNextPow2(tiles->size()));
+ //if (tileBits < 1) tileBits = 1; // need at least one bit!
+ //int polyBits = sizeof(dtPolyRef)*8 - SALT_MIN_BITS - tileBits;
+
+ int polyBits = STATIC_POLY_BITS;
+
+ int maxTiles = tiles->size();
+ int maxPolysPerTile = 1 << polyBits;
+
+ /*** calculate bounds of map ***/
+
+ uint32 tileXMin = 64, tileYMin = 64, tileXMax = 0, tileYMax = 0, tileX, tileY;
+ for (set<uint32>::iterator it = tiles->begin(); it != tiles->end(); ++it)
+ {
+ StaticMapTree::unpackTileID((*it), tileX, tileY);
+
+ if (tileX > tileXMax)
+ tileXMax = tileX;
+ else if (tileX < tileXMin)
+ tileXMin = tileX;
+
+ if (tileY > tileYMax)
+ tileYMax = tileY;
+ else if (tileY < tileYMin)
+ tileYMin = tileY;
+ }
+
+ // use Max because '32 - tileX' is negative for values over 32
+ float bmin[3], bmax[3];
+ getTileBounds(tileXMax, tileYMax, NULL, 0, bmin, bmax);
+
+ /*** now create the navmesh ***/
+
+ // navmesh creation params
+ dtNavMeshParams navMeshParams;
+ memset(&navMeshParams, 0, sizeof(dtNavMeshParams));
+ navMeshParams.tileWidth = GRID_SIZE;
+ navMeshParams.tileHeight = GRID_SIZE;
+ rcVcopy(navMeshParams.orig, bmin);
+ navMeshParams.maxTiles = maxTiles;
+ navMeshParams.maxPolys = maxPolysPerTile;
+
+ navMesh = dtAllocNavMesh();
+ printf("[Map %i] Creating navMesh...\n", mapID);
+ if (!navMesh->init(&navMeshParams))
+ {
+ printf("[Map %i] Failed creating navmesh! \n", mapID);
+ return;
+ }
+
+ char fileName[25];
+ sprintf(fileName, "mmaps/%03u.mmap", mapID);
+
+ FILE* file = fopen(fileName, "wb");
+ if (!file)
+ {
+ dtFreeNavMesh(navMesh);
+ char message[1024];
+ sprintf(message, "[Map %i] Failed to open %s for writing!\n", mapID, fileName);
+ perror(message);
+ return;
+ }
+
+ // now that we know navMesh params are valid, we can write them to file
+ fwrite(&navMeshParams, sizeof(dtNavMeshParams), 1, file);
+ fclose(file);
+ }
+
+ /**************************************************************************/
+ void MapBuilder::buildMoveMapTile(uint32 mapID, uint32 tileX, uint32 tileY,
+ MeshData &meshData, float bmin[3], float bmax[3],
+ dtNavMesh* navMesh)
+ {
+ // console output
+ char tileString[20];
+ sprintf(tileString, "[Map %03i] [%02i,%02i]: ", mapID, tileX, tileY);
+ printf("%s Building movemap tiles...\n", tileString);
+
+ IntermediateValues iv;
+
+ float* tVerts = meshData.solidVerts.getCArray();
+ int tVertCount = meshData.solidVerts.size() / 3;
+ int* tTris = meshData.solidTris.getCArray();
+ int tTriCount = meshData.solidTris.size() / 3;
+
+ float* lVerts = meshData.liquidVerts.getCArray();
+ int lVertCount = meshData.liquidVerts.size() / 3;
+ int* lTris = meshData.liquidTris.getCArray();
+ int lTriCount = meshData.liquidTris.size() / 3;
+ uint8* lTriFlags = meshData.liquidType.getCArray();
+
+ // these are WORLD UNIT based metrics
+ // this are basic unit dimentions
+ // value have to divide GRID_SIZE(533.33333f) ( aka: 0.5333, 0.2666, 0.3333, 0.1333, etc )
+ const static float BASE_UNIT_DIM = m_bigBaseUnit ? 0.533333f : 0.266666f;
+
+ // All are in UNIT metrics!
+ const static int VERTEX_PER_MAP = int(GRID_SIZE/BASE_UNIT_DIM + 0.5f);
+ const static int VERTEX_PER_TILE = m_bigBaseUnit ? 40 : 80; // must divide VERTEX_PER_MAP
+ const static int TILES_PER_MAP = VERTEX_PER_MAP/VERTEX_PER_TILE;
+
+ rcConfig config;
+ memset(&config, 0, sizeof(rcConfig));
+
+ rcVcopy(config.bmin, bmin);
+ rcVcopy(config.bmax, bmax);
+
+ config.maxVertsPerPoly = DT_VERTS_PER_POLYGON;
+ config.cs = BASE_UNIT_DIM;
+ config.ch = BASE_UNIT_DIM;
+ config.walkableSlopeAngle = m_maxWalkableAngle;
+ config.tileSize = VERTEX_PER_TILE;
+ config.walkableRadius = m_bigBaseUnit ? 1 : 2;
+ config.borderSize = config.walkableRadius + 3;
+ config.maxEdgeLen = VERTEX_PER_TILE + 1; //anything bigger than tileSize
+ config.walkableHeight = m_bigBaseUnit ? 3 : 6;
+ config.walkableClimb = m_bigBaseUnit ? 2 : 4; // keep less than walkableHeight
+ config.minRegionArea = rcSqr(60);
+ config.mergeRegionArea = rcSqr(50);
+ config.maxSimplificationError = 2.0f; // eliminates most jagged edges (tinny polygons)
+ config.detailSampleDist = config.cs * 64;
+ config.detailSampleMaxError = config.ch * 2;
+
+ // this sets the dimensions of the heightfield - should maybe happen before border padding
+ rcCalcGridSize(config.bmin, config.bmax, config.cs, &config.width, &config.height);
+
+ // allocate subregions : tiles
+ Tile* tiles = new Tile[TILES_PER_MAP * TILES_PER_MAP];
+
+ // Initialize per tile config.
+ rcConfig tileCfg = config;
+ tileCfg.width = config.tileSize + config.borderSize*2;
+ tileCfg.height = config.tileSize + config.borderSize*2;
+
+ // merge per tile poly and detail meshes
+ rcPolyMesh** pmmerge = new rcPolyMesh*[TILES_PER_MAP * TILES_PER_MAP];
+ if (!pmmerge)
+ {
+ printf("%s alloc pmmerge FIALED!\n", tileString);
+ return;
+ }
+
+ rcPolyMeshDetail** dmmerge = new rcPolyMeshDetail*[TILES_PER_MAP * TILES_PER_MAP];
+ if (!dmmerge)
+ {
+ printf("%s alloc dmmerge FIALED!\n", tileString);
+ return;
+ }
+
+ int nmerge = 0;
+ // build all tiles
+ for (int y = 0; y < TILES_PER_MAP; ++y)
+ {
+ for (int x = 0; x < TILES_PER_MAP; ++x)
+ {
+ Tile& tile = tiles[x + y * TILES_PER_MAP];
+
+ // Calculate the per tile bounding box.
+ tileCfg.bmin[0] = config.bmin[0] + float(x*config.tileSize - config.borderSize)*config.cs;
+ tileCfg.bmin[2] = config.bmin[2] + float(y*config.tileSize - config.borderSize)*config.cs;
+ tileCfg.bmax[0] = config.bmin[0] + float((x+1)*config.tileSize + config.borderSize)*config.cs;
+ tileCfg.bmax[2] = config.bmin[2] + float((y+1)*config.tileSize + config.borderSize)*config.cs;
+
+ // build heightfield
+ tile.solid = rcAllocHeightfield();
+ if (!tile.solid || !rcCreateHeightfield(m_rcContext, *tile.solid, tileCfg.width, tileCfg.height, tileCfg.bmin, tileCfg.bmax, tileCfg.cs, tileCfg.ch))
+ {
+ printf("%s Failed building heightfield! \n", tileString);
+ continue;
+ }
+
+ // mark all walkable tiles, both liquids and solids
+ unsigned char* triFlags = new unsigned char[tTriCount];
+ memset(triFlags, NAV_GROUND, tTriCount*sizeof(unsigned char));
+ rcClearUnwalkableTriangles(m_rcContext, tileCfg.walkableSlopeAngle, tVerts, tVertCount, tTris, tTriCount, triFlags);
+ rcRasterizeTriangles(m_rcContext, tVerts, tVertCount, tTris, triFlags, tTriCount, *tile.solid, config.walkableClimb);
+ delete[] triFlags;
+
+ rcFilterLowHangingWalkableObstacles(m_rcContext, config.walkableClimb, *tile.solid);
+ rcFilterLedgeSpans(m_rcContext, tileCfg.walkableHeight, tileCfg.walkableClimb, *tile.solid);
+ rcFilterWalkableLowHeightSpans(m_rcContext, tileCfg.walkableHeight, *tile.solid);
+
+ rcRasterizeTriangles(m_rcContext, lVerts, lVertCount, lTris, lTriFlags, lTriCount, *tile.solid, config.walkableClimb);
+
+ // compact heightfield spans
+ tile.chf = rcAllocCompactHeightfield();
+ if (!tile.chf || !rcBuildCompactHeightfield(m_rcContext, tileCfg.walkableHeight, tileCfg.walkableClimb, *tile.solid, *tile.chf))
+ {
+ printf("%s Failed compacting heightfield! \n", tileString);
+ continue;
+ }
+
+ // build polymesh intermediates
+ if (!rcErodeWalkableArea(m_rcContext, config.walkableRadius, *tile.chf))
+ {
+ printf("%s Failed eroding area! \n", tileString);
+ continue;
+ }
+
+ if (!rcBuildDistanceField(m_rcContext, *tile.chf))
+ {
+ printf("%s Failed building distance field! \n", tileString);
+ continue;
+ }
+
+ if (!rcBuildRegions(m_rcContext, *tile.chf, tileCfg.borderSize, tileCfg.minRegionArea, tileCfg.mergeRegionArea))
+ {
+ printf("%s Failed building regions! \n", tileString);
+ continue;
+ }
+
+ tile.cset = rcAllocContourSet();
+ if (!tile.cset || !rcBuildContours(m_rcContext, *tile.chf, tileCfg.maxSimplificationError, tileCfg.maxEdgeLen, *tile.cset))
+ {
+ printf("%s Failed building contours! \n", tileString);
+ continue;
+ }
+
+ // build polymesh
+ tile.pmesh = rcAllocPolyMesh();
+ if (!tile.pmesh || !rcBuildPolyMesh(m_rcContext, *tile.cset, tileCfg.maxVertsPerPoly, *tile.pmesh))
+ {
+ printf("%s Failed building polymesh! \n", tileString);
+ continue;
+ }
+
+ tile.dmesh = rcAllocPolyMeshDetail();
+ if (!tile.dmesh || !rcBuildPolyMeshDetail(m_rcContext, *tile.pmesh, *tile.chf, tileCfg.detailSampleDist, tileCfg.detailSampleMaxError, *tile.dmesh))
+ {
+ printf("%s Failed building polymesh detail! \n", tileString);
+ continue;
+ }
+
+ // free those up
+ // we may want to keep them in the future for debug
+ // but right now, we don't have the code to merge them
+ rcFreeHeightField(tile.solid);
+ tile.solid = NULL;
+ rcFreeCompactHeightfield(tile.chf);
+ tile.chf = NULL;
+ rcFreeContourSet(tile.cset);
+ tile.cset = NULL;
+
+ if (tile.pmesh)
+ {
+ pmmerge[nmerge] = tile.pmesh;
+ dmmerge[nmerge] = tile.dmesh;
+ nmerge++;
+ }
+ }
+ }
+
+ iv.polyMesh = rcAllocPolyMesh();
+ if (!iv.polyMesh)
+ {
+ printf("%s alloc iv.polyMesh FIALED!\n", tileString);
+ return;
+ }
+ rcMergePolyMeshes(m_rcContext, pmmerge, nmerge, *iv.polyMesh);
+
+ iv.polyMeshDetail = rcAllocPolyMeshDetail();
+ if (!iv.polyMeshDetail)
+ {
+ printf("%s alloc m_dmesh FIALED!\n", tileString);
+ return;
+ }
+ rcMergePolyMeshDetails(m_rcContext, dmmerge, nmerge, *iv.polyMeshDetail);
+
+ // free things up
+ delete[] pmmerge;
+ delete[] dmmerge;
+
+ delete[] tiles;
+
+ // remove padding for extraction
+ for (int i = 0; i < iv.polyMesh->nverts; ++i)
+ {
+ unsigned short* v = &iv.polyMesh->verts[i*3];
+ v[0] -= (unsigned short)config.borderSize;
+ v[2] -= (unsigned short)config.borderSize;
+ }
+
+ // set polygons as walkable
+ // TODO: special flags for DYNAMIC polygons, ie surfaces that can be turned on and off
+ for (int i = 0; i < iv.polyMesh->npolys; ++i)
+ if (iv.polyMesh->areas[i] & RC_WALKABLE_AREA)
+ iv.polyMesh->flags[i] = iv.polyMesh->areas[i];
+
+ // setup mesh parameters
+ dtNavMeshCreateParams params;
+ memset(&params, 0, sizeof(params));
+ params.verts = iv.polyMesh->verts;
+ params.vertCount = iv.polyMesh->nverts;
+ params.polys = iv.polyMesh->polys;
+ params.polyAreas = iv.polyMesh->areas;
+ params.polyFlags = iv.polyMesh->flags;
+ params.polyCount = iv.polyMesh->npolys;
+ params.nvp = iv.polyMesh->nvp;
+ params.detailMeshes = iv.polyMeshDetail->meshes;
+ params.detailVerts = iv.polyMeshDetail->verts;
+ params.detailVertsCount = iv.polyMeshDetail->nverts;
+ params.detailTris = iv.polyMeshDetail->tris;
+ params.detailTriCount = iv.polyMeshDetail->ntris;
+
+ params.offMeshConVerts = meshData.offMeshConnections.getCArray();
+ params.offMeshConCount = meshData.offMeshConnections.size()/6;
+ params.offMeshConRad = meshData.offMeshConnectionRads.getCArray();
+ params.offMeshConDir = meshData.offMeshConnectionDirs.getCArray();
+ params.offMeshConAreas = meshData.offMeshConnectionsAreas.getCArray();
+ params.offMeshConFlags = meshData.offMeshConnectionsFlags.getCArray();
+
+ params.walkableHeight = BASE_UNIT_DIM*config.walkableHeight; // agent height
+ params.walkableRadius = BASE_UNIT_DIM*config.walkableRadius; // agent radius
+ params.walkableClimb = BASE_UNIT_DIM*config.walkableClimb; // keep less that walkableHeight (aka agent height)!
+ params.tileX = (((bmin[0] + bmax[0]) / 2) - navMesh->getParams()->orig[0]) / GRID_SIZE;
+ params.tileY = (((bmin[2] + bmax[2]) / 2) - navMesh->getParams()->orig[2]) / GRID_SIZE;
+ rcVcopy(params.bmin, bmin);
+ rcVcopy(params.bmax, bmax);
+ params.cs = config.cs;
+ params.ch = config.ch;
+ params.tileSize = VERTEX_PER_MAP;
+
+ // will hold final navmesh
+ unsigned char* navData = NULL;
+ int navDataSize = 0;
+
+ do
+ {
+ // these values are checked within dtCreateNavMeshData - handle them here
+ // so we have a clear error message
+ if (params.nvp > DT_VERTS_PER_POLYGON)
+ {
+ printf("%s Invalid verts-per-polygon value! \n", tileString);
+ continue;
+ }
+ if (params.vertCount >= 0xffff)
+ {
+ printf("%s Too many vertices! \n", tileString);
+ continue;
+ }
+ if (!params.vertCount || !params.verts)
+ {
+ // occurs mostly when adjacent tiles have models
+ // loaded but those models don't span into this tile
+
+ // message is an annoyance
+ //printf("%sNo vertices to build tile! \n", tileString);
+ continue;
+ }
+ if (!params.polyCount || !params.polys ||
+ TILES_PER_MAP*TILES_PER_MAP == params.polyCount)
+ {
+ // we have flat tiles with no actual geometry - don't build those, its useless
+ // keep in mind that we do output those into debug info
+ // drop tiles with only exact count - some tiles may have geometry while having less tiles
+ printf("%s No polygons to build on tile! \n", tileString);
+ continue;
+ }
+ if (!params.detailMeshes || !params.detailVerts || !params.detailTris)
+ {
+ printf("%s No detail mesh to build tile! \n", tileString);
+ continue;
+ }
+
+ printf("%s Building navmesh tile...\n", tileString);
+ if (!dtCreateNavMeshData(&params, &navData, &navDataSize))
+ {
+ printf("%s Failed building navmesh tile! \n", tileString);
+ continue;
+ }
+
+ dtTileRef tileRef = 0;
+ printf("%s Adding tile to navmesh...\n", tileString);
+ // DT_TILE_FREE_DATA tells detour to unallocate memory when the tile
+ // is removed via removeTile()
+ dtStatus dtResult = navMesh->addTile(navData, navDataSize, DT_TILE_FREE_DATA, 0, &tileRef);
+ if (!tileRef || dtResult != DT_SUCCESS)
+ {
+ printf("%s Failed adding tile to navmesh! \n", tileString);
+ continue;
+ }
+
+ // file output
+ char fileName[255];
+ sprintf(fileName, "mmaps/%03u%02i%02i.mmtile", mapID, tileY, tileX);
+ FILE* file = fopen(fileName, "wb");
+ if (!file)
+ {
+ char message[1024];
+ sprintf(message, "[Map %i] Failed to open %s for writing!\n", mapID, fileName);
+ perror(message);
+ navMesh->removeTile(tileRef, NULL, NULL);
+ continue;
+ }
+
+ printf("%s Writing to file...\n", tileString);
+
+ // write header
+ MmapTileHeader header;
+ header.usesLiquids = m_terrainBuilder->usesLiquids();
+ header.size = uint32(navDataSize);
+ fwrite(&header, sizeof(MmapTileHeader), 1, file);
+
+ // write data
+ fwrite(navData, sizeof(unsigned char), navDataSize, file);
+ fclose(file);
+
+ // now that tile is written to disk, we can unload it
+ navMesh->removeTile(tileRef, NULL, NULL);
+ }
+ while (0);
+
+ if (m_debugOutput)
+ {
+ // restore padding so that the debug visualization is correct
+ for (int i = 0; i < iv.polyMesh->nverts; ++i)
+ {
+ unsigned short* v = &iv.polyMesh->verts[i*3];
+ v[0] += (unsigned short)config.borderSize;
+ v[2] += (unsigned short)config.borderSize;
+ }
+
+ iv.generateObjFile(mapID, tileX, tileY, meshData);
+ iv.writeIV(mapID, tileX, tileY);
+ }
+ }
+
+ /**************************************************************************/
+ void MapBuilder::getTileBounds(uint32 tileX, uint32 tileY, float* verts, int vertCount, float* bmin, float* bmax)
+ {
+ // this is for elevation
+ if (verts && vertCount)
+ rcCalcBounds(verts, vertCount, bmin, bmax);
+ else
+ {
+ bmin[1] = FLT_MIN;
+ bmax[1] = FLT_MAX;
+ }
+
+ // this is for width and depth
+ bmax[0] = (32 - int(tileX)) * GRID_SIZE;
+ bmax[2] = (32 - int(tileY)) * GRID_SIZE;
+ bmin[0] = bmax[0] - GRID_SIZE;
+ bmin[2] = bmax[2] - GRID_SIZE;
+ }
+
+ /**************************************************************************/
+ bool MapBuilder::shouldSkipMap(uint32 mapID)
+ {
+ if (m_skipContinents)
+ switch (mapID)
+ {
+ case 0:
+ case 1:
+ case 530:
+ case 571:
+ return true;
+ default:
+ break;
+ }
+
+ if (m_skipJunkMaps)
+ switch (mapID)
+ {
+ case 13: // test.wdt
+ case 25: // ScottTest.wdt
+ case 29: // Test.wdt
+ case 42: // Colin.wdt
+ case 169: // EmeraldDream.wdt (unused, and very large)
+ case 451: // development.wdt
+ case 573: // ExteriorTest.wdt
+ case 597: // CraigTest.wdt
+ case 605: // development_nonweighted.wdt
+ case 606: // QA_DVD.wdt
+ return true;
+ default:
+ if (isTransportMap(mapID))
+ return true;
+ break;
+ }
+
+ if (m_skipBattlegrounds)
+ switch (mapID)
+ {
+ case 30: // AV
+ case 37: // ?
+ case 489: // WSG
+ case 529: // AB
+ case 566: // EotS
+ case 607: // SotA
+ case 628: // IoC
+ return true;
+ default:
+ break;
+ }
+
+ return false;
+ }
+
+ /**************************************************************************/
+ bool MapBuilder::isTransportMap(uint32 mapID)
+ {
+ switch (mapID)
+ {
+ // transport maps
+ case 582:
+ case 584:
+ case 586:
+ case 587:
+ case 588:
+ case 589:
+ case 590:
+ case 591:
+ case 592:
+ case 593:
+ case 594:
+ case 596:
+ case 610:
+ case 612:
+ case 613:
+ case 614:
+ case 620:
+ case 621:
+ case 622:
+ case 623:
+ case 641:
+ case 642:
+ case 647:
+ case 672:
+ case 673:
+ case 712:
+ case 713:
+ case 718:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**************************************************************************/
+ bool MapBuilder::shouldSkipTile(uint32 mapID, uint32 tileX, uint32 tileY)
+ {
+ char fileName[255];
+ sprintf(fileName, "mmaps/%03u%02i%02i.mmtile", mapID, tileY, tileX);
+ FILE* file = fopen(fileName, "rb");
+ if (!file)
+ return false;
+
+ MmapTileHeader header;
+ int count = fread(&header, sizeof(MmapTileHeader), 1, file);
+ fclose(file);
+ if (count != 1)
+ return false;
+
+ if (header.mmapMagic != MMAP_MAGIC || header.dtVersion != DT_NAVMESH_VERSION)
+ return false;
+
+ if (header.mmapVersion != MMAP_VERSION)
+ return false;
+
+ return true;
+ }
+
+}
diff --git a/src/tools/mmaps_generator/MapBuilder.h b/src/tools/mmaps_generator/MapBuilder.h
new file mode 100644
index 00000000000..c083e0b680a
--- /dev/null
+++ b/src/tools/mmaps_generator/MapBuilder.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2008-2013 TrinityCore <http://www.trinitycore.org/>
+ * Copyright (C) 2005-2011 MaNGOS <http://getmangos.com/>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _MAP_BUILDER_H
+#define _MAP_BUILDER_H
+
+#include <vector>
+#include <set>
+#include <map>
+
+#include "TerrainBuilder.h"
+#include "IntermediateValues.h"
+
+#include "IVMapManager.h"
+#include "WorldModel.h"
+
+#include "Recast.h"
+#include "DetourNavMesh.h"
+
+#include "ace/Task.h"
+
+using namespace std;
+using namespace VMAP;
+
+// G3D namespace typedefs conflicts with ACE typedefs
+
+namespace MMAP
+{
+ typedef map<uint32,set<uint32>*> TileList;
+ struct Tile
+ {
+ Tile() : chf(NULL), solid(NULL), cset(NULL), pmesh(NULL), dmesh(NULL) {}
+ ~Tile()
+ {
+ rcFreeCompactHeightfield(chf);
+ rcFreeContourSet(cset);
+ rcFreeHeightField(solid);
+ rcFreePolyMesh(pmesh);
+ rcFreePolyMeshDetail(dmesh);
+ }
+ rcCompactHeightfield* chf;
+ rcHeightfield* solid;
+ rcContourSet* cset;
+ rcPolyMesh* pmesh;
+ rcPolyMeshDetail* dmesh;
+ };
+
+ class MapBuilder
+ {
+ public:
+ MapBuilder(float maxWalkableAngle = 60.f,
+ bool skipLiquid = false,
+ bool skipContinents = false,
+ bool skipJunkMaps = true,
+ bool skipBattlegrounds = false,
+ bool debugOutput = false,
+ bool bigBaseUnit = false,
+ const char* offMeshFilePath = NULL);
+
+ ~MapBuilder();
+
+ // builds all mmap tiles for the specified map id (ignores skip settings)
+ void buildMap(uint32 mapID);
+ void buildMeshFromFile(char* name);
+
+ // builds an mmap tile for the specified map and its mesh
+ void buildSingleTile(uint32 mapID, uint32 tileX, uint32 tileY);
+
+ // builds list of maps, then builds all of mmap tiles (based on the skip settings)
+ void buildAllMaps(int threads);
+
+ private:
+ // detect maps and tiles
+ void discoverTiles();
+ set<uint32>* getTileList(uint32 mapID);
+
+ void buildNavMesh(uint32 mapID, dtNavMesh* &navMesh);
+
+ void buildTile(uint32 mapID, uint32 tileX, uint32 tileY, dtNavMesh* navMesh);
+
+ // move map building
+ void buildMoveMapTile(uint32 mapID,
+ uint32 tileX,
+ uint32 tileY,
+ MeshData &meshData,
+ float bmin[3],
+ float bmax[3],
+ dtNavMesh* navMesh);
+
+ void getTileBounds(uint32 tileX, uint32 tileY,
+ float* verts, int vertCount,
+ float* bmin, float* bmax);
+ void getGridBounds(uint32 mapID, uint32 &minX, uint32 &minY, uint32 &maxX, uint32 &maxY);
+
+ bool shouldSkipMap(uint32 mapID);
+ bool isTransportMap(uint32 mapID);
+ bool shouldSkipTile(uint32 mapID, uint32 tileX, uint32 tileY);
+
+ TerrainBuilder* m_terrainBuilder;
+ TileList m_tiles;
+
+ bool m_debugOutput;
+
+ const char* m_offMeshFilePath;
+ bool m_skipContinents;
+ bool m_skipJunkMaps;
+ bool m_skipBattlegrounds;
+
+ float m_maxWalkableAngle;
+ bool m_bigBaseUnit;
+
+ // build performance - not really used for now
+ rcContext* m_rcContext;
+ };
+
+ class BuilderThread : public ACE_Task<ACE_MT_SYNCH>
+ {
+ private:
+ MapBuilder* _builder;
+ uint32 _mapId;
+ public:
+ BuilderThread(MapBuilder* builder) : _builder(builder), Free(true) {}
+
+ void SetMapId(uint32 mapId) { _mapId = mapId; }
+
+ int svc()
+ {
+ Free = false;
+ if (_builder)
+ _builder->buildMap(_mapId);
+ Free = true;
+ return 0;
+ }
+
+ bool Free;
+ };
+}
+
+#endif
diff --git a/src/tools/mmaps_generator/PathCommon.h b/src/tools/mmaps_generator/PathCommon.h
new file mode 100644
index 00000000000..413f2b8735d
--- /dev/null
+++ b/src/tools/mmaps_generator/PathCommon.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2008-2013 TrinityCore <http://www.trinitycore.org/>
+ * Copyright (C) 2005-2011 MaNGOS <http://getmangos.com/>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _MMAP_COMMON_H
+#define _MMAP_COMMON_H
+
+#include <string>
+#include <vector>
+
+#include "Define.h"
+
+#ifndef _WIN32
+ #include <stddef.h>
+ #include <dirent.h>
+#endif
+
+#ifdef __linux__
+ #include <errno.h>
+#endif
+
+using namespace std;
+
+namespace MMAP
+{
+ inline bool matchWildcardFilter(const char* filter, const char* str)
+ {
+ if (!filter || !str)
+ return false;
+
+ // end on null character
+ while (*filter && *str)
+ {
+ if (*filter == '*')
+ {
+ if (*++filter == '\0') // wildcard at end of filter means all remaing chars match
+ return true;
+
+ while (true)
+ {
+ if (*filter == *str)
+ break;
+ if (*str == '\0')
+ return false; // reached end of string without matching next filter character
+ str++;
+ }
+ }
+ else if (*filter != *str)
+ return false; // mismatch
+
+ filter++;
+ str++;
+ }
+
+ return ((*filter == '\0' || (*filter == '*' && *++filter == '\0')) && *str == '\0');
+ }
+
+ enum ListFilesResult
+ {
+ LISTFILE_DIRECTORY_NOT_FOUND = 0,
+ LISTFILE_OK = 1
+ };
+
+ inline ListFilesResult getDirContents(vector<string> &fileList, string dirpath = ".", string filter = "*")
+ {
+ #ifdef WIN32
+ HANDLE hFind;
+ WIN32_FIND_DATA findFileInfo;
+ string directory;
+
+ directory = dirpath + "/" + filter;
+
+ hFind = FindFirstFile(directory.c_str(), &findFileInfo);
+
+ if (hFind == INVALID_HANDLE_VALUE)
+ return LISTFILE_DIRECTORY_NOT_FOUND;
+ do
+ {
+ if ((findFileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
+ fileList.push_back(string(findFileInfo.cFileName));
+ }
+ while (FindNextFile(hFind, &findFileInfo));
+
+ FindClose(hFind);
+
+ #else
+ const char *p = dirpath.c_str();
+ DIR * dirp = opendir(p);
+ struct dirent * dp;
+ dirp = opendir(p);
+
+ while (dirp)
+ {
+ errno = 0;
+ if ((dp = readdir(dirp)) != NULL)
+ {
+ if (matchWildcardFilter(filter.c_str(), dp->d_name))
+ fileList.push_back(string(dp->d_name));
+ }
+ else
+ break;
+ }
+
+ if (dirp)
+ closedir(dirp);
+ else
+ return LISTFILE_DIRECTORY_NOT_FOUND;
+ #endif
+
+ return LISTFILE_OK;
+ }
+}
+
+#endif
diff --git a/src/tools/mmaps_generator/PathGenerator.cpp b/src/tools/mmaps_generator/PathGenerator.cpp
new file mode 100644
index 00000000000..9707cb4160a
--- /dev/null
+++ b/src/tools/mmaps_generator/PathGenerator.cpp
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2005-2011 MaNGOS <http://getmangos.com/>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "PathCommon.h"
+#include "MapBuilder.h"
+#include "Timer.h"
+
+using namespace MMAP;
+
+bool checkDirectories(bool debugOutput)
+{
+ vector<string> dirFiles;
+
+ if (getDirContents(dirFiles, "maps") == LISTFILE_DIRECTORY_NOT_FOUND || dirFiles.empty())
+ {
+ printf("'maps' directory is empty or does not exist\n");
+ return false;
+ }
+
+ dirFiles.clear();
+ if (getDirContents(dirFiles, "vmaps", "*.vmtree") == LISTFILE_DIRECTORY_NOT_FOUND || dirFiles.empty())
+ {
+ printf("'vmaps' directory is empty or does not exist\n");
+ return false;
+ }
+
+ dirFiles.clear();
+ if (getDirContents(dirFiles, "mmaps") == LISTFILE_DIRECTORY_NOT_FOUND)
+ {
+ printf("'mmaps' directory does not exist\n");
+ return false;
+ }
+
+ dirFiles.clear();
+ if (debugOutput)
+ {
+ if (getDirContents(dirFiles, "meshes") == LISTFILE_DIRECTORY_NOT_FOUND)
+ {
+ printf("'meshes' directory does not exist (no place to put debugOutput files)\n");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool handleArgs(int argc, char** argv,
+ int &mapnum,
+ int &tileX,
+ int &tileY,
+ float &maxAngle,
+ bool &skipLiquid,
+ bool &skipContinents,
+ bool &skipJunkMaps,
+ bool &skipBattlegrounds,
+ bool &debugOutput,
+ bool &silent,
+ bool &bigBaseUnit,
+ char* &offMeshInputPath,
+ char* &file,
+ int& threads)
+{
+ char* param = NULL;
+ for (int i = 1; i < argc; ++i)
+ {
+ if (strcmp(argv[i], "--maxAngle") == 0)
+ {
+ param = argv[++i];
+ if (!param)
+ return false;
+
+ float maxangle = atof(param);
+ if (maxangle <= 90.f && maxangle >= 45.f)
+ maxAngle = maxangle;
+ else
+ printf("invalid option for '--maxAngle', using default\n");
+ }
+ else if (strcmp(argv[i], "--threads") == 0)
+ {
+ param = argv[++i];
+ if (!param)
+ return false;
+ threads = atoi(param);
+ printf("Using %i threads to extract mmaps\n", threads);
+ }
+ else if (strcmp(argv[i], "--file") == 0)
+ {
+ param = argv[++i];
+ if (!param)
+ return false;
+ file = param;
+ }
+ else if (strcmp(argv[i], "--tile") == 0)
+ {
+ param = argv[++i];
+ if (!param)
+ return false;
+
+ char* stileX = strtok(param, ",");
+ char* stileY = strtok(NULL, ",");
+ int tilex = atoi(stileX);
+ int tiley = atoi(stileY);
+
+ if ((tilex > 0 && tilex < 64) || (tilex == 0 && strcmp(stileX, "0") == 0))
+ tileX = tilex;
+ if ((tiley > 0 && tiley < 64) || (tiley == 0 && strcmp(stileY, "0") == 0))
+ tileY = tiley;
+
+ if (tileX < 0 || tileY < 0)
+ {
+ printf("invalid tile coords.\n");
+ return false;
+ }
+ }
+ else if (strcmp(argv[i], "--skipLiquid") == 0)
+ {
+ param = argv[++i];
+ if (!param)
+ return false;
+
+ if (strcmp(param, "true") == 0)
+ skipLiquid = true;
+ else if (strcmp(param, "false") == 0)
+ skipLiquid = false;
+ else
+ printf("invalid option for '--skipLiquid', using default\n");
+ }
+ else if (strcmp(argv[i], "--skipContinents") == 0)
+ {
+ param = argv[++i];
+ if (!param)
+ return false;
+
+ if (strcmp(param, "true") == 0)
+ skipContinents = true;
+ else if (strcmp(param, "false") == 0)
+ skipContinents = false;
+ else
+ printf("invalid option for '--skipContinents', using default\n");
+ }
+ else if (strcmp(argv[i], "--skipJunkMaps") == 0)
+ {
+ param = argv[++i];
+ if (!param)
+ return false;
+
+ if (strcmp(param, "true") == 0)
+ skipJunkMaps = true;
+ else if (strcmp(param, "false") == 0)
+ skipJunkMaps = false;
+ else
+ printf("invalid option for '--skipJunkMaps', using default\n");
+ }
+ else if (strcmp(argv[i], "--skipBattlegrounds") == 0)
+ {
+ param = argv[++i];
+ if (!param)
+ return false;
+
+ if (strcmp(param, "true") == 0)
+ skipBattlegrounds = true;
+ else if (strcmp(param, "false") == 0)
+ skipBattlegrounds = false;
+ else
+ printf("invalid option for '--skipBattlegrounds', using default\n");
+ }
+ else if (strcmp(argv[i], "--debugOutput") == 0)
+ {
+ param = argv[++i];
+ if (!param)
+ return false;
+
+ if (strcmp(param, "true") == 0)
+ debugOutput = true;
+ else if (strcmp(param, "false") == 0)
+ debugOutput = false;
+ else
+ printf("invalid option for '--debugOutput', using default true\n");
+ }
+ else if (strcmp(argv[i], "--silent") == 0)
+ {
+ silent = true;
+ }
+ else if (strcmp(argv[i], "--bigBaseUnit") == 0)
+ {
+ param = argv[++i];
+ if (!param)
+ return false;
+
+ if (strcmp(param, "true") == 0)
+ bigBaseUnit = true;
+ else if (strcmp(param, "false") == 0)
+ bigBaseUnit = false;
+ else
+ printf("invalid option for '--bigBaseUnit', using default false\n");
+ }
+ else if (strcmp(argv[i], "--offMeshInput") == 0)
+ {
+ param = argv[++i];
+ if (!param)
+ return false;
+
+ offMeshInputPath = param;
+ }
+ else
+ {
+ int map = atoi(argv[i]);
+ if (map > 0 || (map == 0 && (strcmp(argv[i], "0") == 0)))
+ mapnum = map;
+ else
+ {
+ printf("invalid map id\n");
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+int finish(const char* message, int returnValue)
+{
+ printf("%s", message);
+ getchar();
+ return returnValue;
+}
+
+int main(int argc, char** argv)
+{
+ int threads = 3, mapnum = -1;
+ float maxAngle = 60.0f;
+ int tileX = -1, tileY = -1;
+ bool skipLiquid = false,
+ skipContinents = false,
+ skipJunkMaps = true,
+ skipBattlegrounds = false,
+ debugOutput = false,
+ silent = false,
+ bigBaseUnit = false;
+ char* offMeshInputPath = NULL;
+ char* file = NULL;
+
+ bool validParam = handleArgs(argc, argv, mapnum,
+ tileX, tileY, maxAngle,
+ skipLiquid, skipContinents, skipJunkMaps, skipBattlegrounds,
+ debugOutput, silent, bigBaseUnit, offMeshInputPath, file, threads);
+
+ if (!validParam)
+ return silent ? -1 : finish("You have specified invalid parameters", -1);
+
+ if (mapnum == -1 && debugOutput)
+ {
+ if (silent)
+ return -2;
+
+ printf("You have specifed debug output, but didn't specify a map to generate.\n");
+ printf("This will generate debug output for ALL maps.\n");
+ printf("Are you sure you want to continue? (y/n) ");
+ if (getchar() != 'y')
+ return 0;
+ }
+
+ if (!checkDirectories(debugOutput))
+ return silent ? -3 : finish("Press any key to close...", -3);
+
+ MapBuilder builder(maxAngle, skipLiquid, skipContinents, skipJunkMaps,
+ skipBattlegrounds, debugOutput, bigBaseUnit, offMeshInputPath);
+
+ uint32 start = getMSTime();
+ if (file)
+ builder.buildMeshFromFile(file);
+ else if (tileX > -1 && tileY > -1 && mapnum >= 0)
+ builder.buildSingleTile(mapnum, tileX, tileY);
+ else if (mapnum >= 0)
+ builder.buildMap(uint32(mapnum));
+ else
+ builder.buildAllMaps(threads);
+
+ if (!silent)
+ printf("Finished. MMAPS were built in %u ms!\n", GetMSTimeDiffToNow(start));
+ return 1;
+}
diff --git a/src/tools/mmaps_generator/TerrainBuilder.cpp b/src/tools/mmaps_generator/TerrainBuilder.cpp
new file mode 100644
index 00000000000..f7cf76e9718
--- /dev/null
+++ b/src/tools/mmaps_generator/TerrainBuilder.cpp
@@ -0,0 +1,880 @@
+/*
+ * Copyright (C) 2008-2013 TrinityCore <http://www.trinitycore.org/>
+ * Copyright (C) 2005-2011 MaNGOS <http://getmangos.com/>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "TerrainBuilder.h"
+
+#include "PathCommon.h"
+#include "MapBuilder.h"
+
+#include "VMapManager2.h"
+#include "MapTree.h"
+#include "ModelInstance.h"
+
+namespace MMAP
+{
+
+ char const* MAP_VERSION_MAGIC = "v1.3";
+
+ TerrainBuilder::TerrainBuilder(bool skipLiquid) : m_skipLiquid (skipLiquid){ }
+ TerrainBuilder::~TerrainBuilder() { }
+
+ /**************************************************************************/
+ void TerrainBuilder::getLoopVars(Spot portion, int &loopStart, int &loopEnd, int &loopInc)
+ {
+ switch (portion)
+ {
+ case ENTIRE:
+ loopStart = 0;
+ loopEnd = V8_SIZE_SQ;
+ loopInc = 1;
+ break;
+ case TOP:
+ loopStart = 0;
+ loopEnd = V8_SIZE;
+ loopInc = 1;
+ break;
+ case LEFT:
+ loopStart = 0;
+ loopEnd = V8_SIZE_SQ - V8_SIZE + 1;
+ loopInc = V8_SIZE;
+ break;
+ case RIGHT:
+ loopStart = V8_SIZE - 1;
+ loopEnd = V8_SIZE_SQ;
+ loopInc = V8_SIZE;
+ break;
+ case BOTTOM:
+ loopStart = V8_SIZE_SQ - V8_SIZE;
+ loopEnd = V8_SIZE_SQ;
+ loopInc = 1;
+ break;
+ }
+ }
+
+ /**************************************************************************/
+ void TerrainBuilder::loadMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData)
+ {
+ if (loadMap(mapID, tileX, tileY, meshData, ENTIRE))
+ {
+ loadMap(mapID, tileX+1, tileY, meshData, LEFT);
+ loadMap(mapID, tileX-1, tileY, meshData, RIGHT);
+ loadMap(mapID, tileX, tileY+1, meshData, TOP);
+ loadMap(mapID, tileX, tileY-1, meshData, BOTTOM);
+ }
+ }
+
+ /**************************************************************************/
+ bool TerrainBuilder::loadMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData, Spot portion)
+ {
+ char mapFileName[255];
+ sprintf(mapFileName, "maps/%03u%02u%02u.map", mapID, tileY, tileX);
+
+ FILE* mapFile = fopen(mapFileName, "rb");
+ if (!mapFile)
+ return false;
+
+ map_fileheader fheader;
+ if (fread(&fheader, sizeof(map_fileheader), 1, mapFile) != 1 ||
+ fheader.versionMagic != *((uint32 const*)(MAP_VERSION_MAGIC)))
+ {
+ fclose(mapFile);
+ printf("%s is the wrong version, please extract new .map files\n", mapFileName);
+ return false;
+ }
+
+ map_heightHeader hheader;
+ fseek(mapFile, fheader.heightMapOffset, SEEK_SET);
+
+ bool haveTerrain = false;
+ bool haveLiquid = false;
+ if (fread(&hheader, sizeof(map_heightHeader), 1, mapFile) == 1)
+ {
+ haveTerrain = !(hheader.flags & MAP_HEIGHT_NO_HEIGHT);
+ haveLiquid = fheader.liquidMapOffset && !m_skipLiquid;
+ }
+
+ // no data in this map file
+ if (!haveTerrain && !haveLiquid)
+ {
+ fclose(mapFile);
+ return false;
+ }
+
+ // data used later
+ uint16 holes[16][16];
+ memset(holes, 0, sizeof(holes));
+ uint8 liquid_type[16][16];
+ memset(liquid_type, 0, sizeof(liquid_type));
+ G3D::Array<int> ltriangles;
+ G3D::Array<int> ttriangles;
+
+ // terrain data
+ if (haveTerrain)
+ {
+ float heightMultiplier;
+ float V9[V9_SIZE_SQ], V8[V8_SIZE_SQ];
+ int expected = V9_SIZE_SQ + V8_SIZE_SQ;
+
+ if (hheader.flags & MAP_HEIGHT_AS_INT8)
+ {
+ uint8 v9[V9_SIZE_SQ];
+ uint8 v8[V8_SIZE_SQ];
+ int count = 0;
+ count += fread(v9, sizeof(uint8), V9_SIZE_SQ, mapFile);
+ count += fread(v8, sizeof(uint8), V8_SIZE_SQ, mapFile);
+ if (count != expected)
+ printf("TerrainBuilder::loadMap: Failed to read some data expected %d, read %d\n", expected, count);
+
+ heightMultiplier = (hheader.gridMaxHeight - hheader.gridHeight) / 255;
+
+ for (int i = 0; i < V9_SIZE_SQ; ++i)
+ V9[i] = (float)v9[i]*heightMultiplier + hheader.gridHeight;
+
+ for (int i = 0; i < V8_SIZE_SQ; ++i)
+ V8[i] = (float)v8[i]*heightMultiplier + hheader.gridHeight;
+ }
+ else if (hheader.flags & MAP_HEIGHT_AS_INT16)
+ {
+ uint16 v9[V9_SIZE_SQ];
+ uint16 v8[V8_SIZE_SQ];
+ int count = 0;
+ count += fread(v9, sizeof(uint16), V9_SIZE_SQ, mapFile);
+ count += fread(v8, sizeof(uint16), V8_SIZE_SQ, mapFile);
+ if (count != expected)
+ printf("TerrainBuilder::loadMap: Failed to read some data expected %d, read %d\n", expected, count);
+
+ heightMultiplier = (hheader.gridMaxHeight - hheader.gridHeight) / 65535;
+
+ for (int i = 0; i < V9_SIZE_SQ; ++i)
+ V9[i] = (float)v9[i]*heightMultiplier + hheader.gridHeight;
+
+ for (int i = 0; i < V8_SIZE_SQ; ++i)
+ V8[i] = (float)v8[i]*heightMultiplier + hheader.gridHeight;
+ }
+ else
+ {
+ int count = 0;
+ count += fread(V9, sizeof(float), V9_SIZE_SQ, mapFile);
+ count += fread(V8, sizeof(float), V8_SIZE_SQ, mapFile);
+ if (count != expected)
+ printf("TerrainBuilder::loadMap: Failed to read some data expected %d, read %d\n", expected, count);
+ }
+
+ // hole data
+ if (fheader.holesSize != 0)
+ {
+ memset(holes, 0, fheader.holesSize);
+ fseek(mapFile, fheader.holesOffset, SEEK_SET);
+ if (fread(holes, fheader.holesSize, 1, mapFile) != 1)
+ printf("TerrainBuilder::loadMap: Failed to read some data expected 1, read 0\n");
+ }
+
+ int count = meshData.solidVerts.size() / 3;
+ float xoffset = (float(tileX)-32)*GRID_SIZE;
+ float yoffset = (float(tileY)-32)*GRID_SIZE;
+
+ float coord[3];
+
+ for (int i = 0; i < V9_SIZE_SQ; ++i)
+ {
+ getHeightCoord(i, GRID_V9, xoffset, yoffset, coord, V9);
+ meshData.solidVerts.append(coord[0]);
+ meshData.solidVerts.append(coord[2]);
+ meshData.solidVerts.append(coord[1]);
+ }
+
+ for (int i = 0; i < V8_SIZE_SQ; ++i)
+ {
+ getHeightCoord(i, GRID_V8, xoffset, yoffset, coord, V8);
+ meshData.solidVerts.append(coord[0]);
+ meshData.solidVerts.append(coord[2]);
+ meshData.solidVerts.append(coord[1]);
+ }
+
+ int indices[3], loopStart = 0, loopEnd = 0, loopInc = 0;
+ getLoopVars(portion, loopStart, loopEnd, loopInc);
+ for (int i = loopStart; i < loopEnd; i+=loopInc)
+ for (int j = TOP; j <= BOTTOM; j+=1)
+ {
+ getHeightTriangle(i, Spot(j), indices);
+ ttriangles.append(indices[2] + count);
+ ttriangles.append(indices[1] + count);
+ ttriangles.append(indices[0] + count);
+ }
+ }
+
+ // liquid data
+ if (haveLiquid)
+ {
+ map_liquidHeader lheader;
+ fseek(mapFile, fheader.liquidMapOffset, SEEK_SET);
+ if (fread(&lheader, sizeof(map_liquidHeader), 1, mapFile) != 1)
+ printf("TerrainBuilder::loadMap: Failed to read some data expected 1, read 0\n");
+
+
+ float* liquid_map = NULL;
+
+ if (!(lheader.flags & MAP_LIQUID_NO_TYPE))
+ if (fread(liquid_type, sizeof(liquid_type), 1, mapFile) != 1)
+ printf("TerrainBuilder::loadMap: Failed to read some data expected 1, read 0\n");
+
+ if (!(lheader.flags & MAP_LIQUID_NO_HEIGHT))
+ {
+ uint32 toRead = lheader.width * lheader.height;
+ liquid_map = new float [toRead];
+ if (fread(liquid_map, sizeof(float), toRead, mapFile) != toRead)
+ printf("TerrainBuilder::loadMap: Failed to read some data expected 1, read 0\n");
+ }
+
+ // FIXME: "the address of ‘liquid_type’ will always evaluate as ‘true’"
+ if (liquid_type && liquid_map)
+ {
+ int count = meshData.liquidVerts.size() / 3;
+ float xoffset = (float(tileX)-32)*GRID_SIZE;
+ float yoffset = (float(tileY)-32)*GRID_SIZE;
+
+ float coord[3];
+ int row, col;
+
+ // generate coordinates
+ if (!(lheader.flags & MAP_LIQUID_NO_HEIGHT))
+ {
+ int j = 0;
+ for (int i = 0; i < V9_SIZE_SQ; ++i)
+ {
+ row = i / V9_SIZE;
+ col = i % V9_SIZE;
+
+ if (row < lheader.offsetY || row >= lheader.offsetY + lheader.height ||
+ col < lheader.offsetX || col >= lheader.offsetX + lheader.width)
+ {
+ // dummy vert using invalid height
+ meshData.liquidVerts.append((xoffset+col*GRID_PART_SIZE)*-1, INVALID_MAP_LIQ_HEIGHT, (yoffset+row*GRID_PART_SIZE)*-1);
+ continue;
+ }
+
+ getLiquidCoord(i, j, xoffset, yoffset, coord, liquid_map);
+ meshData.liquidVerts.append(coord[0]);
+ meshData.liquidVerts.append(coord[2]);
+ meshData.liquidVerts.append(coord[1]);
+ j++;
+ }
+ }
+ else
+ {
+ for (int i = 0; i < V9_SIZE_SQ; ++i)
+ {
+ row = i / V9_SIZE;
+ col = i % V9_SIZE;
+ meshData.liquidVerts.append((xoffset+col*GRID_PART_SIZE)*-1, lheader.liquidLevel, (yoffset+row*GRID_PART_SIZE)*-1);
+ }
+ }
+
+ delete [] liquid_map;
+
+ int indices[3], loopStart = 0, loopEnd = 0, loopInc = 0, triInc = BOTTOM-TOP;
+ getLoopVars(portion, loopStart, loopEnd, loopInc);
+
+ // generate triangles
+ for (int i = loopStart; i < loopEnd; i+=loopInc)
+ for (int j = TOP; j <= BOTTOM; j+= triInc)
+ {
+ getHeightTriangle(i, Spot(j), indices, true);
+ ltriangles.append(indices[2] + count);
+ ltriangles.append(indices[1] + count);
+ ltriangles.append(indices[0] + count);
+ }
+ }
+ }
+
+ fclose(mapFile);
+
+ // now that we have gathered the data, we can figure out which parts to keep:
+ // liquid above ground, ground above liquid
+ int loopStart = 0, loopEnd = 0, loopInc = 0, tTriCount = 4;
+ bool useTerrain, useLiquid;
+
+ float* lverts = meshData.liquidVerts.getCArray();
+ int* ltris = ltriangles.getCArray();
+
+ float* tverts = meshData.solidVerts.getCArray();
+ int* ttris = ttriangles.getCArray();
+
+ if (ltriangles.size() + ttriangles.size() == 0)
+ return false;
+
+ // make a copy of liquid vertices
+ // used to pad right-bottom frame due to lost vertex data at extraction
+ float* lverts_copy = NULL;
+ if (meshData.liquidVerts.size())
+ {
+ lverts_copy = new float[meshData.liquidVerts.size()];
+ memcpy(lverts_copy, lverts, sizeof(float)*meshData.liquidVerts.size());
+ }
+
+ getLoopVars(portion, loopStart, loopEnd, loopInc);
+ for (int i = loopStart; i < loopEnd; i+=loopInc)
+ {
+ for (int j = 0; j < 2; ++j)
+ {
+ // default is true, will change to false if needed
+ useTerrain = true;
+ useLiquid = true;
+ uint8 liquidType = MAP_LIQUID_TYPE_NO_WATER;
+ // FIXME: "warning: the address of ‘liquid_type’ will always evaluate as ‘true’"
+
+ // if there is no liquid, don't use liquid
+ if (!liquid_type || !meshData.liquidVerts.size() || !ltriangles.size())
+ useLiquid = false;
+ else
+ {
+ liquidType = getLiquidType(i, liquid_type);
+ switch (liquidType)
+ {
+ default:
+ useLiquid = false;
+ break;
+ case MAP_LIQUID_TYPE_WATER:
+ case MAP_LIQUID_TYPE_OCEAN:
+ // merge different types of water
+ liquidType = NAV_WATER;
+ break;
+ case MAP_LIQUID_TYPE_MAGMA:
+ liquidType = NAV_MAGMA;
+ break;
+ case MAP_LIQUID_TYPE_SLIME:
+ liquidType = NAV_SLIME;
+ break;
+ case MAP_LIQUID_TYPE_DARK_WATER:
+ // players should not be here, so logically neither should creatures
+ useTerrain = false;
+ useLiquid = false;
+ break;
+ }
+ }
+
+ // if there is no terrain, don't use terrain
+ if (!ttriangles.size())
+ useTerrain = false;
+
+ // while extracting ADT data we are losing right-bottom vertices
+ // this code adds fair approximation of lost data
+ if (useLiquid)
+ {
+ float quadHeight = 0;
+ uint32 validCount = 0;
+ for(uint32 idx = 0; idx < 3; idx++)
+ {
+ float h = lverts_copy[ltris[idx]*3 + 1];
+ if (h != INVALID_MAP_LIQ_HEIGHT && h < INVALID_MAP_LIQ_HEIGHT_MAX)
+ {
+ quadHeight += h;
+ validCount++;
+ }
+ }
+
+ // update vertex height data
+ if (validCount > 0 && validCount < 3)
+ {
+ quadHeight /= validCount;
+ for(uint32 idx = 0; idx < 3; idx++)
+ {
+ float h = lverts[ltris[idx]*3 + 1];
+ if (h == INVALID_MAP_LIQ_HEIGHT || h > INVALID_MAP_LIQ_HEIGHT_MAX)
+ lverts[ltris[idx]*3 + 1] = quadHeight;
+ }
+ }
+
+ // no valid vertexes - don't use this poly at all
+ if (validCount == 0)
+ useLiquid = false;
+ }
+
+ // if there is a hole here, don't use the terrain
+ if (useTerrain && fheader.holesSize != 0)
+ useTerrain = !isHole(i, holes);
+
+ // we use only one terrain kind per quad - pick higher one
+ if (useTerrain && useLiquid)
+ {
+ float minLLevel = INVALID_MAP_LIQ_HEIGHT_MAX;
+ float maxLLevel = INVALID_MAP_LIQ_HEIGHT;
+ for(uint32 x = 0; x < 3; x++)
+ {
+ float h = lverts[ltris[x]*3 + 1];
+ if (minLLevel > h)
+ minLLevel = h;
+
+ if (maxLLevel < h)
+ maxLLevel = h;
+ }
+
+ float maxTLevel = INVALID_MAP_LIQ_HEIGHT;
+ float minTLevel = INVALID_MAP_LIQ_HEIGHT_MAX;
+ for(uint32 x = 0; x < 6; x++)
+ {
+ float h = tverts[ttris[x]*3 + 1];
+ if (maxTLevel < h)
+ maxTLevel = h;
+
+ if (minTLevel > h)
+ minTLevel = h;
+ }
+
+ // terrain under the liquid?
+ if (minLLevel > maxTLevel)
+ useTerrain = false;
+
+ //liquid under the terrain?
+ if (minTLevel > maxLLevel)
+ useLiquid = false;
+ }
+
+ // store the result
+ if (useLiquid)
+ {
+ meshData.liquidType.append(liquidType);
+ for (int k = 0; k < 3; ++k)
+ meshData.liquidTris.append(ltris[k]);
+ }
+
+ if (useTerrain)
+ for (int k = 0; k < 3*tTriCount/2; ++k)
+ meshData.solidTris.append(ttris[k]);
+
+ // advance to next set of triangles
+ ltris += 3;
+ ttris += 3*tTriCount/2;
+ }
+ }
+
+ if (lverts_copy)
+ delete [] lverts_copy;
+
+ return meshData.solidTris.size() || meshData.liquidTris.size();
+ }
+
+ /**************************************************************************/
+ void TerrainBuilder::getHeightCoord(int index, Grid grid, float xOffset, float yOffset, float* coord, float* v)
+ {
+ // wow coords: x, y, height
+ // coord is mirroed about the horizontal axes
+ switch (grid)
+ {
+ case GRID_V9:
+ coord[0] = (xOffset + index%(V9_SIZE)*GRID_PART_SIZE) * -1.f;
+ coord[1] = (yOffset + (int)(index/(V9_SIZE))*GRID_PART_SIZE) * -1.f;
+ coord[2] = v[index];
+ break;
+ case GRID_V8:
+ coord[0] = (xOffset + index%(V8_SIZE)*GRID_PART_SIZE + GRID_PART_SIZE/2.f) * -1.f;
+ coord[1] = (yOffset + (int)(index/(V8_SIZE))*GRID_PART_SIZE + GRID_PART_SIZE/2.f) * -1.f;
+ coord[2] = v[index];
+ break;
+ }
+ }
+
+ /**************************************************************************/
+ void TerrainBuilder::getHeightTriangle(int square, Spot triangle, int* indices, bool liquid/* = false*/)
+ {
+ int rowOffset = square/V8_SIZE;
+ if (!liquid)
+ switch (triangle)
+ {
+ case TOP:
+ indices[0] = square+rowOffset; // 0-----1 .... 128
+ indices[1] = square+1+rowOffset; // |\ T /|
+ indices[2] = (V9_SIZE_SQ)+square; // | \ / |
+ break; // |L 0 R| .. 127
+ case LEFT: // | / \ |
+ indices[0] = square+rowOffset; // |/ B \|
+ indices[1] = (V9_SIZE_SQ)+square; // 129---130 ... 386
+ indices[2] = square+V9_SIZE+rowOffset; // |\ /|
+ break; // | \ / |
+ case RIGHT: // | 128 | .. 255
+ indices[0] = square+1+rowOffset; // | / \ |
+ indices[1] = square+V9_SIZE+1+rowOffset; // |/ \|
+ indices[2] = (V9_SIZE_SQ)+square; // 258---259 ... 515
+ break;
+ case BOTTOM:
+ indices[0] = (V9_SIZE_SQ)+square;
+ indices[1] = square+V9_SIZE+1+rowOffset;
+ indices[2] = square+V9_SIZE+rowOffset;
+ break;
+ default: break;
+ }
+ else
+ switch (triangle)
+ { // 0-----1 .... 128
+ case TOP: // |\ |
+ indices[0] = square+rowOffset; // | \ T |
+ indices[1] = square+1+rowOffset; // | \ |
+ indices[2] = square+V9_SIZE+1+rowOffset; // | B \ |
+ break; // | \|
+ case BOTTOM: // 129---130 ... 386
+ indices[0] = square+rowOffset; // |\ |
+ indices[1] = square+V9_SIZE+1+rowOffset; // | \ |
+ indices[2] = square+V9_SIZE+rowOffset; // | \ |
+ break; // | \ |
+ default: break; // | \|
+ } // 258---259 ... 515
+
+ }
+
+ /**************************************************************************/
+ void TerrainBuilder::getLiquidCoord(int index, int index2, float xOffset, float yOffset, float* coord, float* v)
+ {
+ // wow coords: x, y, height
+ // coord is mirroed about the horizontal axes
+ coord[0] = (xOffset + index%(V9_SIZE)*GRID_PART_SIZE) * -1.f;
+ coord[1] = (yOffset + (int)(index/(V9_SIZE))*GRID_PART_SIZE) * -1.f;
+ coord[2] = v[index2];
+ }
+
+ static uint16 holetab_h[4] = {0x1111, 0x2222, 0x4444, 0x8888};
+ static uint16 holetab_v[4] = {0x000F, 0x00F0, 0x0F00, 0xF000};
+
+ /**************************************************************************/
+ bool TerrainBuilder::isHole(int square, const uint16 holes[16][16])
+ {
+ int row = square / 128;
+ int col = square % 128;
+ int cellRow = row / 8; // 8 squares per cell
+ int cellCol = col / 8;
+ int holeRow = row % 8 / 2;
+ int holeCol = (square - (row * 128 + cellCol * 8)) / 2;
+
+ uint16 hole = holes[cellRow][cellCol];
+
+ return (hole & holetab_h[holeCol] & holetab_v[holeRow]) != 0;
+ }
+
+ /**************************************************************************/
+ uint8 TerrainBuilder::getLiquidType(int square, const uint8 liquid_type[16][16])
+ {
+ int row = square / 128;
+ int col = square % 128;
+ int cellRow = row / 8; // 8 squares per cell
+ int cellCol = col / 8;
+
+ return liquid_type[cellRow][cellCol];
+ }
+
+ /**************************************************************************/
+ bool TerrainBuilder::loadVMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData)
+ {
+ IVMapManager* vmapManager = new VMapManager2();
+ int result = vmapManager->loadMap("vmaps", mapID, tileX, tileY);
+ bool retval = false;
+
+ do
+ {
+ if (result == VMAP_LOAD_RESULT_ERROR)
+ break;
+
+ InstanceTreeMap instanceTrees;
+ ((VMapManager2*)vmapManager)->getInstanceMapTree(instanceTrees);
+
+ if (!instanceTrees[mapID])
+ break;
+
+ ModelInstance* models = NULL;
+ uint32 count = 0;
+ instanceTrees[mapID]->getModelInstances(models, count);
+
+ if (!models)
+ break;
+
+ for (uint32 i = 0; i < count; ++i)
+ {
+ ModelInstance instance = models[i];
+
+ // model instances exist in tree even though there are instances of that model in this tile
+ WorldModel* worldModel = instance.getWorldModel();
+ if (!worldModel)
+ continue;
+
+ // now we have a model to add to the meshdata
+ retval = true;
+
+ vector<GroupModel> groupModels;
+ worldModel->getGroupModels(groupModels);
+
+ // all M2s need to have triangle indices reversed
+ bool isM2 = instance.name.find(".m2") != instance.name.npos || instance.name.find(".M2") != instance.name.npos;
+
+ // transform data
+ float scale = instance.iScale;
+ G3D::Matrix3 rotation = G3D::Matrix3::fromEulerAnglesXYZ(G3D::pi()*instance.iRot.z/-180.f, G3D::pi()*instance.iRot.x/-180.f, G3D::pi()*instance.iRot.y/-180.f);
+ Vector3 position = instance.iPos;
+ position.x -= 32*GRID_SIZE;
+ position.y -= 32*GRID_SIZE;
+
+ for (vector<GroupModel>::iterator it = groupModels.begin(); it != groupModels.end(); ++it)
+ {
+ vector<Vector3> tempVertices;
+ vector<Vector3> transformedVertices;
+ vector<MeshTriangle> tempTriangles;
+ WmoLiquid* liquid = NULL;
+
+ (*it).getMeshData(tempVertices, tempTriangles, liquid);
+
+ // first handle collision mesh
+ transform(tempVertices, transformedVertices, scale, rotation, position);
+
+ int offset = meshData.solidVerts.size() / 3;
+
+ copyVertices(transformedVertices, meshData.solidVerts);
+ copyIndices(tempTriangles, meshData.solidTris, offset, isM2);
+
+ // now handle liquid data
+ if (liquid)
+ {
+ vector<Vector3> liqVerts;
+ vector<int> liqTris;
+ uint32 tilesX, tilesY, vertsX, vertsY;
+ Vector3 corner;
+ liquid->getPosInfo(tilesX, tilesY, corner);
+ vertsX = tilesX + 1;
+ vertsY = tilesY + 1;
+ uint8* flags = liquid->GetFlagsStorage();
+ float* data = liquid->GetHeightStorage();
+ uint8 type = NAV_EMPTY;
+
+ // convert liquid type to NavTerrain
+ switch (liquid->GetType())
+ {
+ case 0:
+ case 1:
+ type = NAV_WATER;
+ break;
+ case 2:
+ type = NAV_MAGMA;
+ break;
+ case 3:
+ type = NAV_SLIME;
+ break;
+ }
+
+ // indexing is weird...
+ // after a lot of trial and error, this is what works:
+ // vertex = y*vertsX+x
+ // tile = x*tilesY+y
+ // flag = y*tilesY+x
+
+ Vector3 vert;
+ for (uint32 x = 0; x < vertsX; ++x)
+ for (uint32 y = 0; y < vertsY; ++y)
+ {
+ vert = Vector3(corner.x + x * GRID_PART_SIZE, corner.y + y * GRID_PART_SIZE, data[y*vertsX + x]);
+ vert = vert * rotation * scale + position;
+ vert.x *= -1.f;
+ vert.y *= -1.f;
+ liqVerts.push_back(vert);
+ }
+
+ int idx1, idx2, idx3, idx4;
+ uint32 square;
+ for (uint32 x = 0; x < tilesX; ++x)
+ for (uint32 y = 0; y < tilesY; ++y)
+ if ((flags[x+y*tilesX] & 0x0f) != 0x0f)
+ {
+ square = x * tilesY + y;
+ idx1 = square+x;
+ idx2 = square+1+x;
+ idx3 = square+tilesY+1+1+x;
+ idx4 = square+tilesY+1+x;
+
+ // top triangle
+ liqTris.push_back(idx3);
+ liqTris.push_back(idx2);
+ liqTris.push_back(idx1);
+ // bottom triangle
+ liqTris.push_back(idx4);
+ liqTris.push_back(idx3);
+ liqTris.push_back(idx1);
+ }
+
+ uint32 liqOffset = meshData.liquidVerts.size() / 3;
+ for (uint32 i = 0; i < liqVerts.size(); ++i)
+ meshData.liquidVerts.append(liqVerts[i].y, liqVerts[i].z, liqVerts[i].x);
+
+ for (uint32 i = 0; i < liqTris.size() / 3; ++i)
+ {
+ meshData.liquidTris.append(liqTris[i*3+1] + liqOffset, liqTris[i*3+2] + liqOffset, liqTris[i*3] + liqOffset);
+ meshData.liquidType.append(type);
+ }
+ }
+ }
+ }
+ }
+ while (false);
+
+ vmapManager->unloadMap(mapID, tileX, tileY);
+ delete vmapManager;
+
+ return retval;
+ }
+
+ /**************************************************************************/
+ void TerrainBuilder::transform(vector<Vector3> &source, vector<Vector3> &transformedVertices, float scale, G3D::Matrix3 &rotation, Vector3 &position)
+ {
+ for (vector<Vector3>::iterator it = source.begin(); it != source.end(); ++it)
+ {
+ // apply tranform, then mirror along the horizontal axes
+ Vector3 v((*it) * rotation * scale + position);
+ v.x *= -1.f;
+ v.y *= -1.f;
+ transformedVertices.push_back(v);
+ }
+ }
+
+ /**************************************************************************/
+ void TerrainBuilder::copyVertices(vector<Vector3> &source, G3D::Array<float> &dest)
+ {
+ for (vector<Vector3>::iterator it = source.begin(); it != source.end(); ++it)
+ {
+ dest.push_back((*it).y);
+ dest.push_back((*it).z);
+ dest.push_back((*it).x);
+ }
+ }
+
+ /**************************************************************************/
+ void TerrainBuilder::copyIndices(vector<MeshTriangle> &source, G3D::Array<int> &dest, int offset, bool flip)
+ {
+ if (flip)
+ {
+ for (vector<MeshTriangle>::iterator it = source.begin(); it != source.end(); ++it)
+ {
+ dest.push_back((*it).idx2+offset);
+ dest.push_back((*it).idx1+offset);
+ dest.push_back((*it).idx0+offset);
+ }
+ }
+ else
+ {
+ for (vector<MeshTriangle>::iterator it = source.begin(); it != source.end(); ++it)
+ {
+ dest.push_back((*it).idx0+offset);
+ dest.push_back((*it).idx1+offset);
+ dest.push_back((*it).idx2+offset);
+ }
+ }
+ }
+
+ /**************************************************************************/
+ void TerrainBuilder::copyIndices(G3D::Array<int> &source, G3D::Array<int> &dest, int offset)
+ {
+ int* src = source.getCArray();
+ for (int32 i = 0; i < source.size(); ++i)
+ dest.append(src[i] + offset);
+ }
+
+ /**************************************************************************/
+ void TerrainBuilder::cleanVertices(G3D::Array<float> &verts, G3D::Array<int> &tris)
+ {
+ map<int, int> vertMap;
+
+ int* t = tris.getCArray();
+ float* v = verts.getCArray();
+
+ G3D::Array<float> cleanVerts;
+ int index, count = 0;
+ // collect all the vertex indices from triangle
+ for (int i = 0; i < tris.size(); ++i)
+ {
+ if (vertMap.find(t[i]) != vertMap.end())
+ continue;
+ std::pair<int, int> val;
+ val.first = t[i];
+
+ index = val.first;
+ val.second = count;
+
+ vertMap.insert(val);
+ cleanVerts.append(v[index * 3], v[index * 3 + 1], v[index * 3 + 2]);
+ count++;
+ }
+
+ verts.fastClear();
+ verts.append(cleanVerts);
+ cleanVerts.clear();
+
+ // update triangles to use new indices
+ for (int i = 0; i < tris.size(); ++i)
+ {
+ map<int, int>::iterator it;
+ if ((it = vertMap.find(t[i])) == vertMap.end())
+ continue;
+
+ t[i] = (*it).second;
+ }
+
+ vertMap.clear();
+ }
+
+ /**************************************************************************/
+ void TerrainBuilder::loadOffMeshConnections(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData, const char* offMeshFilePath)
+ {
+ // no meshfile input given?
+ if (offMeshFilePath == NULL)
+ return;
+
+ FILE* fp = fopen(offMeshFilePath, "rb");
+ if (!fp)
+ {
+ printf(" loadOffMeshConnections:: input file %s not found!\n", offMeshFilePath);
+ return;
+ }
+
+ // pretty silly thing, as we parse entire file and load only the tile we need
+ // but we don't expect this file to be too large
+ char* buf = new char[512];
+ while(fgets(buf, 512, fp))
+ {
+ float p0[3], p1[3];
+ uint32 mid, tx, ty;
+ float size;
+ if (sscanf(buf, "%d %d,%d (%f %f %f) (%f %f %f) %f", &mid, &tx, &ty,
+ &p0[0], &p0[1], &p0[2], &p1[0], &p1[1], &p1[2], &size) != 10)
+ continue;
+
+ if (mapID == mid && tileX == tx && tileY == ty)
+ {
+ meshData.offMeshConnections.append(p0[1]);
+ meshData.offMeshConnections.append(p0[2]);
+ meshData.offMeshConnections.append(p0[0]);
+
+ meshData.offMeshConnections.append(p1[1]);
+ meshData.offMeshConnections.append(p1[2]);
+ meshData.offMeshConnections.append(p1[0]);
+
+ meshData.offMeshConnectionDirs.append(1); // 1 - both direction, 0 - one sided
+ meshData.offMeshConnectionRads.append(size); // agent size equivalent
+ // can be used same way as polygon flags
+ meshData.offMeshConnectionsAreas.append((unsigned char)0xFF);
+ meshData.offMeshConnectionsFlags.append((unsigned short)0xFF); // all movement masks can make this path
+ }
+
+ }
+
+ delete [] buf;
+ fclose(fp);
+ }
+}
diff --git a/src/tools/mmaps_generator/TerrainBuilder.h b/src/tools/mmaps_generator/TerrainBuilder.h
new file mode 100644
index 00000000000..6ad4284f416
--- /dev/null
+++ b/src/tools/mmaps_generator/TerrainBuilder.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2008-2013 TrinityCore <http://www.trinitycore.org/>
+ * Copyright (C) 2005-2011 MaNGOS <http://getmangos.com/>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _MMAP_TERRAIN_BUILDER_H
+#define _MMAP_TERRAIN_BUILDER_H
+
+#include "PathCommon.h"
+#include "Map.h"
+#include "SharedDefines.h"
+
+#include "WorldModel.h"
+
+#include "G3D/Array.h"
+#include "G3D/Vector3.h"
+#include "G3D/Matrix3.h"
+
+using namespace Trinity;
+
+namespace MMAP
+{
+ enum Spot
+ {
+ TOP = 1,
+ RIGHT = 2,
+ LEFT = 3,
+ BOTTOM = 4,
+ ENTIRE = 5
+ };
+
+ enum Grid
+ {
+ GRID_V8,
+ GRID_V9
+ };
+
+ static const int V9_SIZE = 129;
+ static const int V9_SIZE_SQ = V9_SIZE*V9_SIZE;
+ static const int V8_SIZE = 128;
+ static const int V8_SIZE_SQ = V8_SIZE*V8_SIZE;
+ static const float GRID_SIZE = 533.33333f;
+ static const float GRID_PART_SIZE = GRID_SIZE/V8_SIZE;
+
+ // see contrib/extractor/system.cpp, CONF_use_minHeight
+ static const float INVALID_MAP_LIQ_HEIGHT = -500.f;
+ static const float INVALID_MAP_LIQ_HEIGHT_MAX = 5000.0f;
+
+ // see following files:
+ // contrib/extractor/system.cpp
+ // src/game/Map.cpp
+
+ struct MeshData
+ {
+ G3D::Array<float> solidVerts;
+ G3D::Array<int> solidTris;
+
+ G3D::Array<float> liquidVerts;
+ G3D::Array<int> liquidTris;
+ G3D::Array<uint8> liquidType;
+
+ // offmesh connection data
+ G3D::Array<float> offMeshConnections; // [p0y,p0z,p0x,p1y,p1z,p1x] - per connection
+ G3D::Array<float> offMeshConnectionRads;
+ G3D::Array<unsigned char> offMeshConnectionDirs;
+ G3D::Array<unsigned char> offMeshConnectionsAreas;
+ G3D::Array<unsigned short> offMeshConnectionsFlags;
+ };
+
+ class TerrainBuilder
+ {
+ public:
+ TerrainBuilder(bool skipLiquid);
+ ~TerrainBuilder();
+
+ void loadMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData);
+ bool loadVMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData);
+ void loadOffMeshConnections(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData, const char* offMeshFilePath);
+
+ bool usesLiquids() { return !m_skipLiquid; }
+
+ // vert and triangle methods
+ static void transform(vector<G3D::Vector3> &original, vector<G3D::Vector3> &transformed,
+ float scale, G3D::Matrix3 &rotation, G3D::Vector3 &position);
+ static void copyVertices(vector<G3D::Vector3> &source, G3D::Array<float> &dest);
+ static void copyIndices(vector<VMAP::MeshTriangle> &source, G3D::Array<int> &dest, int offest, bool flip);
+ static void copyIndices(G3D::Array<int> &src, G3D::Array<int> &dest, int offset);
+ static void cleanVertices(G3D::Array<float> &verts, G3D::Array<int> &tris);
+ private:
+ /// Loads a portion of a map's terrain
+ bool loadMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData, Spot portion);
+
+ /// Sets loop variables for selecting only certain parts of a map's terrain
+ void getLoopVars(Spot portion, int &loopStart, int &loopEnd, int &loopInc);
+
+ /// Controls whether liquids are loaded
+ bool m_skipLiquid;
+
+ /// Load the map terrain from file
+ bool loadHeightMap(uint32 mapID, uint32 tileX, uint32 tileY, G3D::Array<float> &vertices, G3D::Array<int> &triangles, Spot portion);
+
+ /// Get the vector coordinate for a specific position
+ void getHeightCoord(int index, Grid grid, float xOffset, float yOffset, float* coord, float* v);
+
+ /// Get the triangle's vector indices for a specific position
+ void getHeightTriangle(int square, Spot triangle, int* indices, bool liquid = false);
+
+ /// Determines if the specific position's triangles should be rendered
+ bool isHole(int square, const uint16 holes[16][16]);
+
+ /// Get the liquid vector coordinate for a specific position
+ void getLiquidCoord(int index, int index2, float xOffset, float yOffset, float* coord, float* v);
+
+ /// Get the liquid type for a specific position
+ uint8 getLiquidType(int square, const uint8 liquid_type[16][16]);
+
+ // hide parameterless and copy constructor
+ TerrainBuilder();
+ TerrainBuilder(const TerrainBuilder &tb);
+ };
+}
+
+#endif
+
diff --git a/src/tools/mmaps_generator/VMapExtensions.cpp b/src/tools/mmaps_generator/VMapExtensions.cpp
new file mode 100644
index 00000000000..37b7d94af55
--- /dev/null
+++ b/src/tools/mmaps_generator/VMapExtensions.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2008-2013 TrinityCore <http://www.trinitycore.org/>
+ * Copyright (C) 2005-2011 MaNGOS <http://getmangos.com/>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <vector>
+#include "MapTree.h"
+#include "VMapManager2.h"
+#include "WorldModel.h"
+#include "ModelInstance.h"
+
+using namespace std;
+
+namespace VMAP
+{
+ // Need direct access to encapsulated VMAP data, so we add functions for MMAP generator
+ // maybe add MapBuilder as friend to all of the below classes would be better?
+
+ // declared in src/shared/vmap/MapTree.h
+ void StaticMapTree::getModelInstances(ModelInstance* &models, uint32 &count)
+ {
+ models = iTreeValues;
+ count = iNTreeValues;
+ }
+
+ // declared in src/shared/vmap/VMapManager2.h
+ void VMapManager2::getInstanceMapTree(InstanceTreeMap &instanceMapTree)
+ {
+ instanceMapTree = iInstanceMapTrees;
+ }
+
+ // declared in src/shared/vmap/WorldModel.h
+ void WorldModel::getGroupModels(vector<GroupModel> &groupModels)
+ {
+ groupModels = this->groupModels;
+ }
+
+ // declared in src/shared/vmap/WorldModel.h
+ void GroupModel::getMeshData(vector<Vector3> &vertices, vector<MeshTriangle> &triangles, WmoLiquid* &liquid)
+ {
+ vertices = this->vertices;
+ triangles = this->triangles;
+ liquid = iLiquid;
+ }
+
+ // declared in src/shared/vmap/ModelInstance.h
+ WorldModel* ModelInstance::getWorldModel()
+ {
+ return iModel;
+ }
+
+ // declared in src/shared/vmap/WorldModel.h
+ void WmoLiquid::getPosInfo(uint32 &tilesX, uint32 &tilesY, Vector3 &corner) const
+ {
+ tilesX = iTilesX;
+ tilesY = iTilesY;
+ corner = iCorner;
+ }
+}
diff --git a/src/tools/vmap4_extractor/adtfile.cpp b/src/tools/vmap4_extractor/adtfile.cpp
index a605118eead..56e62d474d1 100644
--- a/src/tools/vmap4_extractor/adtfile.cpp
+++ b/src/tools/vmap4_extractor/adtfile.cpp
@@ -69,8 +69,7 @@ void fixname2(char* name, size_t len)
char* GetExtension(char* FileName)
{
- char* szTemp;
- if (szTemp = strrchr(FileName, '.'))
+ if (char* szTemp = strrchr(FileName, '.'))
return szTemp;
return NULL;
}
diff --git a/src/tools/vmap4_extractor/model.cpp b/src/tools/vmap4_extractor/model.cpp
index c642f73ccae..b950db023fe 100644
--- a/src/tools/vmap4_extractor/model.cpp
+++ b/src/tools/vmap4_extractor/model.cpp
@@ -152,10 +152,10 @@ ModelInstance::ModelInstance(MPQFile& f, char const* ModelInstName, uint32 mapID
fseek(input, 8, SEEK_SET); // get the correct no of vertices
int nVertices;
- fread(&nVertices, sizeof (int), 1, input);
+ int count = fread(&nVertices, sizeof (int), 1, input);
fclose(input);
- if(nVertices == 0)
+ if (count != 1 || nVertices == 0)
return;
uint16 adtId = 0;// not used for models
diff --git a/src/tools/vmap4_extractor/vmapexport.cpp b/src/tools/vmap4_extractor/vmapexport.cpp
index 186f9c8606e..89e4b850dac 100644
--- a/src/tools/vmap4_extractor/vmapexport.cpp
+++ b/src/tools/vmap4_extractor/vmapexport.cpp
@@ -461,8 +461,7 @@ int main(int argc, char ** argv)
printf("Your output directory seems to be polluted, please use an empty directory!\n");
printf("<press return to exit>");
char garbage[2];
- scanf("%c", garbage);
- return 1;
+ return scanf("%c", garbage);
}
}
diff --git a/src/tools/vmap4_extractor/wmo.cpp b/src/tools/vmap4_extractor/wmo.cpp
index 0bc749b9bd5..7c9f23c5a95 100644
--- a/src/tools/vmap4_extractor/wmo.cpp
+++ b/src/tools/vmap4_extractor/wmo.cpp
@@ -521,10 +521,10 @@ WMOInstance::WMOInstance(MPQFile& f, char const* WmoInstName, uint32 mapID, uint
fseek(input, 8, SEEK_SET); // get the correct no of vertices
int nVertices;
- fread(&nVertices, sizeof (int), 1, input);
+ int count = fread(&nVertices, sizeof (int), 1, input);
fclose(input);
- if(nVertices == 0)
+ if (count != 1 || nVertices == 0)
return;
float x,z;