diff options
Diffstat (limited to 'src/tools')
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(¶ms, 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(¶ms, 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(¶ms); + 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(¶ms, 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(¶ms, &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(¶ms, 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(¶ms, &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; |
