aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorVenugh <venugh@gmx.net>2012-04-09 14:45:05 +0200
committerVenugh <venugh@gmx.net>2012-04-09 14:45:05 +0200
commit204b846fe695768ad56f8f8c832d6deaa6dc2e1c (patch)
tree48ec20e64638ad2db0c61ebad9c721ba84a9703c /src
parent37cf893ad9055f9e5b57e13e5adac0a01e5606ac (diff)
Core/Tools: Implemented mmaps_generator.
Diffstat (limited to 'src')
-rw-r--r--src/tools/CMakeLists.txt1
-rw-r--r--src/tools/mmap_extractor/CMakeLists.txt68
-rw-r--r--src/tools/mmap_extractor/Info/readme.txt66
-rw-r--r--src/tools/mmap_extractor/IntermediateValues.cpp277
-rw-r--r--src/tools/mmap_extractor/IntermediateValues.h53
-rw-r--r--src/tools/mmap_extractor/MapBuilder.cpp894
-rw-r--r--src/tools/mmap_extractor/MapBuilder.h129
-rw-r--r--src/tools/mmap_extractor/PathCommon.h128
-rw-r--r--src/tools/mmap_extractor/PathGenerator.cpp273
-rw-r--r--src/tools/mmap_extractor/TerrainBuilder.cpp854
-rw-r--r--src/tools/mmap_extractor/TerrainBuilder.h138
-rw-r--r--src/tools/mmap_extractor/VMapExtensions.cpp72
12 files changed, 2953 insertions, 0 deletions
diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt
index a956655ffaa..f4c5f695b26 100644
--- a/src/tools/CMakeLists.txt
+++ b/src/tools/CMakeLists.txt
@@ -11,3 +11,4 @@
add_subdirectory(map_extractor)
add_subdirectory(vmap4_assembler)
add_subdirectory(vmap4_extractor)
+add_subdirectory(mmaps_extractor)
diff --git a/src/tools/mmap_extractor/CMakeLists.txt b/src/tools/mmap_extractor/CMakeLists.txt
new file mode 100644
index 00000000000..c7c1a954371
--- /dev/null
+++ b/src/tools/mmap_extractor/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/acelite
+ ${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/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(movements_extractor ${sources})
+
+target_link_libraries(movements_extractor
+ ${MYSQL_LIBRARY}
+ ${ACE_LIBRARY}
+ ${BZIP2_LIBRARIES}
+ ${ZLIB_LIBRARIES}
+ Recast
+ Detour
+ collision
+ g3dlib
+ shared
+)
+
+if( UNIX )
+ install(TARGETS movements_extractor DESTINATION bin)
+elseif( WIN32 )
+ install(TARGETS movements_extractor DESTINATION "${CMAKE_INSTALL_PREFIX}")
+endif()
diff --git a/src/tools/mmap_extractor/Info/readme.txt b/src/tools/mmap_extractor/Info/readme.txt
new file mode 100644
index 00000000000..8d7c4f9d2e0
--- /dev/null
+++ b/src/tools/mmap_extractor/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/mmap_extractor/IntermediateValues.cpp b/src/tools/mmap_extractor/IntermediateValues.cpp
new file mode 100644
index 00000000000..9eefb1e65f0
--- /dev/null
+++ b/src/tools/mmap_extractor/IntermediateValues.cpp
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2008-2011 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.obj", mapID);
+
+ 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/mmap_extractor/IntermediateValues.h b/src/tools/mmap_extractor/IntermediateValues.h
new file mode 100644
index 00000000000..a267a0f6412
--- /dev/null
+++ b/src/tools/mmap_extractor/IntermediateValues.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2008-2011 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() : compactHeightfield(NULL), heightfield(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/mmap_extractor/MapBuilder.cpp b/src/tools/mmap_extractor/MapBuilder.cpp
new file mode 100644
index 00000000000..3ca158f0127
--- /dev/null
+++ b/src/tools/mmap_extractor/MapBuilder.cpp
@@ -0,0 +1,894 @@
+/*
+ * Copyright (C) 2008-2011 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 "DetourCommon.h"
+
+// These make the linker happy.
+LoginDatabaseWorkerPool LoginDatabase;
+#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_skipContinents (skipContinents),
+ m_skipJunkMaps (skipJunkMaps),
+ m_skipBattlegrounds (skipBattlegrounds),
+ m_maxWalkableAngle (maxWalkableAngle),
+ m_bigBaseUnit (bigBaseUnit),
+ m_rcContext (NULL),
+ m_offMeshFilePath (offMeshFilePath)
+ {
+ 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()
+ {
+ for (TileList::iterator it = m_tiles.begin(); it != m_tiles.end(); ++it)
+ {
+ uint32 mapID = (*it).first;
+ if (!shouldSkipMap(mapID))
+ buildMap(mapID);
+ }
+ }
+
+ /**************************************************************************/
+ 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::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("Building map %03u:\n", 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->size())
+ return;
+
+ // build navMesh
+ dtNavMesh* navMesh = NULL;
+ buildNavMesh(mapID, navMesh);
+ if (!navMesh)
+ {
+ printf("Failed creating navmesh! \n");
+ return;
+ }
+
+ // now start building mmtiles for each tile
+ printf("We have %u tiles. \n", (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("Complete! \n\n");
+ }
+
+ /**************************************************************************/
+ void MapBuilder::buildTile(uint32 mapID, uint32 tileX, uint32 tileY, dtNavMesh* navMesh)
+ {
+ printf("Building map %03u, 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 tileBits = STATIC_TILE_BITS;
+ 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("Creating navMesh... \r");
+ if (!navMesh->init(&navMeshParams))
+ {
+ printf("Failed creating navmesh! \n");
+ return;
+ }
+
+ char fileName[25];
+ sprintf(fileName, "mmaps/%03u.mmap", mapID);
+
+ FILE* file = fopen(fileName, "wb");
+ if (!file)
+ {
+ dtFreeNavMesh(navMesh);
+ char message[1024];
+ sprintf(message, "Failed to open %s for writing!\n", 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[10];
+ sprintf(tileString, "[%02i,%02i]: ", tileX, tileY);
+ printf("%s Building movemap tiles... \r", 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;
+ memcpy(&tileCfg, &config, sizeof(rcConfig));
+ tileCfg.width = config.tileSize + config.borderSize*2;
+ tileCfg.height = config.tileSize + config.borderSize*2;
+
+ // 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] + (x*config.tileSize - config.borderSize)*config.cs;
+ tileCfg.bmin[2] = config.bmin[2] + (y*config.tileSize - config.borderSize)*config.cs;
+ tileCfg.bmax[0] = config.bmin[0] + ((x+1)*config.tileSize + config.borderSize)*config.cs;
+ tileCfg.bmax[2] = config.bmin[2] + ((y+1)*config.tileSize + config.borderSize)*config.cs;
+
+ float tbmin[2], tbmax[2];
+ tbmin[0] = tileCfg.bmin[0];
+ tbmin[1] = tileCfg.bmin[2];
+ tbmax[0] = tileCfg.bmax[0];
+ tbmax[1] = tileCfg.bmax[2];
+
+ // 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("%sFailed 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("%sFailed compacting heightfield! \n", tileString);
+ continue;
+ }
+
+ // build polymesh intermediates
+ if (!rcErodeWalkableArea(m_rcContext, config.walkableRadius, *tile.chf))
+ {
+ printf("%sFailed eroding area! \n", tileString);
+ continue;
+ }
+
+ if (!rcBuildDistanceField(m_rcContext, *tile.chf))
+ {
+ printf("%sFailed building distance field! \n", tileString);
+ continue;
+ }
+
+ if (!rcBuildRegions(m_rcContext, *tile.chf, tileCfg.borderSize, tileCfg.minRegionArea, tileCfg.mergeRegionArea))
+ {
+ printf("%sFailed building regions! \n", tileString);
+ continue;
+ }
+
+ tile.cset = rcAllocContourSet();
+ if (!tile.cset || !rcBuildContours(m_rcContext, *tile.chf, tileCfg.maxSimplificationError, tileCfg.maxEdgeLen, *tile.cset))
+ {
+ printf("%sFailed building contours! \n", tileString);
+ continue;
+ }
+
+ // build polymesh
+ tile.pmesh = rcAllocPolyMesh();
+ if (!tile.pmesh || !rcBuildPolyMesh(m_rcContext, *tile.cset, tileCfg.maxVertsPerPoly, *tile.pmesh))
+ {
+ printf("%sFailed 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("%sFailed 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;
+ }
+ }
+
+ // merge per tile poly and detail meshes
+ rcPolyMesh** pmmerge = new rcPolyMesh*[TILES_PER_MAP * TILES_PER_MAP];
+ if (!pmmerge)
+ {
+ printf("%s alloc pmmerge FIALED! \r", tileString);
+ return;
+ }
+
+ rcPolyMeshDetail** dmmerge = new rcPolyMeshDetail*[TILES_PER_MAP * TILES_PER_MAP];
+ if (!dmmerge)
+ {
+ printf("%s alloc dmmerge FIALED! \r", tileString);
+ return;
+ }
+
+ int nmerge = 0;
+ 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];
+ if (tile.pmesh)
+ {
+ pmmerge[nmerge] = tile.pmesh;
+ dmmerge[nmerge] = tile.dmesh;
+ nmerge++;
+ }
+ }
+ }
+
+ iv.polyMesh = rcAllocPolyMesh();
+ if (!iv.polyMesh)
+ {
+ printf("%s alloc iv.polyMesh FIALED! \r", tileString);
+ return;
+ }
+ rcMergePolyMeshes(m_rcContext, pmmerge, nmerge, *iv.polyMesh);
+
+ iv.polyMeshDetail = rcAllocPolyMeshDetail();
+ if (!iv.polyMeshDetail)
+ {
+ printf("%s alloc m_dmesh FIALED! \r", tileString);
+ return;
+ }
+ rcMergePolyMeshDetails(m_rcContext, dmmerge, nmerge, *iv.polyMeshDetail);
+
+ // free things up
+ delete [] pmmerge;
+ delete [] dmmerge;
+
+ delete [] tiles;
+
+ // remove padding for extraction
+ for (int i = 0; i < iv.polyMesh->nverts; ++i)
+ {
+ unsigned short* v = &iv.polyMesh->verts[i*3];
+ v[0] -= (unsigned short)config.borderSize;
+ v[2] -= (unsigned short)config.borderSize;
+ }
+
+ // set polygons as walkable
+ // TODO: special flags for DYNAMIC polygons, ie surfaces that can be turned on and off
+ for (int i = 0; i < iv.polyMesh->npolys; ++i)
+ if (iv.polyMesh->areas[i] & RC_WALKABLE_AREA)
+ iv.polyMesh->flags[i] = iv.polyMesh->areas[i];
+
+ // setup mesh parameters
+ dtNavMeshCreateParams params;
+ memset(&params, 0, sizeof(params));
+ params.verts = iv.polyMesh->verts;
+ params.vertCount = iv.polyMesh->nverts;
+ params.polys = iv.polyMesh->polys;
+ params.polyAreas = iv.polyMesh->areas;
+ params.polyFlags = iv.polyMesh->flags;
+ params.polyCount = iv.polyMesh->npolys;
+ params.nvp = iv.polyMesh->nvp;
+ params.detailMeshes = iv.polyMeshDetail->meshes;
+ params.detailVerts = iv.polyMeshDetail->verts;
+ params.detailVertsCount = iv.polyMeshDetail->nverts;
+ params.detailTris = iv.polyMeshDetail->tris;
+ params.detailTriCount = iv.polyMeshDetail->ntris;
+
+ params.offMeshConVerts = meshData.offMeshConnections.getCArray();
+ params.offMeshConCount = meshData.offMeshConnections.size()/6;
+ params.offMeshConRad = meshData.offMeshConnectionRads.getCArray();
+ params.offMeshConDir = meshData.offMeshConnectionDirs.getCArray();
+ params.offMeshConAreas = meshData.offMeshConnectionsAreas.getCArray();
+ params.offMeshConFlags = meshData.offMeshConnectionsFlags.getCArray();
+
+ params.walkableHeight = BASE_UNIT_DIM*config.walkableHeight; // agent height
+ params.walkableRadius = BASE_UNIT_DIM*config.walkableRadius; // agent radius
+ params.walkableClimb = BASE_UNIT_DIM*config.walkableClimb; // keep less that walkableHeight (aka agent height)!
+ params.tileX = (((bmin[0] + bmax[0]) / 2) - navMesh->getParams()->orig[0]) / GRID_SIZE;
+ params.tileY = (((bmin[2] + bmax[2]) / 2) - navMesh->getParams()->orig[2]) / GRID_SIZE;
+ rcVcopy(params.bmin, bmin);
+ rcVcopy(params.bmax, bmax);
+ params.cs = config.cs;
+ params.ch = config.ch;
+ params.tileSize = VERTEX_PER_MAP;
+
+ // will hold final navmesh
+ unsigned char* navData = NULL;
+ int navDataSize = 0;
+
+ do
+ {
+ // these values are checked within dtCreateNavMeshData - handle them here
+ // so we have a clear error message
+ if (params.nvp > DT_VERTS_PER_POLYGON)
+ {
+ printf("%s Invalid verts-per-polygon value! \n", tileString);
+ continue;
+ }
+ if (params.vertCount >= 0xffff)
+ {
+ printf("%s Too many vertices! \n", tileString);
+ continue;
+ }
+ if (!params.vertCount || !params.verts)
+ {
+ // occurs mostly when adjacent tiles have models
+ // loaded but those models don't span into this tile
+
+ // message is an annoyance
+ //printf("%sNo vertices to build tile! \n", tileString);
+ continue;
+ }
+ if (!params.polyCount || !params.polys ||
+ TILES_PER_MAP*TILES_PER_MAP == params.polyCount)
+ {
+ // we have flat tiles with no actual geometry - don't build those, its useless
+ // keep in mind that we do output those into debug info
+ // drop tiles with only exact count - some tiles may have geometry while having less tiles
+ printf("%s No polygons to build on tile! \n", tileString);
+ continue;
+ }
+ if (!params.detailMeshes || !params.detailVerts || !params.detailTris)
+ {
+ printf("%s No detail mesh to build tile! \n", tileString);
+ continue;
+ }
+
+ printf("%s Building navmesh tile... \r", tileString);
+ if (!dtCreateNavMeshData(&params, &navData, &navDataSize))
+ {
+ printf("%s Failed building navmesh tile! \n", tileString);
+ continue;
+ }
+
+ dtTileRef tileRef = 0;
+ printf("%s Adding tile to navmesh... \r", 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, "Failed to open %s for writing!\n", fileName);
+ perror(message);
+ navMesh->removeTile(tileRef, NULL, NULL);
+ continue;
+ }
+
+ printf("%s Writing to file... \r", 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;
+ fread(&header, sizeof(MmapTileHeader), 1, file);
+ fclose(file);
+
+ 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/mmap_extractor/MapBuilder.h b/src/tools/mmap_extractor/MapBuilder.h
new file mode 100644
index 00000000000..d0f33ce9a79
--- /dev/null
+++ b/src/tools/mmap_extractor/MapBuilder.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2008-2011 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"
+
+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);
+
+ // 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();
+
+ 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;
+ };
+}
+
+#endif
diff --git a/src/tools/mmap_extractor/PathCommon.h b/src/tools/mmap_extractor/PathCommon.h
new file mode 100644
index 00000000000..fd02ec02d50
--- /dev/null
+++ b/src/tools/mmap_extractor/PathCommon.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2008-2011 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 = "*", bool includeSubDirs = false)
+ {
+ #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 (includeSubDirs || (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/mmap_extractor/PathGenerator.cpp b/src/tools/mmap_extractor/PathGenerator.cpp
new file mode 100644
index 00000000000..2eb2c6545c4
--- /dev/null
+++ b/src/tools/mmap_extractor/PathGenerator.cpp
@@ -0,0 +1,273 @@
+/*
+ * 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"
+
+using namespace MMAP;
+
+bool checkDirectories(bool debugOutput)
+{
+ vector<string> dirFiles;
+
+ if (getDirContents(dirFiles, "maps") == LISTFILE_DIRECTORY_NOT_FOUND || !dirFiles.size())
+ {
+ printf("'maps' directory is empty or does not exist\n");
+ return false;
+ }
+
+ dirFiles.clear();
+ if (getDirContents(dirFiles, "vmaps", "*.vmtree") == LISTFILE_DIRECTORY_NOT_FOUND || !dirFiles.size())
+ {
+ 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* 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], "--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 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;
+
+ bool validParam = handleArgs(argc, argv, mapnum,
+ tileX, tileY, maxAngle,
+ skipLiquid, skipContinents, skipJunkMaps, skipBattlegrounds,
+ debugOutput, silent, bigBaseUnit, offMeshInputPath);
+
+ 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);
+
+ if (tileX > -1 && tileY > -1 && mapnum >= 0)
+ builder.buildSingleTile(mapnum, tileX, tileY);
+ else if (mapnum >= 0)
+ builder.buildMap(uint32(mapnum));
+ else
+ builder.buildAllMaps();
+
+ return silent ? 1 : finish("Movemap build is complete!", 1);
+}
diff --git a/src/tools/mmap_extractor/TerrainBuilder.cpp b/src/tools/mmap_extractor/TerrainBuilder.cpp
new file mode 100644
index 00000000000..c696f6017a5
--- /dev/null
+++ b/src/tools/mmap_extractor/TerrainBuilder.cpp
@@ -0,0 +1,854 @@
+/*
+ * Copyright (C) 2008-2011 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
+{
+ 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;
+ fread(&fheader, sizeof(map_fileheader), 1, mapFile);
+
+ if (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);
+ fread(&hheader, sizeof(map_heightHeader), 1, mapFile);
+
+ bool haveTerrain = !(hheader.flags & MAP_HEIGHT_NO_HEIGHT);
+ bool 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)
+ {
+ int i;
+ float heightMultiplier;
+ float V9[V9_SIZE_SQ], V8[V8_SIZE_SQ];
+
+ if (hheader.flags & MAP_HEIGHT_AS_INT8)
+ {
+ uint8 v9[V9_SIZE_SQ];
+ uint8 v8[V8_SIZE_SQ];
+ fread(v9, sizeof(uint8), V9_SIZE_SQ, mapFile);
+ fread(v8, sizeof(uint8), V8_SIZE_SQ, mapFile);
+ heightMultiplier = (hheader.gridMaxHeight - hheader.gridHeight) / 255;
+
+ for (i = 0; i < V9_SIZE_SQ; ++i)
+ V9[i] = (float)v9[i]*heightMultiplier + hheader.gridHeight;
+
+ for (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];
+ fread(v9, sizeof(uint16), V9_SIZE_SQ, mapFile);
+ fread(v8, sizeof(uint16), V8_SIZE_SQ, mapFile);
+ heightMultiplier = (hheader.gridMaxHeight - hheader.gridHeight) / 65535;
+
+ for (i = 0; i < V9_SIZE_SQ; ++i)
+ V9[i] = (float)v9[i]*heightMultiplier + hheader.gridHeight;
+
+ for (i = 0; i < V8_SIZE_SQ; ++i)
+ V8[i] = (float)v8[i]*heightMultiplier + hheader.gridHeight;
+ }
+ else
+ {
+ fread (V9, sizeof(float), V9_SIZE_SQ, mapFile);
+ fread(V8, sizeof(float), V8_SIZE_SQ, mapFile);
+ }
+
+ // hole data
+ memset(holes, 0, fheader.holesSize);
+ fseek(mapFile, fheader.holesOffset, SEEK_SET);
+ fread(holes, fheader.holesSize, 1, mapFile);
+
+ int count = meshData.solidVerts.size() / 3;
+ float xoffset = (float(tileX)-32)*GRID_SIZE;
+ float yoffset = (float(tileY)-32)*GRID_SIZE;
+
+ float coord[3];
+
+ for (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 (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 j, indices[3], loopStart, loopEnd, loopInc;
+ getLoopVars(portion, loopStart, loopEnd, loopInc);
+ for (i = loopStart; i < loopEnd; i+=loopInc)
+ for (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);
+ fread(&lheader, sizeof(map_liquidHeader), 1, mapFile);
+
+ float* liquid_map = NULL;
+
+ if (!(lheader.flags & MAP_LIQUID_NO_TYPE))
+ fread(liquid_type, sizeof(liquid_type), 1, mapFile);
+
+ if (!(lheader.flags & MAP_LIQUID_NO_HEIGHT))
+ {
+ liquid_map = new float [lheader.width*lheader.height];
+ fread(liquid_map, sizeof(float), lheader.width*lheader.height, mapFile);
+ }
+
+ 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, loopEnd, loopInc, triInc;
+ getLoopVars(portion, loopStart, loopEnd, loopInc);
+ triInc = BOTTOM-TOP;
+
+ // 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, loopEnd, loopInc, 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;
+
+ // 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)
+ 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();
+
+ // collect all the vertex indices from triangle
+ for (int i = 0; i < tris.size(); ++i)
+ {
+ if (vertMap.find(t[i]) != vertMap.end())
+ continue;
+
+ vertMap.insert(std::pair<int, int>(t[i], 0));
+ }
+
+ // collect the vertices
+ G3D::Array<float> cleanVerts;
+ int index, count = 0;
+ for (map<int, int>::iterator it = vertMap.begin(); it != vertMap.end(); ++it)
+ {
+ index = (*it).first;
+ (*it).second = count;
+ 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];
+ int mid, tx, ty;
+ float size;
+ if (10 != 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))
+ 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/mmap_extractor/TerrainBuilder.h b/src/tools/mmap_extractor/TerrainBuilder.h
new file mode 100644
index 00000000000..a7f21883af2
--- /dev/null
+++ b/src/tools/mmap_extractor/TerrainBuilder.h
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2008-2011 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
+ static char const* MAP_VERSION_MAGIC = "v1.2";
+
+ 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/mmap_extractor/VMapExtensions.cpp b/src/tools/mmap_extractor/VMapExtensions.cpp
new file mode 100644
index 00000000000..4f203e11c21
--- /dev/null
+++ b/src/tools/mmap_extractor/VMapExtensions.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2008-2011 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* const 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;
+ }
+}