mirror of
https://github.com/mod-playerbots/azerothcore-wotlk.git
synced 2026-02-03 11:03:47 +00:00
feat(Core/mmaps): Add configuration file for mmaps-generator. (#22506)
This commit is contained in:
committed by
GitHub
parent
10d5a3c553
commit
f2f31acdcf
1
deps/CMakeLists.txt
vendored
1
deps/CMakeLists.txt
vendored
@@ -45,4 +45,5 @@ endif()
|
|||||||
if (BUILD_TOOLS_MAPS)
|
if (BUILD_TOOLS_MAPS)
|
||||||
add_subdirectory(bzip2)
|
add_subdirectory(bzip2)
|
||||||
add_subdirectory(libmpq)
|
add_subdirectory(libmpq)
|
||||||
|
add_subdirectory(fkYAML)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
4
deps/PackageList.txt
vendored
4
deps/PackageList.txt
vendored
@@ -81,3 +81,7 @@ recastnavigation (Recast is state of the art navigation mesh construction toolse
|
|||||||
{fmt} is an open-source formatting library providing a fast and safe alternative to C stdio and C++ iostreams.
|
{fmt} is an open-source formatting library providing a fast and safe alternative to C stdio and C++ iostreams.
|
||||||
https://github.com/fmtlib/fmt
|
https://github.com/fmtlib/fmt
|
||||||
Version: 7.1.3
|
Version: 7.1.3
|
||||||
|
|
||||||
|
fkYAML (A C++ header-only YAML library)
|
||||||
|
https://github.com/fktn-k/fkYAML
|
||||||
|
Version: 721edb3e1a817e527fd9e1e18a3bea300822522e
|
||||||
|
|||||||
17
deps/fkYAML/CMakeLists.txt
vendored
Normal file
17
deps/fkYAML/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#
|
||||||
|
# This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
add_library(fkYAML INTERFACE)
|
||||||
|
|
||||||
|
target_include_directories(fkYAML INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|
||||||
|
set_target_properties(fkYAML PROPERTIES FOLDER "deps")
|
||||||
14730
deps/fkYAML/fkYAML/node.hpp
vendored
Normal file
14730
deps/fkYAML/fkYAML/node.hpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -23,9 +23,6 @@
|
|||||||
|
|
||||||
namespace MMAP
|
namespace MMAP
|
||||||
{
|
{
|
||||||
static char const* const MAP_FILE_NAME_FORMAT = "{}/mmaps/{:03}.mmap";
|
|
||||||
static char const* const TILE_FILE_NAME_FORMAT = "{}/mmaps/{:03}{:02}{:02}.mmtile";
|
|
||||||
|
|
||||||
// ######################## MMapMgr ########################
|
// ######################## MMapMgr ########################
|
||||||
MMapMgr::~MMapMgr()
|
MMapMgr::~MMapMgr()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ inline void dtCustomFree(void* ptr)
|
|||||||
// move map related classes
|
// move map related classes
|
||||||
namespace MMAP
|
namespace MMAP
|
||||||
{
|
{
|
||||||
|
static char const* const MAP_FILE_NAME_FORMAT = "{}/mmaps/{:03}.mmap";
|
||||||
|
static char const* const TILE_FILE_NAME_FORMAT = "{}/mmaps/{:03}{:02}{:02}.mmtile";
|
||||||
|
|
||||||
typedef std::unordered_map<uint32, dtTileRef> MMapTileSet;
|
typedef std::unordered_map<uint32, dtTileRef> MMapTileSet;
|
||||||
typedef std::unordered_map<uint32, dtNavMeshQuery*> NavMeshQuerySet;
|
typedef std::unordered_map<uint32, dtNavMeshQuery*> NavMeshQuerySet;
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,40 @@
|
|||||||
#define SIZE_OF_GRIDS 533.3333f
|
#define SIZE_OF_GRIDS 533.3333f
|
||||||
|
|
||||||
#define MMAP_MAGIC 0x4d4d4150 // 'MMAP'
|
#define MMAP_MAGIC 0x4d4d4150 // 'MMAP'
|
||||||
#define MMAP_VERSION 16
|
#define MMAP_VERSION 17
|
||||||
|
|
||||||
|
struct MmapTileRecastConfig
|
||||||
|
{
|
||||||
|
float walkableSlopeAngle;
|
||||||
|
|
||||||
|
uint8 walkableRadius; // 1
|
||||||
|
uint8 walkableHeight; // 1
|
||||||
|
uint8 walkableClimb; // 1
|
||||||
|
uint8 padding0{0}; // 1 → align next to 4
|
||||||
|
|
||||||
|
uint32 vertexPerMapEdge;
|
||||||
|
uint32 vertexPerTileEdge;
|
||||||
|
uint32 tilesPerMapEdge;
|
||||||
|
float baseUnitDim;
|
||||||
|
float cellSizeHorizontal;
|
||||||
|
float cellSizeVertical;
|
||||||
|
float maxSimplificationError;
|
||||||
|
|
||||||
|
bool operator==(const MmapTileRecastConfig& b) const {
|
||||||
|
return walkableSlopeAngle == b.walkableSlopeAngle &&
|
||||||
|
walkableRadius == b.walkableRadius &&
|
||||||
|
walkableHeight == b.walkableHeight &&
|
||||||
|
walkableClimb == b.walkableClimb &&
|
||||||
|
vertexPerMapEdge == b.vertexPerMapEdge &&
|
||||||
|
vertexPerTileEdge == b.vertexPerTileEdge &&
|
||||||
|
tilesPerMapEdge == b.tilesPerMapEdge &&
|
||||||
|
baseUnitDim == b.baseUnitDim &&
|
||||||
|
cellSizeHorizontal == b.cellSizeHorizontal &&
|
||||||
|
cellSizeVertical == b.cellSizeVertical &&
|
||||||
|
maxSimplificationError == b.maxSimplificationError;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
static_assert(sizeof(MmapTileRecastConfig) == 36, "Unexpected size of MmapTileRecastConfig");
|
||||||
|
|
||||||
struct MmapTileHeader
|
struct MmapTileHeader
|
||||||
{
|
{
|
||||||
@@ -37,17 +70,20 @@ struct MmapTileHeader
|
|||||||
char usesLiquids{true};
|
char usesLiquids{true};
|
||||||
char padding[3] {};
|
char padding[3] {};
|
||||||
|
|
||||||
|
MmapTileRecastConfig recastConfig;
|
||||||
|
|
||||||
MmapTileHeader() : dtVersion(DT_NAVMESH_VERSION) { }
|
MmapTileHeader() : dtVersion(DT_NAVMESH_VERSION) { }
|
||||||
};
|
};
|
||||||
|
|
||||||
// All padding fields must be handled and initialized to ensure mmaps_generator will produce binary-identical *.mmtile files
|
// All padding fields must be handled and initialized to ensure mmaps_generator will produce binary-identical *.mmtile files
|
||||||
static_assert(sizeof(MmapTileHeader) == 20, "MmapTileHeader size is not correct, adjust the padding field size");
|
static_assert(sizeof(MmapTileHeader) == 56, "MmapTileHeader size is not correct, adjust the padding field size");
|
||||||
static_assert(sizeof(MmapTileHeader) == (sizeof(MmapTileHeader::mmapMagic) +
|
static_assert(sizeof(MmapTileHeader) == (sizeof(MmapTileHeader::mmapMagic) +
|
||||||
sizeof(MmapTileHeader::dtVersion) +
|
sizeof(MmapTileHeader::dtVersion) +
|
||||||
sizeof(MmapTileHeader::mmapVersion) +
|
sizeof(MmapTileHeader::mmapVersion) +
|
||||||
sizeof(MmapTileHeader::size) +
|
sizeof(MmapTileHeader::size) +
|
||||||
sizeof(MmapTileHeader::usesLiquids) +
|
sizeof(MmapTileHeader::usesLiquids) +
|
||||||
sizeof(MmapTileHeader::padding)), "MmapTileHeader has uninitialized padding fields");
|
sizeof(MmapTileHeader::padding)+
|
||||||
|
sizeof(MmapTileRecastConfig)), "MmapTileHeader has uninitialized padding fields");
|
||||||
|
|
||||||
enum NavTerrain
|
enum NavTerrain
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
#include "GridNotifiers.h"
|
#include "GridNotifiers.h"
|
||||||
#include "GridNotifiersImpl.h"
|
#include "GridNotifiersImpl.h"
|
||||||
#include "MMapFactory.h"
|
#include "MMapFactory.h"
|
||||||
|
#include "MMapMgr.h"
|
||||||
#include "Map.h"
|
#include "Map.h"
|
||||||
#include "PathGenerator.h"
|
#include "PathGenerator.h"
|
||||||
#include "Player.h"
|
#include "Player.h"
|
||||||
@@ -136,6 +137,40 @@ public:
|
|||||||
GridCoord const gridCoord = Acore::ComputeGridCoord(player->GetPositionX(), player->GetPositionY());
|
GridCoord const gridCoord = Acore::ComputeGridCoord(player->GetPositionX(), player->GetPositionY());
|
||||||
|
|
||||||
handler->PSendSysMessage("{}{}{}.mmtile", player->GetMapId(), gridCoord.x_coord, gridCoord.y_coord);
|
handler->PSendSysMessage("{}{}{}.mmtile", player->GetMapId(), gridCoord.x_coord, gridCoord.y_coord);
|
||||||
|
|
||||||
|
std::string fileName = Acore::StringFormat(MMAP::TILE_FILE_NAME_FORMAT, sConfigMgr->GetOption<std::string>("DataDir", "."), player->GetMapId(), gridCoord.x_coord, gridCoord.y_coord);
|
||||||
|
FILE* file = fopen(fileName.c_str(), "rb");
|
||||||
|
if (!file)
|
||||||
|
{
|
||||||
|
LOG_DEBUG("maps", "MMAP:loadMap: Could not open mmtile file '{}'", fileName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read header
|
||||||
|
MmapTileHeader fileHeader;
|
||||||
|
if (fread(&fileHeader, sizeof(MmapTileHeader), 1, file) != 1 || fileHeader.mmapMagic != MMAP_MAGIC)
|
||||||
|
{
|
||||||
|
LOG_ERROR("maps", "MMAP:loadMap: Bad header in mmap {:03}{:02}{:02}.mmtile", player->GetMapId(), gridCoord.x_coord, gridCoord.y_coord);
|
||||||
|
fclose(file);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fclose(file);
|
||||||
|
handler->PSendSysMessage("Recast config used:");
|
||||||
|
handler->PSendSysMessage("- walkableSlopeAngle: {}", fileHeader.recastConfig.walkableSlopeAngle);
|
||||||
|
|
||||||
|
const float cellHeight = fileHeader.recastConfig.cellSizeVertical;
|
||||||
|
handler->PSendSysMessage("- walkableHeight: {} ({} units)", fileHeader.recastConfig.walkableHeight * cellHeight, fileHeader.recastConfig.walkableHeight);
|
||||||
|
handler->PSendSysMessage("- walkableClimb: {} ({} units)", fileHeader.recastConfig.walkableClimb * cellHeight, fileHeader.recastConfig.walkableClimb);
|
||||||
|
handler->PSendSysMessage("- walkableRadius: {} ({} units)", fileHeader.recastConfig.walkableRadius * cellHeight, fileHeader.recastConfig.walkableRadius);
|
||||||
|
|
||||||
|
handler->PSendSysMessage("- maxSimplificationError: {}", fileHeader.recastConfig.maxSimplificationError);
|
||||||
|
handler->PSendSysMessage("- vertexPerMapEdge: {}", fileHeader.recastConfig.vertexPerMapEdge);
|
||||||
|
handler->PSendSysMessage("- vertexPerTileEdge: {}", fileHeader.recastConfig.vertexPerTileEdge);
|
||||||
|
handler->PSendSysMessage("- tilesPerMapEdge: {}", fileHeader.recastConfig.tilesPerMapEdge);
|
||||||
|
handler->PSendSysMessage("- baseUnitDim: {}", fileHeader.recastConfig.baseUnitDim);
|
||||||
|
handler->PSendSysMessage("- cellSizeHorizontal: {}", fileHeader.recastConfig.cellSizeHorizontal);
|
||||||
|
handler->PSendSysMessage("- cellSizeVertical: {}", fileHeader.recastConfig.cellSizeVertical);
|
||||||
|
|
||||||
handler->PSendSysMessage("gridloc [{}, {}]", gridCoord.x_coord, gridCoord.y_coord);
|
handler->PSendSysMessage("gridloc [{}, {}]", gridCoord.x_coord, gridCoord.y_coord);
|
||||||
|
|
||||||
// calculate navmesh tile location
|
// calculate navmesh tile location
|
||||||
|
|||||||
@@ -140,7 +140,8 @@ foreach(TOOL_NAME ${TOOLS_BUILD_LIST})
|
|||||||
mpq
|
mpq
|
||||||
zlib
|
zlib
|
||||||
Recast
|
Recast
|
||||||
g3dlib)
|
g3dlib
|
||||||
|
fkYAML)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
unset(TOOL_PUBLIC_INCLUDES)
|
unset(TOOL_PUBLIC_INCLUDES)
|
||||||
@@ -170,4 +171,8 @@ foreach(TOOL_NAME ${TOOLS_BUILD_LIST})
|
|||||||
elseif (WIN32)
|
elseif (WIN32)
|
||||||
install(TARGETS ${TOOL_PROJECT_NAME} DESTINATION "${CMAKE_INSTALL_PREFIX}")
|
install(TARGETS ${TOOL_PROJECT_NAME} DESTINATION "${CMAKE_INSTALL_PREFIX}")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (${TOOL_PROJECT_NAME} STREQUAL "mmaps_generator")
|
||||||
|
install(FILES ${SOURCE_TOOL_PATH}/mmaps-config.yaml DESTINATION bin)
|
||||||
|
endif()
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|||||||
276
src/tools/mmaps_generator/Config.cpp
Normal file
276
src/tools/mmaps_generator/Config.cpp
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by the
|
||||||
|
* Free Software Foundation; either version 3 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 Affero 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 "Config.h"
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fkYAML/node.hpp>
|
||||||
|
#include "PathCommon.h"
|
||||||
|
#include "TerrainBuilder.h"
|
||||||
|
|
||||||
|
namespace MMAP
|
||||||
|
{
|
||||||
|
float ComputeBaseUnitDim(int vertexPerMapEdge)
|
||||||
|
{
|
||||||
|
return GRID_SIZE / static_cast<float>(vertexPerMapEdge - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<uint32, uint32> MakeTileKey(uint32 x, uint32 y)
|
||||||
|
{
|
||||||
|
return {x, y};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isCurrentDirectory(const std::string& pathStr) {
|
||||||
|
try {
|
||||||
|
const std::filesystem::path givenPath = std::filesystem::canonical(std::filesystem::absolute(pathStr));
|
||||||
|
const std::filesystem::path currentPath = std::filesystem::canonical(std::filesystem::current_path());
|
||||||
|
return givenPath == currentPath;
|
||||||
|
} catch (const std::filesystem::filesystem_error& e) {
|
||||||
|
std::cerr << "Filesystem error: " << e.what() << "\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MmapTileRecastConfig ResolvedMeshConfig::toMMAPTileRecastConfig() const {
|
||||||
|
MmapTileRecastConfig config;
|
||||||
|
config.walkableSlopeAngle = walkableSlopeAngle;
|
||||||
|
config.walkableHeight = walkableHeight;
|
||||||
|
config.walkableClimb = walkableClimb;
|
||||||
|
config.walkableRadius = walkableRadius;
|
||||||
|
config.maxSimplificationError = maxSimplificationError;
|
||||||
|
config.cellSizeHorizontal = cellSizeHorizontal;
|
||||||
|
config.cellSizeVertical = cellSizeVertical;
|
||||||
|
config.baseUnitDim = baseUnitDim;
|
||||||
|
config.vertexPerMapEdge = vertexPerMapEdge;
|
||||||
|
config.vertexPerTileEdge = vertexPerTileEdge;
|
||||||
|
config.tilesPerMapEdge = tilesPerMapEdge;
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<Config> Config::FromFile(std::string_view configFile) {
|
||||||
|
Config config;
|
||||||
|
if (!config.LoadConfig(configFile))
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
Config::Config()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ResolvedMeshConfig Config::GetConfigForTile(uint32 mapID, uint32 tileX, uint32 tileY) const
|
||||||
|
{
|
||||||
|
const MapOverride* mapOverride = nullptr;
|
||||||
|
const TileOverride* tileOverride = nullptr;
|
||||||
|
|
||||||
|
// Lookup map and tile overrides
|
||||||
|
if (auto mapIt = _maps.find(mapID); mapIt != _maps.end())
|
||||||
|
{
|
||||||
|
mapOverride = &mapIt->second;
|
||||||
|
|
||||||
|
auto tileIt = mapOverride->tileOverrides.find(MakeTileKey(tileY, tileX));
|
||||||
|
if (tileIt != mapOverride->tileOverrides.end())
|
||||||
|
tileOverride = &tileIt->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper lambdas to resolve values in order: tile -> map -> global
|
||||||
|
auto resolveFloat = [&](auto TileField, auto MapField, float GlobalValue) -> float {
|
||||||
|
if (tileOverride && TileField(tileOverride)) return *TileField(tileOverride);
|
||||||
|
if (mapOverride && MapField(mapOverride)) return *MapField(mapOverride);
|
||||||
|
return GlobalValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto resolveInt = [&](auto TileField, auto MapField, int GlobalValue) -> int {
|
||||||
|
if (tileOverride && TileField(tileOverride)) return *TileField(tileOverride);
|
||||||
|
if (mapOverride && MapField(mapOverride)) return *MapField(mapOverride);
|
||||||
|
return GlobalValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Resolve vertex settings
|
||||||
|
int vertexPerMap = resolveInt(
|
||||||
|
[](const TileOverride*) { return std::optional<int>{}; },
|
||||||
|
[](const MapOverride* m) { return m->vertexPerMapEdge; },
|
||||||
|
_global.vertexPerMapEdge
|
||||||
|
);
|
||||||
|
|
||||||
|
int vertexPerTile = resolveInt(
|
||||||
|
[](const TileOverride*) { return std::optional<int>{}; },
|
||||||
|
[](const MapOverride* m) { return m->vertexPerTileEdge; },
|
||||||
|
_global.vertexPerTileEdge
|
||||||
|
);
|
||||||
|
|
||||||
|
ResolvedMeshConfig config;
|
||||||
|
config.walkableSlopeAngle = resolveFloat(
|
||||||
|
[](const TileOverride* t) { return t->walkableSlopeAngle; },
|
||||||
|
[](const MapOverride* m) { return m->walkableSlopeAngle; },
|
||||||
|
_global.walkableSlopeAngle
|
||||||
|
);
|
||||||
|
|
||||||
|
config.walkableRadius = resolveInt(
|
||||||
|
[](const TileOverride* t) { return t->walkableRadius; },
|
||||||
|
[](const MapOverride* m) { return m->walkableRadius; },
|
||||||
|
_global.walkableRadius
|
||||||
|
);
|
||||||
|
|
||||||
|
config.walkableHeight = resolveInt(
|
||||||
|
[](const TileOverride* t) { return t->walkableHeight; },
|
||||||
|
[](const MapOverride* m) { return m->walkableHeight; },
|
||||||
|
_global.walkableHeight
|
||||||
|
);
|
||||||
|
|
||||||
|
config.walkableClimb = resolveInt(
|
||||||
|
[](const TileOverride* t) { return t->walkableClimb; },
|
||||||
|
[](const MapOverride* m) { return m->walkableClimb; },
|
||||||
|
_global.walkableClimb
|
||||||
|
);
|
||||||
|
|
||||||
|
config.vertexPerMapEdge = vertexPerMap;
|
||||||
|
config.vertexPerTileEdge = vertexPerTile;
|
||||||
|
config.baseUnitDim = ComputeBaseUnitDim(vertexPerMap);
|
||||||
|
config.tilesPerMapEdge = vertexPerMap / vertexPerTile;
|
||||||
|
config.maxSimplificationError = _global.maxSimplificationError;
|
||||||
|
config.cellSizeHorizontal = config.baseUnitDim;
|
||||||
|
config.cellSizeVertical = config.baseUnitDim;
|
||||||
|
|
||||||
|
if (mapOverride && mapOverride->cellSizeHorizontal.has_value())
|
||||||
|
config.cellSizeHorizontal = *mapOverride->cellSizeHorizontal;
|
||||||
|
|
||||||
|
if (mapOverride && mapOverride->cellSizeVertical.has_value())
|
||||||
|
config.cellSizeVertical = *mapOverride->cellSizeVertical;
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Config::LoadConfig(std::string_view configFile) {
|
||||||
|
FILE* f = std::fopen(configFile.data(), "r");
|
||||||
|
if (!f)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
fkyaml::node root = fkyaml::node::deserialize(f);
|
||||||
|
std::fclose(f);
|
||||||
|
|
||||||
|
if (!root.contains("mmapsConfig"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
fkyaml::node mmapsNode = root["mmapsConfig"];
|
||||||
|
|
||||||
|
auto tryFloat = [](const fkyaml::node& n, const char* key, float& out)
|
||||||
|
{
|
||||||
|
if (n.contains(key)) out = n[key].get_value<float>();
|
||||||
|
};
|
||||||
|
auto tryInt = [](const fkyaml::node& n, const char* key, int& out)
|
||||||
|
{
|
||||||
|
if (n.contains(key)) out = n[key].get_value<int>();
|
||||||
|
};
|
||||||
|
auto tryBoolean = [](const fkyaml::node& n, const char* key, bool& out)
|
||||||
|
{
|
||||||
|
if (n.contains(key)) out = n[key].get_value<bool>();
|
||||||
|
};
|
||||||
|
auto tryString = [](const fkyaml::node& n, const char* key, std::string& out)
|
||||||
|
{
|
||||||
|
if (n.contains(key)) out = n[key].get_value<std::string>();
|
||||||
|
};
|
||||||
|
|
||||||
|
tryBoolean(mmapsNode, "skipLiquid", _skipLiquid);
|
||||||
|
tryBoolean(mmapsNode, "skipContinents", _skipContinents);
|
||||||
|
tryBoolean(mmapsNode, "skipJunkMaps", _skipJunkMaps);
|
||||||
|
tryBoolean(mmapsNode, "skipBattlegrounds", _skipBattlegrounds);
|
||||||
|
tryBoolean(mmapsNode, "debugOutput", _debugOutput);
|
||||||
|
|
||||||
|
std::string dataDirPath;
|
||||||
|
tryString(mmapsNode, "dataDir", dataDirPath);
|
||||||
|
_dataDir = dataDirPath;
|
||||||
|
|
||||||
|
mmapsNode = mmapsNode["meshSettings"];
|
||||||
|
|
||||||
|
// Global config
|
||||||
|
tryFloat(mmapsNode, "walkableSlopeAngle", _global.walkableSlopeAngle);
|
||||||
|
tryInt(mmapsNode, "walkableHeight", _global.walkableHeight);
|
||||||
|
tryInt(mmapsNode, "walkableClimb", _global.walkableClimb);
|
||||||
|
tryInt(mmapsNode, "walkableRadius", _global.walkableRadius);
|
||||||
|
tryInt(mmapsNode, "vertexPerMapEdge", _global.vertexPerMapEdge);
|
||||||
|
tryInt(mmapsNode, "vertexPerTileEdge", _global.vertexPerTileEdge);
|
||||||
|
tryFloat(mmapsNode, "maxSimplificationError", _global.maxSimplificationError);
|
||||||
|
|
||||||
|
// Map overrides
|
||||||
|
if (mmapsNode.contains("mapsOverrides"))
|
||||||
|
{
|
||||||
|
fkyaml::node maps = mmapsNode["mapsOverrides"];
|
||||||
|
for (auto const& mapEntry : maps.as_map())
|
||||||
|
{
|
||||||
|
uint32 mapId = std::stoi(mapEntry.first.as_str());
|
||||||
|
|
||||||
|
MapOverride override;
|
||||||
|
fkyaml::node mapNode = mapEntry.second;
|
||||||
|
|
||||||
|
if (mapNode.contains("walkableSlopeAngle"))
|
||||||
|
override.walkableSlopeAngle = mapNode["walkableSlopeAngle"].get_value<float>();
|
||||||
|
if (mapNode.contains("walkableRadius"))
|
||||||
|
override.walkableRadius = mapNode["walkableRadius"].get_value<int>();
|
||||||
|
if (mapNode.contains("walkableHeight"))
|
||||||
|
override.walkableHeight = mapNode["walkableHeight"].get_value<int>();
|
||||||
|
if (mapNode.contains("walkableClimb"))
|
||||||
|
override.walkableClimb = mapNode["walkableClimb"].get_value<int>();
|
||||||
|
if (mapNode.contains("vertexPerMapEdge"))
|
||||||
|
override.vertexPerMapEdge = mapNode["vertexPerMapEdge"].get_value<int>();
|
||||||
|
if (mapNode.contains("cellSizeHorizontal"))
|
||||||
|
override.cellSizeHorizontal = mapNode["cellSizeHorizontal"].get_value<float>();
|
||||||
|
if (mapNode.contains("cellSizeVertical"))
|
||||||
|
override.cellSizeVertical = mapNode["cellSizeVertical"].get_value<float>();
|
||||||
|
|
||||||
|
// Tile overrides
|
||||||
|
if (mapNode.contains("tilesOverrides"))
|
||||||
|
{
|
||||||
|
fkyaml::node tiles = mapNode["tilesOverrides"];
|
||||||
|
for (auto const& tileEntry : tiles.as_map())
|
||||||
|
{
|
||||||
|
std::string key = tileEntry.first.as_str();
|
||||||
|
fkyaml::node tileNode = tileEntry.second;
|
||||||
|
|
||||||
|
size_t comma = key.find(',');
|
||||||
|
if (comma == std::string::npos)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
uint32 tileX = static_cast<uint32>(std::stoi(key.substr(0, comma)));
|
||||||
|
uint32 tileY = static_cast<uint32>(std::stoi(key.substr(comma + 1)));
|
||||||
|
|
||||||
|
TileOverride tileOverride;
|
||||||
|
if (tileNode.contains("walkableSlopeAngle"))
|
||||||
|
tileOverride.walkableSlopeAngle = tileNode["walkableSlopeAngle"].get_value<float>();
|
||||||
|
if (tileNode.contains("walkableRadius"))
|
||||||
|
tileOverride.walkableRadius = tileNode["walkableRadius"].get_value<int>();
|
||||||
|
if (tileNode.contains("walkableHeight"))
|
||||||
|
tileOverride.walkableHeight = tileNode["walkableHeight"].get_value<int>();
|
||||||
|
if (tileNode.contains("walkableClimb"))
|
||||||
|
tileOverride.walkableClimb = tileNode["walkableClimb"].get_value<int>();
|
||||||
|
|
||||||
|
override.tileOverrides[{tileX, tileY}] = std::move(tileOverride);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_maps[mapId] = std::move(override);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve data dir path. Maybe we need to use an executable path instead of the current dir.
|
||||||
|
if (isCurrentDirectory(_dataDir.string()) && !std::filesystem::exists(MapsPath()))
|
||||||
|
if (auto execPath = std::filesystem::path(executableDirectoryPath()); std::filesystem::exists(execPath/ "maps"))
|
||||||
|
_dataDir = execPath;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
159
src/tools/mmaps_generator/Config.h
Normal file
159
src/tools/mmaps_generator/Config.h
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by the
|
||||||
|
* Free Software Foundation; either version 3 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 Affero 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 CONFIG_H
|
||||||
|
#define CONFIG_H
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <boost/program_options/options_description.hpp>
|
||||||
|
#include "Define.h"
|
||||||
|
#include "MapDefines.h"
|
||||||
|
|
||||||
|
namespace std
|
||||||
|
{
|
||||||
|
template <>
|
||||||
|
struct hash<std::pair<uint32_t, uint32_t>>
|
||||||
|
{
|
||||||
|
std::size_t operator()(const std::pair<uint32_t, uint32_t>& p) const noexcept
|
||||||
|
{
|
||||||
|
return std::hash<uint64_t>()((static_cast<uint64_t>(p.first) << 32) | p.second);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace MMAP
|
||||||
|
{
|
||||||
|
struct ResolvedMeshConfig {
|
||||||
|
float walkableSlopeAngle;
|
||||||
|
int walkableRadius;
|
||||||
|
int walkableHeight;
|
||||||
|
int walkableClimb;
|
||||||
|
int vertexPerMapEdge;
|
||||||
|
int vertexPerTileEdge;
|
||||||
|
int tilesPerMapEdge;
|
||||||
|
float baseUnitDim;
|
||||||
|
float cellSizeHorizontal;
|
||||||
|
float cellSizeVertical;
|
||||||
|
float maxSimplificationError;
|
||||||
|
|
||||||
|
MmapTileRecastConfig toMMAPTileRecastConfig() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Config {
|
||||||
|
public:
|
||||||
|
static std::optional<Config> FromFile(std::string_view configFile);
|
||||||
|
|
||||||
|
~Config() = default;
|
||||||
|
|
||||||
|
ResolvedMeshConfig GetConfigForTile(uint32 mapID, uint32 tileX, uint32 tileY) const;
|
||||||
|
|
||||||
|
bool ShouldSkipLiquid() const { return _skipLiquid; }
|
||||||
|
bool ShouldSkipContinents() const { return _skipContinents; }
|
||||||
|
bool ShouldSkipJunkMaps() const { return _skipJunkMaps; }
|
||||||
|
bool ShouldSkipBattlegrounds() const { return _skipBattlegrounds; }
|
||||||
|
bool IsDebugOutputEnabled() const { return _debugOutput; }
|
||||||
|
|
||||||
|
std::string VMapsPath() const { return (_dataDir / "vmaps").string(); }
|
||||||
|
std::string MapsPath() const { return (_dataDir / "maps").string(); }
|
||||||
|
std::string MMapsPath() const { return (_dataDir / "mmaps").string(); }
|
||||||
|
std::string DataDirPath() const { return _dataDir.string(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit Config();
|
||||||
|
|
||||||
|
bool LoadConfig(std::string_view configFile);
|
||||||
|
|
||||||
|
struct TileOverride {
|
||||||
|
std::optional<float> walkableSlopeAngle;
|
||||||
|
std::optional<int> walkableRadius;
|
||||||
|
std::optional<int> walkableHeight;
|
||||||
|
std::optional<int> walkableClimb;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MapOverride {
|
||||||
|
std::optional<float> walkableSlopeAngle;
|
||||||
|
std::optional<int> walkableRadius;
|
||||||
|
std::optional<int> walkableHeight;
|
||||||
|
std::optional<int> walkableClimb;
|
||||||
|
std::optional<int> vertexPerMapEdge;
|
||||||
|
std::optional<int> vertexPerTileEdge;
|
||||||
|
|
||||||
|
// The width/depth of each cell in the XZ-plane grid used for voxelization. [Units: world units]
|
||||||
|
// A smaller value increases navmesh resolution but also memory and CPU usage.
|
||||||
|
// Default is equal to calculated baseUnitDim.
|
||||||
|
// Recast reference: https://github.com/recastnavigation/recastnavigation/blob/bd98d84c274ee06842bf51a4088ca82ac71f8c2d/Recast/Include/Recast.h#L231
|
||||||
|
std::optional<float> cellSizeHorizontal;
|
||||||
|
|
||||||
|
// The height of each cell in the Y-axis used for voxelization. [Units: world units]
|
||||||
|
// Controls how vertical features are represented. Lower values improve accuracy for uneven terrain.
|
||||||
|
// Default is equal to calculated baseUnitDim.
|
||||||
|
// Recast reference: https://github.com/recastnavigation/recastnavigation/blob/bd98d84c274ee06842bf51a4088ca82ac71f8c2d/Recast/Include/Recast.h#L234
|
||||||
|
std::optional<float> cellSizeVertical;
|
||||||
|
|
||||||
|
std::unordered_map<std::pair<uint32, uint32>, TileOverride> tileOverrides;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GlobalConfig {
|
||||||
|
// Maximum slope angle (in degrees) NPCs can walk on.
|
||||||
|
// Surfaces steeper than this will be considered unwalkable.
|
||||||
|
float walkableSlopeAngle = 60.0f;
|
||||||
|
|
||||||
|
// Minimum distance (in cell units) around walkable surfaces.
|
||||||
|
// Helps prevent NPCs from clipping into walls and narrow gaps.
|
||||||
|
int walkableRadius = 2;
|
||||||
|
|
||||||
|
// Minimum ceiling height (in cell units) NPCs need to pass under an obstacle.
|
||||||
|
// Controls how much vertical clearance is required.
|
||||||
|
int walkableHeight = 6;
|
||||||
|
|
||||||
|
// Maximum height difference (in cell units) NPCs can step up or down.
|
||||||
|
// Higher values allow walking over fences, ledges, or steps.
|
||||||
|
int walkableClimb = 6;
|
||||||
|
|
||||||
|
// Number of vertices along one edge of the entire map's navmesh grid.
|
||||||
|
// Higher values increase mesh resolution but also CPU/memory usage.
|
||||||
|
int vertexPerMapEdge = 2000;
|
||||||
|
|
||||||
|
// Number of vertices along one edge of each tile chunk.
|
||||||
|
// Must divide (vertexPerMapEdge - 1) evenly for seamless tiles.
|
||||||
|
// A higher vertex count per tile means fewer total tiles,
|
||||||
|
// reducing runtime work to load, unload, and manage tiles.
|
||||||
|
int vertexPerTileEdge = 80;
|
||||||
|
|
||||||
|
// Tolerance for how much a polygon can deviate from the original geometry when simplified.
|
||||||
|
// Higher values produce simpler (faster) meshes but can reduce accuracy.
|
||||||
|
float maxSimplificationError = 1.8f;
|
||||||
|
};
|
||||||
|
|
||||||
|
GlobalConfig _global;
|
||||||
|
std::unordered_map<uint32, MapOverride> _maps;
|
||||||
|
|
||||||
|
bool _skipLiquid;
|
||||||
|
bool _skipContinents;
|
||||||
|
bool _skipJunkMaps;
|
||||||
|
bool _skipBattlegrounds;
|
||||||
|
bool _debugOutput;
|
||||||
|
|
||||||
|
std::filesystem::path _dataDir;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //CONFIG_H
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
Generator command line args
|
Generator command line args
|
||||||
|
|
||||||
|
--config [file.*] The path the yaml config file
|
||||||
|
Default: "mmaps-config.yaml"
|
||||||
|
|
||||||
--threads [#] Max number of threads used by the generator
|
--threads [#] Max number of threads used by the generator
|
||||||
Default: 3
|
Default: 3
|
||||||
|
|
||||||
@@ -11,39 +14,6 @@ Generator command line args
|
|||||||
--silent [] Make us script friendly. Do not wait for user input
|
--silent [] Make us script friendly. Do not wait for user input
|
||||||
on error or completion.
|
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 [true|false] extract 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 Moongose,
|
|
||||||
you don't want debugging files
|
|
||||||
|
|
||||||
false: don't create debugging files (default)
|
|
||||||
|
|
||||||
--tile [#,#] Build the specified tile
|
--tile [#,#] Build the specified tile
|
||||||
seperate number with a comma ','
|
seperate number with a comma ','
|
||||||
must specify a map number (see below)
|
must specify a map number (see below)
|
||||||
@@ -58,9 +28,6 @@ examples:
|
|||||||
movement_extractor
|
movement_extractor
|
||||||
builds maps using the default settings (see above for defaults)
|
builds maps using the default settings (see above for defaults)
|
||||||
|
|
||||||
movement_extractor --skipContinents true
|
|
||||||
builds the default maps, except continents
|
|
||||||
|
|
||||||
movement_extractor 0
|
movement_extractor 0
|
||||||
builds all tiles of map 0
|
builds all tiles of map 0
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "IntermediateValues.h"
|
#include "IntermediateValues.h"
|
||||||
|
#include <string>
|
||||||
|
#include "StringFormat.h"
|
||||||
|
|
||||||
namespace MMAP
|
namespace MMAP
|
||||||
{
|
{
|
||||||
@@ -28,15 +30,15 @@ namespace MMAP
|
|||||||
rcFreePolyMeshDetail(polyMeshDetail);
|
rcFreePolyMeshDetail(polyMeshDetail);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IntermediateValues::writeIV(uint32 mapID, uint32 tileX, uint32 tileY)
|
void IntermediateValues::writeIV(const std::string& dataPath, uint32 mapID, uint32 tileX, uint32 tileY)
|
||||||
{
|
{
|
||||||
char fileName[255];
|
char fileName[512];
|
||||||
char tileString[25];
|
char tileString[25];
|
||||||
sprintf(tileString, "[%02u,%02u]: ", tileX, tileY);
|
sprintf(tileString, "[%02u,%02u]: ", tileX, tileY);
|
||||||
|
|
||||||
printf("%sWriting debug output... \r", tileString);
|
printf("%sWriting debug output... \r", tileString);
|
||||||
|
|
||||||
std::string name("meshes/%03u%02i%02i.");
|
std::string name(dataPath+"/meshes/%03u%02i%02i.");
|
||||||
|
|
||||||
#define DEBUG_WRITE(fileExtension,data) \
|
#define DEBUG_WRITE(fileExtension,data) \
|
||||||
do { \
|
do { \
|
||||||
@@ -198,16 +200,19 @@ namespace MMAP
|
|||||||
fwrite(mesh->meshes, sizeof(int), mesh->nmeshes * 4, file);
|
fwrite(mesh->meshes, sizeof(int), mesh->nmeshes * 4, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IntermediateValues::generateObjFile(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData)
|
void IntermediateValues::generateObjFile(const std::string& dataPath, uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData)
|
||||||
{
|
{
|
||||||
char objFileName[255];
|
std::string objFileName = Acore::StringFormat(
|
||||||
sprintf(objFileName, "meshes/map%03u%02u%02u.obj", mapID, tileY, tileX);
|
"{}/meshes/map{:03}{:02}{:02}.obj",
|
||||||
|
dataPath,
|
||||||
|
mapID, tileY, tileX
|
||||||
|
);
|
||||||
|
|
||||||
FILE* objFile = fopen(objFileName, "wb");
|
FILE* objFile = fopen(objFileName.c_str(), "wb");
|
||||||
if (!objFile)
|
if (!objFile)
|
||||||
{
|
{
|
||||||
char message[1024];
|
char message[1024];
|
||||||
sprintf(message, "Failed to open %s for writing!\n", objFileName);
|
sprintf(message, "Failed to open %s for writing!\n", objFileName.c_str());
|
||||||
perror(message);
|
perror(message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -237,13 +242,17 @@ namespace MMAP
|
|||||||
sprintf(tileString, "[%02u,%02u]: ", tileY, tileX);
|
sprintf(tileString, "[%02u,%02u]: ", tileY, tileX);
|
||||||
printf("%sWriting debug output... \r", tileString);
|
printf("%sWriting debug output... \r", tileString);
|
||||||
|
|
||||||
sprintf(objFileName, "meshes/%03u.map", mapID);
|
objFileName = Acore::StringFormat(
|
||||||
|
"{}/meshes/{:03}.map",
|
||||||
|
dataPath,
|
||||||
|
mapID
|
||||||
|
);
|
||||||
|
|
||||||
objFile = fopen(objFileName, "wb");
|
objFile = fopen(objFileName.c_str(), "wb");
|
||||||
if (!objFile)
|
if (!objFile)
|
||||||
{
|
{
|
||||||
char message[1024];
|
char message[1024];
|
||||||
sprintf(message, "Failed to open %s for writing!\n", objFileName);
|
sprintf(message, "Failed to open %s for writing!\n", objFileName.c_str());
|
||||||
perror(message);
|
perror(message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -252,12 +261,17 @@ namespace MMAP
|
|||||||
fwrite(&b, sizeof(char), 1, objFile);
|
fwrite(&b, sizeof(char), 1, objFile);
|
||||||
fclose(objFile);
|
fclose(objFile);
|
||||||
|
|
||||||
sprintf(objFileName, "meshes/%03u%02u%02u.mesh", mapID, tileY, tileX);
|
objFileName = Acore::StringFormat(
|
||||||
objFile = fopen(objFileName, "wb");
|
"{}/meshes/{:03}{:02}{:02}.mesh",
|
||||||
|
dataPath,
|
||||||
|
mapID, tileY, tileX
|
||||||
|
);
|
||||||
|
|
||||||
|
objFile = fopen(objFileName.c_str(), "wb");
|
||||||
if (!objFile)
|
if (!objFile)
|
||||||
{
|
{
|
||||||
char message[1024];
|
char message[1024];
|
||||||
sprintf(message, "Failed to open %s for writing!\n", objFileName);
|
sprintf(message, "Failed to open %s for writing!\n", objFileName.c_str());
|
||||||
perror(message);
|
perror(message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ namespace MMAP
|
|||||||
IntermediateValues() {}
|
IntermediateValues() {}
|
||||||
~IntermediateValues();
|
~IntermediateValues();
|
||||||
|
|
||||||
void writeIV(uint32 mapID, uint32 tileX, uint32 tileY);
|
void writeIV(const std::string& dataPath, uint32 mapID, uint32 tileX, uint32 tileY);
|
||||||
|
|
||||||
void debugWrite(FILE* file, const rcHeightfield* mesh);
|
void debugWrite(FILE* file, const rcHeightfield* mesh);
|
||||||
void debugWrite(FILE* file, const rcCompactHeightfield* chf);
|
void debugWrite(FILE* file, const rcCompactHeightfield* chf);
|
||||||
@@ -43,7 +43,7 @@ namespace MMAP
|
|||||||
void debugWrite(FILE* file, const rcPolyMesh* mesh);
|
void debugWrite(FILE* file, const rcPolyMesh* mesh);
|
||||||
void debugWrite(FILE* file, const rcPolyMeshDetail* mesh);
|
void debugWrite(FILE* file, const rcPolyMeshDetail* mesh);
|
||||||
|
|
||||||
void generateObjFile(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData);
|
void generateObjFile(const std::string& dataPath, uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -16,28 +16,28 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "MapBuilder.h"
|
#include "MapBuilder.h"
|
||||||
|
#include <DetourCommon.h>
|
||||||
|
#include <DetourNavMesh.h>
|
||||||
|
#include <DetourNavMeshBuilder.h>
|
||||||
#include "IntermediateValues.h"
|
#include "IntermediateValues.h"
|
||||||
#include "MapDefines.h"
|
#include "MapDefines.h"
|
||||||
#include "MapTree.h"
|
#include "MapTree.h"
|
||||||
|
#include "MMapMgr.h"
|
||||||
#include "ModelInstance.h"
|
#include "ModelInstance.h"
|
||||||
#include "PathCommon.h"
|
#include "PathCommon.h"
|
||||||
#include "StringFormat.h"
|
#include "StringFormat.h"
|
||||||
#include "VMapMgr2.h"
|
#include "VMapMgr2.h"
|
||||||
#include <DetourCommon.h>
|
|
||||||
#include <DetourNavMesh.h>
|
|
||||||
#include <DetourNavMeshBuilder.h>
|
|
||||||
|
|
||||||
namespace MMAP
|
namespace MMAP
|
||||||
{
|
{
|
||||||
TileBuilder::TileBuilder(MapBuilder* mapBuilder, bool skipLiquid, bool bigBaseUnit, bool debugOutput) :
|
TileBuilder::TileBuilder(MapBuilder* mapBuilder, bool skipLiquid, bool debugOutput) :
|
||||||
m_bigBaseUnit(bigBaseUnit),
|
|
||||||
m_debugOutput(debugOutput),
|
m_debugOutput(debugOutput),
|
||||||
m_mapBuilder(mapBuilder),
|
m_mapBuilder(mapBuilder),
|
||||||
m_terrainBuilder(nullptr),
|
m_terrainBuilder(nullptr),
|
||||||
m_workerThread(&TileBuilder::WorkerThread, this),
|
m_workerThread(&TileBuilder::WorkerThread, this),
|
||||||
m_rcContext(nullptr)
|
m_rcContext(nullptr)
|
||||||
{
|
{
|
||||||
m_terrainBuilder = new TerrainBuilder(skipLiquid);
|
m_terrainBuilder = new TerrainBuilder(m_mapBuilder->getConfig().DataDirPath(), skipLiquid);
|
||||||
m_rcContext = new rcContext(false);
|
m_rcContext = new rcContext(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,26 +55,22 @@ namespace MMAP
|
|||||||
m_workerThread.join();
|
m_workerThread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
MapBuilder::MapBuilder(float maxWalkableAngle, bool skipLiquid,
|
MapBuilder::MapBuilder(Config* config, int mapid, const char* offMeshFilePath, unsigned int threads) :
|
||||||
bool skipContinents, bool skipJunkMaps, bool skipBattlegrounds,
|
m_config (config),
|
||||||
bool debugOutput, bool bigBaseUnit, int mapid, const char* offMeshFilePath, unsigned int threads) :
|
m_debugOutput (config->IsDebugOutputEnabled()),
|
||||||
|
|
||||||
m_debugOutput (debugOutput),
|
|
||||||
m_offMeshFilePath (offMeshFilePath),
|
m_offMeshFilePath (offMeshFilePath),
|
||||||
m_threads (threads),
|
m_threads (threads),
|
||||||
m_skipContinents (skipContinents),
|
m_skipContinents (config->ShouldSkipContinents()),
|
||||||
m_skipJunkMaps (skipJunkMaps),
|
m_skipJunkMaps (config->ShouldSkipJunkMaps()),
|
||||||
m_skipBattlegrounds (skipBattlegrounds),
|
m_skipBattlegrounds (config->ShouldSkipBattlegrounds()),
|
||||||
m_skipLiquid (skipLiquid),
|
m_skipLiquid (config->ShouldSkipLiquid()),
|
||||||
m_maxWalkableAngle (maxWalkableAngle),
|
|
||||||
m_bigBaseUnit (bigBaseUnit),
|
|
||||||
m_mapid (mapid),
|
m_mapid (mapid),
|
||||||
m_totalTiles (0u),
|
m_totalTiles (0u),
|
||||||
m_totalTilesProcessed(0u),
|
m_totalTilesProcessed(0u),
|
||||||
|
|
||||||
_cancelationToken (false)
|
_cancelationToken (false)
|
||||||
{
|
{
|
||||||
m_terrainBuilder = new TerrainBuilder(skipLiquid);
|
m_terrainBuilder = new TerrainBuilder(config->DataDirPath(), config->ShouldSkipLiquid());
|
||||||
|
|
||||||
m_rcContext = new rcContext(false);
|
m_rcContext = new rcContext(false);
|
||||||
|
|
||||||
@@ -105,7 +101,7 @@ namespace MMAP
|
|||||||
char filter[12];
|
char filter[12];
|
||||||
|
|
||||||
printf("Discovering maps... ");
|
printf("Discovering maps... ");
|
||||||
getDirContents(files, "maps");
|
getDirContents(files, m_config->MapsPath());
|
||||||
for (auto & file : files)
|
for (auto & file : files)
|
||||||
{
|
{
|
||||||
mapID = uint32(atoi(file.substr(0, file.size() - 8).c_str()));
|
mapID = uint32(atoi(file.substr(0, file.size() - 8).c_str()));
|
||||||
@@ -117,7 +113,7 @@ namespace MMAP
|
|||||||
}
|
}
|
||||||
|
|
||||||
files.clear();
|
files.clear();
|
||||||
getDirContents(files, "vmaps", "*.vmtree");
|
getDirContents(files, m_config->VMapsPath(), "*.vmtree");
|
||||||
for (auto & file : files)
|
for (auto & file : files)
|
||||||
{
|
{
|
||||||
mapID = uint32(atoi(file.substr(0, file.size() - 7).c_str()));
|
mapID = uint32(atoi(file.substr(0, file.size() - 7).c_str()));
|
||||||
@@ -138,7 +134,7 @@ namespace MMAP
|
|||||||
|
|
||||||
sprintf(filter, "%03u*.vmtile", mapID);
|
sprintf(filter, "%03u*.vmtile", mapID);
|
||||||
files.clear();
|
files.clear();
|
||||||
getDirContents(files, "vmaps", filter);
|
getDirContents(files, m_config->VMapsPath(), filter);
|
||||||
for (auto & file : files)
|
for (auto & file : files)
|
||||||
{
|
{
|
||||||
fsize = file.size();
|
fsize = file.size();
|
||||||
@@ -153,7 +149,7 @@ namespace MMAP
|
|||||||
|
|
||||||
sprintf(filter, "%03u*", mapID);
|
sprintf(filter, "%03u*", mapID);
|
||||||
files.clear();
|
files.clear();
|
||||||
getDirContents(files, "maps", filter);
|
getDirContents(files, m_config->MapsPath(), filter);
|
||||||
for (auto & file : files)
|
for (auto & file : files)
|
||||||
{
|
{
|
||||||
fsize = file.size();
|
fsize = file.size();
|
||||||
@@ -209,7 +205,7 @@ namespace MMAP
|
|||||||
|
|
||||||
for (unsigned int i = 0; i < m_threads; ++i)
|
for (unsigned int i = 0; i < m_threads; ++i)
|
||||||
{
|
{
|
||||||
m_tileBuilders.push_back(new TileBuilder(this, m_skipLiquid, m_bigBaseUnit, m_debugOutput));
|
m_tileBuilders.push_back(new TileBuilder(this, m_skipLiquid, m_debugOutput));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mapID)
|
if (mapID)
|
||||||
@@ -367,7 +363,7 @@ namespace MMAP
|
|||||||
getTileBounds(tileX, tileY, data.solidVerts.getCArray(), data.solidVerts.size() / 3, bmin, bmax);
|
getTileBounds(tileX, tileY, data.solidVerts.getCArray(), data.solidVerts.size() / 3, bmin, bmax);
|
||||||
|
|
||||||
// build navmesh tile
|
// build navmesh tile
|
||||||
TileBuilder tileBuilder = TileBuilder(this, m_skipLiquid, m_bigBaseUnit, m_debugOutput);
|
TileBuilder tileBuilder = TileBuilder(this, m_skipLiquid, m_debugOutput);
|
||||||
tileBuilder.buildMoveMapTile(mapId, tileX, tileY, data, bmin, bmax, navMesh);
|
tileBuilder.buildMoveMapTile(mapId, tileX, tileY, data, bmin, bmax, navMesh);
|
||||||
fclose(file);
|
fclose(file);
|
||||||
}
|
}
|
||||||
@@ -385,7 +381,7 @@ namespace MMAP
|
|||||||
|
|
||||||
/// @todo: delete the old tile as the user clearly wants to rebuild it
|
/// @todo: delete the old tile as the user clearly wants to rebuild it
|
||||||
|
|
||||||
TileBuilder tileBuilder = TileBuilder(this, m_skipLiquid, m_bigBaseUnit, m_debugOutput);
|
TileBuilder tileBuilder = TileBuilder(this, m_skipLiquid, m_debugOutput);
|
||||||
tileBuilder.buildTile(mapID, tileX, tileY, navMesh);
|
tileBuilder.buildTile(mapID, tileX, tileY, navMesh);
|
||||||
dtFreeNavMesh(navMesh);
|
dtFreeNavMesh(navMesh);
|
||||||
|
|
||||||
@@ -567,15 +563,18 @@ namespace MMAP
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
char fileName[25];
|
const std::string fileName = Acore::StringFormat(
|
||||||
sprintf(fileName, "mmaps/%03u.mmap", mapID);
|
MAP_FILE_NAME_FORMAT,
|
||||||
|
m_config->DataDirPath(),
|
||||||
|
mapID
|
||||||
|
);
|
||||||
|
|
||||||
FILE* file = fopen(fileName, "wb");
|
FILE* file = fopen(fileName.c_str(), "wb");
|
||||||
if (!file)
|
if (!file)
|
||||||
{
|
{
|
||||||
dtFreeNavMesh(navMesh);
|
dtFreeNavMesh(navMesh);
|
||||||
char message[1024];
|
char message[1024];
|
||||||
sprintf(message, "[Map %03i] Failed to open %s for writing!\n", mapID, fileName);
|
sprintf(message, "[Map %03i] Failed to open %s for writing!\n", mapID, fileName.c_str());
|
||||||
perror(message);
|
perror(message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -608,16 +607,17 @@ namespace MMAP
|
|||||||
int lTriCount = meshData.liquidTris.size() / 3;
|
int lTriCount = meshData.liquidTris.size() / 3;
|
||||||
uint8* lTriFlags = meshData.liquidType.getCArray();
|
uint8* lTriFlags = meshData.liquidType.getCArray();
|
||||||
|
|
||||||
const TileConfig tileConfig = TileConfig(m_bigBaseUnit);
|
ResolvedMeshConfig cfg = m_mapBuilder->getConfig().GetConfigForTile(mapID, tileX, tileY);
|
||||||
int TILES_PER_MAP = tileConfig.TILES_PER_MAP;
|
int tilesPerMap = cfg.tilesPerMapEdge;
|
||||||
float BASE_UNIT_DIM = tileConfig.BASE_UNIT_DIM;
|
float baseUnitDim = cfg.baseUnitDim;
|
||||||
rcConfig config = m_mapBuilder->GetMapSpecificConfig(mapID, bmin, bmax, tileConfig);
|
|
||||||
|
rcConfig config = m_mapBuilder->getRecastConfig(cfg, bmin, bmax);
|
||||||
|
|
||||||
// this sets the dimensions of the heightfield - should maybe happen before border padding
|
// this sets the dimensions of the heightfield - should maybe happen before border padding
|
||||||
rcCalcGridSize(config.bmin, config.bmax, config.cs, &config.width, &config.height);
|
rcCalcGridSize(config.bmin, config.bmax, config.cs, &config.width, &config.height);
|
||||||
|
|
||||||
// allocate subregions : tiles
|
// allocate subregions : tiles
|
||||||
Tile* tiles = new Tile[TILES_PER_MAP * TILES_PER_MAP];
|
Tile* tiles = new Tile[tilesPerMap * tilesPerMap];
|
||||||
|
|
||||||
// Initialize per tile config.
|
// Initialize per tile config.
|
||||||
rcConfig tileCfg = config;
|
rcConfig tileCfg = config;
|
||||||
@@ -625,15 +625,16 @@ namespace MMAP
|
|||||||
tileCfg.height = config.tileSize + config.borderSize * 2;
|
tileCfg.height = config.tileSize + config.borderSize * 2;
|
||||||
|
|
||||||
// merge per tile poly and detail meshes
|
// merge per tile poly and detail meshes
|
||||||
rcPolyMesh** pmmerge = new rcPolyMesh*[TILES_PER_MAP * TILES_PER_MAP];
|
rcPolyMesh** pmmerge = new rcPolyMesh*[tilesPerMap * tilesPerMap];
|
||||||
rcPolyMeshDetail** dmmerge = new rcPolyMeshDetail*[TILES_PER_MAP * TILES_PER_MAP];
|
rcPolyMeshDetail** dmmerge = new rcPolyMeshDetail*[tilesPerMap * tilesPerMap];
|
||||||
int nmerge = 0;
|
int nmerge = 0;
|
||||||
|
|
||||||
// build all tiles
|
// build all tiles
|
||||||
for (int y = 0; y < TILES_PER_MAP; ++y)
|
for (int y = 0; y < tilesPerMap; ++y)
|
||||||
{
|
{
|
||||||
for (int x = 0; x < TILES_PER_MAP; ++x)
|
for (int x = 0; x < tilesPerMap; ++x)
|
||||||
{
|
{
|
||||||
Tile& tile = tiles[x + y * TILES_PER_MAP];
|
Tile& tile = tiles[x + y * tilesPerMap];
|
||||||
|
|
||||||
// Calculate the per tile bounding box.
|
// Calculate the per tile bounding box.
|
||||||
tileCfg.bmin[0] = config.bmin[0] + x * float(config.tileSize * config.cs);
|
tileCfg.bmin[0] = config.bmin[0] + x * float(config.tileSize * config.cs);
|
||||||
@@ -790,9 +791,9 @@ namespace MMAP
|
|||||||
params.offMeshConAreas = meshData.offMeshConnectionsAreas.getCArray();
|
params.offMeshConAreas = meshData.offMeshConnectionsAreas.getCArray();
|
||||||
params.offMeshConFlags = meshData.offMeshConnectionsFlags.getCArray();
|
params.offMeshConFlags = meshData.offMeshConnectionsFlags.getCArray();
|
||||||
|
|
||||||
params.walkableHeight = BASE_UNIT_DIM * config.walkableHeight; // agent height
|
params.walkableHeight = baseUnitDim * config.walkableHeight; // agent height
|
||||||
params.walkableRadius = BASE_UNIT_DIM * config.walkableRadius; // agent radius
|
params.walkableRadius = baseUnitDim * config.walkableRadius; // agent radius
|
||||||
params.walkableClimb = BASE_UNIT_DIM * config.walkableClimb; // keep less that walkableHeight (aka agent height)!
|
params.walkableClimb = baseUnitDim * config.walkableClimb; // keep less that walkableHeight (aka agent height)!
|
||||||
params.tileX = (((bmin[0] + bmax[0]) / 2) - navMesh->getParams()->orig[0]) / GRID_SIZE;
|
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;
|
params.tileY = (((bmin[2] + bmax[2]) / 2) - navMesh->getParams()->orig[2]) / GRID_SIZE;
|
||||||
rcVcopy(params.bmin, bmin);
|
rcVcopy(params.bmin, bmin);
|
||||||
@@ -861,13 +862,17 @@ namespace MMAP
|
|||||||
}
|
}
|
||||||
|
|
||||||
// file output
|
// file output
|
||||||
char fileName[255];
|
const std::string fileName = Acore::StringFormat(
|
||||||
sprintf(fileName, "mmaps/%03u%02i%02i.mmtile", mapID, tileY, tileX);
|
TILE_FILE_NAME_FORMAT,
|
||||||
FILE* file = fopen(fileName, "wb");
|
m_mapBuilder->getConfig().DataDirPath(),
|
||||||
|
mapID, tileY, tileX
|
||||||
|
);
|
||||||
|
|
||||||
|
FILE* file = fopen(fileName.c_str(), "wb");
|
||||||
if (!file)
|
if (!file)
|
||||||
{
|
{
|
||||||
char message[1024];
|
char message[1024];
|
||||||
sprintf(message, "[Map %03i] Failed to open %s for writing!\n", mapID, fileName);
|
sprintf(message, "[Map %03i] Failed to open %s for writing!\n", mapID, fileName.c_str());
|
||||||
perror(message);
|
perror(message);
|
||||||
navMesh->removeTile(tileRef, nullptr, nullptr);
|
navMesh->removeTile(tileRef, nullptr, nullptr);
|
||||||
break;
|
break;
|
||||||
@@ -879,6 +884,7 @@ namespace MMAP
|
|||||||
MmapTileHeader header;
|
MmapTileHeader header;
|
||||||
header.usesLiquids = m_terrainBuilder->usesLiquids();
|
header.usesLiquids = m_terrainBuilder->usesLiquids();
|
||||||
header.size = uint32(navDataSize);
|
header.size = uint32(navDataSize);
|
||||||
|
header.recastConfig = cfg.toMMAPTileRecastConfig();
|
||||||
fwrite(&header, sizeof(MmapTileHeader), 1, file);
|
fwrite(&header, sizeof(MmapTileHeader), 1, file);
|
||||||
|
|
||||||
// write data
|
// write data
|
||||||
@@ -899,8 +905,8 @@ namespace MMAP
|
|||||||
v[2] += (unsigned short)config.borderSize;
|
v[2] += (unsigned short)config.borderSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
iv.generateObjFile(mapID, tileX, tileY, meshData);
|
iv.generateObjFile(m_mapBuilder->getConfig().DataDirPath(), mapID, tileX, tileY, meshData);
|
||||||
iv.writeIV(mapID, tileX, tileY);
|
iv.writeIV(m_mapBuilder->getConfig().DataDirPath(), mapID, tileX, tileY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1028,9 +1034,13 @@ namespace MMAP
|
|||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
bool TileBuilder::shouldSkipTile(uint32 mapID, uint32 tileX, uint32 tileY) const
|
bool TileBuilder::shouldSkipTile(uint32 mapID, uint32 tileX, uint32 tileY) const
|
||||||
{
|
{
|
||||||
char fileName[255];
|
const std::string fileName = Acore::StringFormat(
|
||||||
sprintf(fileName, "mmaps/%03u%02i%02i.mmtile", mapID, tileY, tileX);
|
TILE_FILE_NAME_FORMAT,
|
||||||
FILE* file = fopen(fileName, "rb");
|
m_mapBuilder->getConfig().DataDirPath(),
|
||||||
|
mapID, tileY, tileX
|
||||||
|
);
|
||||||
|
|
||||||
|
FILE* file = fopen(fileName.c_str(), "rb");
|
||||||
if (!file)
|
if (!file)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -1046,10 +1056,11 @@ namespace MMAP
|
|||||||
if (header.mmapVersion != MMAP_VERSION)
|
if (header.mmapVersion != MMAP_VERSION)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
const auto desiredRecastConfig = m_mapBuilder->getConfig().GetConfigForTile(mapID, tileX, tileY).toMMAPTileRecastConfig();
|
||||||
|
return header.recastConfig == desiredRecastConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
rcConfig MapBuilder::GetMapSpecificConfig(uint32 mapID, float bmin[3], float bmax[3], const TileConfig &tileConfig) const
|
rcConfig MapBuilder::getRecastConfig(const ResolvedMeshConfig &cfg, float bmin[3], float bmax[3]) const
|
||||||
{
|
{
|
||||||
rcConfig config;
|
rcConfig config;
|
||||||
memset(&config, 0, sizeof(rcConfig));
|
memset(&config, 0, sizeof(rcConfig));
|
||||||
@@ -1058,39 +1069,20 @@ namespace MMAP
|
|||||||
rcVcopy(config.bmax, bmax);
|
rcVcopy(config.bmax, bmax);
|
||||||
|
|
||||||
config.maxVertsPerPoly = DT_VERTS_PER_POLYGON;
|
config.maxVertsPerPoly = DT_VERTS_PER_POLYGON;
|
||||||
config.cs = tileConfig.BASE_UNIT_DIM;
|
config.cs = cfg.cellSizeHorizontal;
|
||||||
config.ch = tileConfig.BASE_UNIT_DIM;
|
config.ch = cfg.cellSizeVertical;
|
||||||
config.walkableSlopeAngle = m_maxWalkableAngle;
|
config.walkableSlopeAngle = cfg.walkableSlopeAngle;
|
||||||
config.tileSize = tileConfig.VERTEX_PER_TILE;
|
config.tileSize = cfg.vertexPerTileEdge;
|
||||||
config.walkableRadius = m_bigBaseUnit ? 1 : 2;
|
config.walkableRadius = cfg.walkableRadius;
|
||||||
config.borderSize = config.walkableRadius + 3;
|
config.borderSize = cfg.walkableRadius + 3;
|
||||||
config.maxEdgeLen = tileConfig.VERTEX_PER_TILE + 1; // anything bigger than tileSize
|
config.maxEdgeLen = cfg.vertexPerTileEdge + 1; // anything bigger than tileSize
|
||||||
config.walkableHeight = m_bigBaseUnit ? 3 : 6;
|
config.walkableHeight = cfg.walkableHeight;
|
||||||
// a value >= 3|6 allows npcs to walk over some fences
|
config.walkableClimb = cfg.walkableClimb;
|
||||||
// a value >= 4|8 allows npcs to walk over all fences
|
|
||||||
config.walkableClimb = m_bigBaseUnit ? 3 : 6;
|
|
||||||
config.minRegionArea = rcSqr(60);
|
config.minRegionArea = rcSqr(60);
|
||||||
config.mergeRegionArea = rcSqr(50);
|
config.mergeRegionArea = rcSqr(50);
|
||||||
config.maxSimplificationError = 1.8f; // eliminates most jagged edges (tiny polygons)
|
config.maxSimplificationError = cfg.maxSimplificationError; // eliminates most jagged edges (tiny polygons)
|
||||||
config.detailSampleDist = config.cs * 16;
|
config.detailSampleDist = config.cs * 16;
|
||||||
config.detailSampleMaxError = config.ch * 1;
|
config.detailSampleMaxError = config.ch * 1;
|
||||||
|
|
||||||
switch (mapID)
|
|
||||||
{
|
|
||||||
// Blade's Edge Arena
|
|
||||||
case 562:
|
|
||||||
// This allows to walk on the ropes to the pillars
|
|
||||||
config.walkableRadius = 0;
|
|
||||||
break;
|
|
||||||
// Blackfathom Deeps
|
|
||||||
case 48:
|
|
||||||
// Reduce the chance to have underground levels
|
|
||||||
config.ch *= 2;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "Config.h"
|
||||||
#include "Optional.h"
|
#include "Optional.h"
|
||||||
#include "TerrainBuilder.h"
|
#include "TerrainBuilder.h"
|
||||||
|
|
||||||
@@ -71,27 +72,6 @@ namespace MMAP
|
|||||||
rcPolyMeshDetail* dmesh{nullptr};
|
rcPolyMeshDetail* dmesh{nullptr};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TileConfig
|
|
||||||
{
|
|
||||||
TileConfig(bool bigBaseUnit)
|
|
||||||
{
|
|
||||||
// these are WORLD UNIT based metrics
|
|
||||||
// this are basic unit dimentions
|
|
||||||
// value have to divide GRID_SIZE(533.3333f) ( aka: 0.5333, 0.2666, 0.3333, 0.1333, etc )
|
|
||||||
BASE_UNIT_DIM = bigBaseUnit ? 0.5333333f : 0.2666666f;
|
|
||||||
|
|
||||||
// All are in UNIT metrics!
|
|
||||||
VERTEX_PER_MAP = int(GRID_SIZE / BASE_UNIT_DIM + 0.5f);
|
|
||||||
VERTEX_PER_TILE = bigBaseUnit ? 40 : 80; // must divide VERTEX_PER_MAP
|
|
||||||
TILES_PER_MAP = VERTEX_PER_MAP / VERTEX_PER_TILE;
|
|
||||||
}
|
|
||||||
|
|
||||||
float BASE_UNIT_DIM;
|
|
||||||
int VERTEX_PER_MAP;
|
|
||||||
int VERTEX_PER_TILE;
|
|
||||||
int TILES_PER_MAP;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct TileInfo
|
struct TileInfo
|
||||||
{
|
{
|
||||||
TileInfo() : m_mapId(uint32(-1)), m_tileX(), m_tileY(), m_navMeshParams() {}
|
TileInfo() : m_mapId(uint32(-1)), m_tileX(), m_tileY(), m_navMeshParams() {}
|
||||||
@@ -109,7 +89,6 @@ namespace MMAP
|
|||||||
public:
|
public:
|
||||||
TileBuilder(MapBuilder* mapBuilder,
|
TileBuilder(MapBuilder* mapBuilder,
|
||||||
bool skipLiquid,
|
bool skipLiquid,
|
||||||
bool bigBaseUnit,
|
|
||||||
bool debugOutput);
|
bool debugOutput);
|
||||||
|
|
||||||
TileBuilder(TileBuilder&&) = default;
|
TileBuilder(TileBuilder&&) = default;
|
||||||
@@ -131,7 +110,6 @@ namespace MMAP
|
|||||||
bool shouldSkipTile(uint32 mapID, uint32 tileX, uint32 tileY) const;
|
bool shouldSkipTile(uint32 mapID, uint32 tileX, uint32 tileY) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_bigBaseUnit;
|
|
||||||
bool m_debugOutput;
|
bool m_debugOutput;
|
||||||
|
|
||||||
MapBuilder* m_mapBuilder;
|
MapBuilder* m_mapBuilder;
|
||||||
@@ -145,13 +123,7 @@ namespace MMAP
|
|||||||
{
|
{
|
||||||
friend class TileBuilder;
|
friend class TileBuilder;
|
||||||
public:
|
public:
|
||||||
MapBuilder(float maxWalkableAngle,
|
MapBuilder(Config* config,
|
||||||
bool skipLiquid,
|
|
||||||
bool skipContinents,
|
|
||||||
bool skipJunkMaps,
|
|
||||||
bool skipBattlegrounds,
|
|
||||||
bool debugOutput,
|
|
||||||
bool bigBaseUnit,
|
|
||||||
int mapid,
|
int mapid,
|
||||||
char const* offMeshFilePath,
|
char const* offMeshFilePath,
|
||||||
unsigned int threads);
|
unsigned int threads);
|
||||||
@@ -166,6 +138,7 @@ namespace MMAP
|
|||||||
// builds list of maps, then builds all of mmap tiles (based on the skip settings)
|
// builds list of maps, then builds all of mmap tiles (based on the skip settings)
|
||||||
void buildMaps(Optional<uint32> mapID);
|
void buildMaps(Optional<uint32> mapID);
|
||||||
|
|
||||||
|
const Config& getConfig() const { return *m_config; }
|
||||||
private:
|
private:
|
||||||
// builds all mmap tiles for the specified map id (ignores skip settings)
|
// builds all mmap tiles for the specified map id (ignores skip settings)
|
||||||
void buildMap(uint32 mapID);
|
void buildMap(uint32 mapID);
|
||||||
@@ -184,7 +157,7 @@ namespace MMAP
|
|||||||
bool isTransportMap(uint32 mapID) const;
|
bool isTransportMap(uint32 mapID) const;
|
||||||
bool isContinentMap(uint32 mapID) const;
|
bool isContinentMap(uint32 mapID) const;
|
||||||
|
|
||||||
rcConfig GetMapSpecificConfig(uint32 mapID, float bmin[3], float bmax[3], const TileConfig &tileConfig) const;
|
rcConfig getRecastConfig(const ResolvedMeshConfig &cfg, float bmin[3], float bmax[3]) const;
|
||||||
|
|
||||||
uint32 percentageDone(uint32 totalTiles, uint32 totalTilesDone) const;
|
uint32 percentageDone(uint32 totalTiles, uint32 totalTilesDone) const;
|
||||||
uint32 currentPercentageDone() const;
|
uint32 currentPercentageDone() const;
|
||||||
@@ -201,10 +174,10 @@ namespace MMAP
|
|||||||
bool m_skipBattlegrounds;
|
bool m_skipBattlegrounds;
|
||||||
bool m_skipLiquid;
|
bool m_skipLiquid;
|
||||||
|
|
||||||
float m_maxWalkableAngle;
|
|
||||||
bool m_bigBaseUnit;
|
|
||||||
int32 m_mapid;
|
int32 m_mapid;
|
||||||
|
|
||||||
|
Config* m_config;
|
||||||
|
|
||||||
std::atomic<uint32> m_totalTiles;
|
std::atomic<uint32> m_totalTiles;
|
||||||
std::atomic<uint32> m_totalTilesProcessed;
|
std::atomic<uint32> m_totalTilesProcessed;
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <boost/dll/runtime_symbol_info.hpp>
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
@@ -35,6 +36,11 @@
|
|||||||
|
|
||||||
namespace MMAP
|
namespace MMAP
|
||||||
{
|
{
|
||||||
|
inline std::string executableDirectoryPath()
|
||||||
|
{
|
||||||
|
return boost::dll::program_location().parent_path().string();
|
||||||
|
}
|
||||||
|
|
||||||
inline bool matchWildcardFilter(const char* filter, const char* str)
|
inline bool matchWildcardFilter(const char* filter, const char* str)
|
||||||
{
|
{
|
||||||
if (!filter || !str)
|
if (!filter || !str)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "Config.h"
|
||||||
#include "MapBuilder.h"
|
#include "MapBuilder.h"
|
||||||
#include "PathCommon.h"
|
#include "PathCommon.h"
|
||||||
#include "Timer.h"
|
#include "Timer.h"
|
||||||
@@ -23,37 +24,34 @@
|
|||||||
|
|
||||||
using namespace MMAP;
|
using namespace MMAP;
|
||||||
|
|
||||||
bool checkDirectories(bool debugOutput)
|
bool checkDirectories(const std::string &dataDirPath, bool debugOutput)
|
||||||
{
|
{
|
||||||
std::vector<std::string> dirFiles;
|
std::vector<std::string> dirFiles;
|
||||||
|
|
||||||
if (getDirContents(dirFiles, "maps") == LISTFILE_DIRECTORY_NOT_FOUND || dirFiles.empty())
|
if (getDirContents(dirFiles, (std::filesystem::path(dataDirPath) / "maps").string()) == LISTFILE_DIRECTORY_NOT_FOUND || dirFiles.empty())
|
||||||
{
|
{
|
||||||
printf("'maps' directory is empty or does not exist\n");
|
printf("'maps' directory is empty or does not exist\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
dirFiles.clear();
|
dirFiles.clear();
|
||||||
if (getDirContents(dirFiles, "vmaps", "*.vmtree") == LISTFILE_DIRECTORY_NOT_FOUND || dirFiles.empty())
|
if (getDirContents(dirFiles, (std::filesystem::path(dataDirPath) / "vmaps").string(), "*.vmtree") == LISTFILE_DIRECTORY_NOT_FOUND || dirFiles.empty())
|
||||||
{
|
{
|
||||||
printf("'vmaps' directory is empty or does not exist\n");
|
printf("'vmaps' directory is empty or does not exist\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
dirFiles.clear();
|
dirFiles.clear();
|
||||||
if (getDirContents(dirFiles, "mmaps") == LISTFILE_DIRECTORY_NOT_FOUND)
|
if (getDirContents(dirFiles, (std::filesystem::path(dataDirPath) / "mmaps").string()) == LISTFILE_DIRECTORY_NOT_FOUND)
|
||||||
{
|
{
|
||||||
return boost::filesystem::create_directory("mmaps");
|
return boost::filesystem::create_directory((std::filesystem::path(dataDirPath) / "mmaps").string());
|
||||||
}
|
}
|
||||||
|
|
||||||
dirFiles.clear();
|
dirFiles.clear();
|
||||||
if (debugOutput)
|
if (debugOutput && getDirContents(dirFiles, (std::filesystem::path(dataDirPath) / "meshes").string()) == LISTFILE_DIRECTORY_NOT_FOUND)
|
||||||
{
|
{
|
||||||
if (getDirContents(dirFiles, "meshes") == LISTFILE_DIRECTORY_NOT_FOUND)
|
printf("'meshes' directory does not exist creating...\n");
|
||||||
{
|
return boost::filesystem::create_directory((std::filesystem::path(dataDirPath) / "meshes").string());
|
||||||
printf("'meshes' directory does not exist (no place to put debugOutput files)\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -63,32 +61,24 @@ bool handleArgs(int argc, char** argv,
|
|||||||
int& mapnum,
|
int& mapnum,
|
||||||
int& tileX,
|
int& tileX,
|
||||||
int& tileY,
|
int& tileY,
|
||||||
float& maxAngle,
|
std::string& configFilePath,
|
||||||
bool& skipLiquid,
|
|
||||||
bool& skipContinents,
|
|
||||||
bool& skipJunkMaps,
|
|
||||||
bool& skipBattlegrounds,
|
|
||||||
bool& debugOutput,
|
|
||||||
bool& silent,
|
bool& silent,
|
||||||
bool& bigBaseUnit,
|
|
||||||
char*& offMeshInputPath,
|
char*& offMeshInputPath,
|
||||||
char*& file,
|
char*& file,
|
||||||
unsigned int& threads)
|
unsigned int& threads)
|
||||||
{
|
{
|
||||||
|
bool hasCustomConfigPath = false;
|
||||||
char* param = nullptr;
|
char* param = nullptr;
|
||||||
for (int i = 1; i < argc; ++i)
|
for (int i = 1; i < argc; ++i)
|
||||||
{
|
{
|
||||||
if (strcmp(argv[i], "--maxAngle") == 0)
|
if (strcmp(argv[i], "--config") == 0)
|
||||||
{
|
{
|
||||||
param = argv[++i];
|
param = argv[++i];
|
||||||
if (!param)
|
if (!param)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
float maxangle = atof(param);
|
hasCustomConfigPath = true;
|
||||||
if (maxangle <= 90.f && maxangle >= 45.f)
|
configFilePath = param;
|
||||||
maxAngle = maxangle;
|
|
||||||
else
|
|
||||||
printf("invalid option for '--maxAngle', using default\n");
|
|
||||||
}
|
}
|
||||||
else if (strcmp(argv[i], "--threads") == 0)
|
else if (strcmp(argv[i], "--threads") == 0)
|
||||||
{
|
{
|
||||||
@@ -126,88 +116,10 @@ bool handleArgs(int argc, char** argv,
|
|||||||
return false;
|
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)
|
else if (strcmp(argv[i], "--silent") == 0)
|
||||||
{
|
{
|
||||||
silent = true;
|
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)
|
else if (strcmp(argv[i], "--offMeshInput") == 0)
|
||||||
{
|
{
|
||||||
param = argv[++i];
|
param = argv[++i];
|
||||||
@@ -229,6 +141,23 @@ bool handleArgs(int argc, char** argv,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!hasCustomConfigPath)
|
||||||
|
{
|
||||||
|
FILE* f = fopen(configFilePath.c_str(), "r");
|
||||||
|
if (!f)
|
||||||
|
{
|
||||||
|
auto execRelPath = std::filesystem::path(executableDirectoryPath())/configFilePath;
|
||||||
|
f = fopen(execRelPath.string().c_str(), "r");
|
||||||
|
if (!f)
|
||||||
|
{
|
||||||
|
printf("Failed to load configuration. Ensure that 'mmaps-config.yaml' exists in the current directory or specify its path using the --config option.'\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
configFilePath = execRelPath.string();
|
||||||
|
}
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,42 +173,36 @@ int main(int argc, char** argv)
|
|||||||
unsigned int threads = std::thread::hardware_concurrency();
|
unsigned int threads = std::thread::hardware_concurrency();
|
||||||
int mapnum = -1;
|
int mapnum = -1;
|
||||||
int tileX = -1, tileY = -1;
|
int tileX = -1, tileY = -1;
|
||||||
float maxAngle = 60.0f;
|
bool silent = false;
|
||||||
bool skipLiquid = false,
|
|
||||||
skipContinents = false,
|
|
||||||
skipJunkMaps = true,
|
|
||||||
skipBattlegrounds = false,
|
|
||||||
debugOutput = false,
|
|
||||||
silent = false,
|
|
||||||
bigBaseUnit = false;
|
|
||||||
char* offMeshInputPath = nullptr;
|
char* offMeshInputPath = nullptr;
|
||||||
char* file = nullptr;
|
char* file = nullptr;
|
||||||
|
std::string configFilePath = "mmaps-config.yaml";
|
||||||
bool validParam = handleArgs(argc, argv, mapnum,
|
bool validParam = handleArgs(argc, argv, mapnum,
|
||||||
tileX, tileY, maxAngle,
|
tileX, tileY, configFilePath, silent, offMeshInputPath, file, threads);
|
||||||
skipLiquid, skipContinents, skipJunkMaps, skipBattlegrounds,
|
|
||||||
debugOutput, silent, bigBaseUnit, offMeshInputPath, file, threads);
|
|
||||||
|
|
||||||
if (!validParam)
|
if (!validParam)
|
||||||
return silent ? -1 : finish("You have specified invalid parameters", -1);
|
return silent ? -1 : finish("You have specified invalid parameters", -1);
|
||||||
|
|
||||||
if (mapnum == -1 && debugOutput)
|
auto config = Config::FromFile(configFilePath);
|
||||||
|
if (!config)
|
||||||
|
return silent ? -1 : finish("Failed to load configuration. Ensure that 'mmaps-config.yaml' exists in the current directory or specify its path using the --config option.", -1);
|
||||||
|
|
||||||
|
if (mapnum == -1 && config->IsDebugOutputEnabled())
|
||||||
{
|
{
|
||||||
if (silent)
|
if (silent)
|
||||||
return -2;
|
return -2;
|
||||||
|
|
||||||
printf("You have specifed debug output, but didn't specify a map to generate.\n");
|
printf("You have specified debug output, but didn't specify a map to generate.\n");
|
||||||
printf("This will generate debug output for ALL maps.\n");
|
printf("This will generate debug output for ALL maps.\n");
|
||||||
printf("Are you sure you want to continue? (y/n) ");
|
printf("Are you sure you want to continue? (y/n) ");
|
||||||
if (getchar() != 'y')
|
if (getchar() != 'y')
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!checkDirectories(debugOutput))
|
if (!checkDirectories(config->DataDirPath(), config->IsDebugOutputEnabled()))
|
||||||
return silent ? -3 : finish("Press ENTER to close...", -3);
|
return silent ? -3 : finish("Press ENTER to close...", -3);
|
||||||
|
|
||||||
MapBuilder builder(maxAngle, skipLiquid, skipContinents, skipJunkMaps,
|
MapBuilder builder(&config.value(), mapnum, offMeshInputPath, threads);
|
||||||
skipBattlegrounds, debugOutput, bigBaseUnit, mapnum, offMeshInputPath, threads);
|
|
||||||
|
|
||||||
uint32 start = getMSTime();
|
uint32 start = getMSTime();
|
||||||
if (file)
|
if (file)
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
|
#include "StringFormat.h"
|
||||||
|
|
||||||
// ******************************************
|
// ******************************************
|
||||||
// Map file format defines
|
// Map file format defines
|
||||||
// ******************************************
|
// ******************************************
|
||||||
@@ -80,10 +82,17 @@ struct map_liquidHeader
|
|||||||
|
|
||||||
namespace MMAP
|
namespace MMAP
|
||||||
{
|
{
|
||||||
|
static char const* const MAP_FILE_NAME_FORMAT = "{}/{:03}{:02}{:02}.map";
|
||||||
|
|
||||||
uint32 const MAP_VERSION_MAGIC = 9;
|
uint32 const MAP_VERSION_MAGIC = 9;
|
||||||
|
|
||||||
TerrainBuilder::TerrainBuilder(bool skipLiquid) : m_skipLiquid (skipLiquid) { }
|
TerrainBuilder::TerrainBuilder(const std::string &dataDirPath, bool skipLiquid) :
|
||||||
|
m_skipLiquid (skipLiquid),
|
||||||
|
m_mapsPath((std::filesystem::path(dataDirPath) / "maps").string()),
|
||||||
|
m_vmapsPath((std::filesystem::path(dataDirPath) / "vmaps").string())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
TerrainBuilder::~TerrainBuilder() = default;
|
TerrainBuilder::~TerrainBuilder() = default;
|
||||||
|
|
||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
@@ -134,10 +143,13 @@ namespace MMAP
|
|||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
bool TerrainBuilder::loadMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData, Spot portion)
|
bool TerrainBuilder::loadMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData, Spot portion)
|
||||||
{
|
{
|
||||||
char mapFileName[255];
|
const std::string mapFileName = Acore::StringFormat(
|
||||||
sprintf(mapFileName, "maps/%03u%02u%02u.map", mapID, tileY, tileX);
|
MAP_FILE_NAME_FORMAT,
|
||||||
|
m_mapsPath,
|
||||||
|
mapID, tileY, tileX
|
||||||
|
);
|
||||||
|
|
||||||
FILE* mapFile = fopen(mapFileName, "rb");
|
FILE* mapFile = fopen(mapFileName.c_str(), "rb");
|
||||||
if (!mapFile)
|
if (!mapFile)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -146,7 +158,7 @@ namespace MMAP
|
|||||||
fheader.versionMagic != MAP_VERSION_MAGIC)
|
fheader.versionMagic != MAP_VERSION_MAGIC)
|
||||||
{
|
{
|
||||||
fclose(mapFile);
|
fclose(mapFile);
|
||||||
printf("%s is the wrong version, please extract new .map files\n", mapFileName);
|
printf("%s is the wrong version, please extract new .map files\n", mapFileName.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -665,7 +677,7 @@ namespace MMAP
|
|||||||
bool TerrainBuilder::loadVMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData)
|
bool TerrainBuilder::loadVMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData)
|
||||||
{
|
{
|
||||||
IVMapMgr* vmapMgr = new VMapMgr2();
|
IVMapMgr* vmapMgr = new VMapMgr2();
|
||||||
int result = vmapMgr->loadMap("vmaps", mapID, tileX, tileY);
|
int result = vmapMgr->loadMap(m_vmapsPath.c_str(), mapID, tileX, tileY);
|
||||||
bool retval = false;
|
bool retval = false;
|
||||||
|
|
||||||
do
|
do
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ namespace MMAP
|
|||||||
class TerrainBuilder
|
class TerrainBuilder
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
TerrainBuilder(bool skipLiquid);
|
TerrainBuilder(const std::string &mapsPath, bool skipLiquid);
|
||||||
~TerrainBuilder();
|
~TerrainBuilder();
|
||||||
|
|
||||||
TerrainBuilder(const TerrainBuilder& tb) = delete;
|
TerrainBuilder(const TerrainBuilder& tb) = delete;
|
||||||
@@ -121,6 +121,9 @@ namespace MMAP
|
|||||||
|
|
||||||
/// Get the liquid type for a specific position
|
/// Get the liquid type for a specific position
|
||||||
uint8 getLiquidType(int square, const uint8 liquid_type[16][16]);
|
uint8 getLiquidType(int square, const uint8 liquid_type[16][16]);
|
||||||
|
|
||||||
|
std::string m_mapsPath;
|
||||||
|
std::string m_vmapsPath;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
145
src/tools/mmaps_generator/mmaps-config.yaml
Normal file
145
src/tools/mmaps_generator/mmaps-config.yaml
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
mmapsConfig:
|
||||||
|
skipLiquid: false
|
||||||
|
skipContinents: false
|
||||||
|
skipJunkMaps: true
|
||||||
|
skipBattlegrounds: false
|
||||||
|
|
||||||
|
# Path to the directory containing navigation data files.
|
||||||
|
# This directory should contain the "maps" and "vmaps" folders,
|
||||||
|
# and is also where the "mmaps" folder will be created or located.
|
||||||
|
dataDir: "./"
|
||||||
|
|
||||||
|
meshSettings:
|
||||||
|
# Here we have global config for recast navigation.
|
||||||
|
# It's possible to override these data on map or tile level (see mapsOverrides).
|
||||||
|
|
||||||
|
# Maximum slope angle (in degrees) NPCs can walk on.
|
||||||
|
# Surfaces steeper than this will be considered unwalkable.
|
||||||
|
walkableSlopeAngle: 60
|
||||||
|
|
||||||
|
# --- Cell Size Calculation ---
|
||||||
|
# Many parameters below are defined in "cell units".
|
||||||
|
# In RecastDemo, you often work with world units instead of cell units.
|
||||||
|
# By default, these cell units are converted to world units using the formula:
|
||||||
|
#
|
||||||
|
# cellSize = MMAP::GRID_SIZE / (verticesPerMapEdge - 1)
|
||||||
|
#
|
||||||
|
# Where:
|
||||||
|
# MMAP::GRID_SIZE = 533.3333f (the size of one map tile in world units)
|
||||||
|
# verticesPerMapEdge = number of vertices along one edge of the full map grid
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# If verticesPerMapEdge = 2000, then:
|
||||||
|
# cellSize ≈ 533.3333 / (2000 - 1) ≈ 0.2667 world units per cell
|
||||||
|
#
|
||||||
|
# To convert a value from cell units to world units (e.g., walkableClimb),
|
||||||
|
# multiply by cellSize. For example, a walkableClimb of 6 corresponds to:
|
||||||
|
# 6 * 0.2667 ≈ 1.6 world units
|
||||||
|
|
||||||
|
# Minimum ceiling height (in cell units) NPCs need to pass under an obstacle.
|
||||||
|
# Controls how much vertical clearance is required.
|
||||||
|
# To convert to world units, multiply by cellSize (see "Cell Size Calculation").
|
||||||
|
walkableHeight: 6
|
||||||
|
|
||||||
|
# Maximum height difference (in cell units) NPCs can step up or down.
|
||||||
|
# Higher values allow walking over fences, ledges, or steps.
|
||||||
|
# To convert to world units, multiply by cellSize (see "Cell Size Calculation").
|
||||||
|
#
|
||||||
|
# Vanilla WotLK uses 6, which allows creatures to "jump" over fences.
|
||||||
|
# Classic WotLK uses 4, which forces creatures to walk around fences.
|
||||||
|
walkableClimb: 6
|
||||||
|
|
||||||
|
# Minimum distance (in cell units) around walkable surfaces.
|
||||||
|
# Helps prevent NPCs from clipping into walls and narrow gaps.
|
||||||
|
# To convert to world units, multiply by cellSize (see "Cell Size Calculation").
|
||||||
|
walkableRadius: 2
|
||||||
|
|
||||||
|
# Number of vertices along one edge of the entire map's navmesh grid.
|
||||||
|
# Higher values increase mesh resolution but also CPU/memory usage.
|
||||||
|
verticesPerMapEdge: 2000
|
||||||
|
|
||||||
|
# Number of vertices along one edge of each tile chunk.
|
||||||
|
# Must divide (vertexPerMapEdge - 1) evenly for seamless tiles.
|
||||||
|
# A higher vertex count per tile means fewer total tiles,
|
||||||
|
# reducing runtime work to load, unload, and manage tiles.
|
||||||
|
verticesPerTileEdge: 80
|
||||||
|
|
||||||
|
# Tolerance for how much a polygon can deviate from the original geometry when simplified.
|
||||||
|
# Higher values produce simpler (faster) meshes but can reduce accuracy.
|
||||||
|
maxSimplificationError: 1.8
|
||||||
|
|
||||||
|
# You can override any global parameter for a specific map by specifying its map ID.
|
||||||
|
# Inside each map override, you can also override parameters per individual tile,
|
||||||
|
# identified by a string "tileX,tileY" (coordinates).
|
||||||
|
#
|
||||||
|
# Overrides cascade: global settings → map overrides → tile overrides.
|
||||||
|
# For example:
|
||||||
|
#
|
||||||
|
# mapsOverrides:
|
||||||
|
# "0": # Map ID 0 overrides
|
||||||
|
# walkableRadius: 5 # Override global climb height for entire map 0
|
||||||
|
#
|
||||||
|
# tilesOverrides:
|
||||||
|
# "50,70": # Tile at coordinates (50,70) on map 0
|
||||||
|
# walkableSlopeAngle: 70 # Override slope angle locally just here
|
||||||
|
# walkableClimb: 4 # Also override climb height for this tile only
|
||||||
|
#
|
||||||
|
# "51,71":
|
||||||
|
# walkableClimb: 3 # Override climb height for tile (51,71)
|
||||||
|
#
|
||||||
|
# "48,32":
|
||||||
|
# walkableClimb: 1 # Even smaller climb for tile (48,32)
|
||||||
|
#
|
||||||
|
# "1": # Map ID 1 overrides example
|
||||||
|
# walkableHeight: 8 # Increase clearance for whole map 1
|
||||||
|
#
|
||||||
|
# tilesOverrides:
|
||||||
|
# "100,100":
|
||||||
|
# maxSimplificationError: 2.5 # Looser mesh simplification for this tile only
|
||||||
|
#
|
||||||
|
# "101,101":
|
||||||
|
# walkableRadius: 1 # Smaller NPC radius here for tight corridors
|
||||||
|
#
|
||||||
|
# This approach allows very fine-grained control of navigation mesh parameters
|
||||||
|
# on a per-map and per-tile basis, optimizing pathfinding quality and performance.
|
||||||
|
#
|
||||||
|
# All parameters defined globally are eligible for override.
|
||||||
|
# Just specify the parameter name and new value in the override section.
|
||||||
|
mapsOverrides:
|
||||||
|
"562": # Blade's Edge Arena
|
||||||
|
walkableRadius: 0 # This allows walking on the ropes to the pillars
|
||||||
|
|
||||||
|
"48": # Blackfathom Deeps
|
||||||
|
cellSizeVertical: 0.5334 # ch*2 = 0.2667 * 2 ≈ 0.5334. Reduce the chance to have underground levels.
|
||||||
|
|
||||||
|
"529": # Arathi Basin
|
||||||
|
tilesOverrides:
|
||||||
|
"30,29": # Lumber Mill
|
||||||
|
# Make sure that Fear will not drop players rom cliff -
|
||||||
|
# https://github.com/azerothcore/azerothcore-wotlk/pull/22462#issuecomment-3067024680
|
||||||
|
walkableSlopeAngle: 45
|
||||||
|
|
||||||
|
# debugOutput generates debug files in the `meshes` directory for use with RecastDemo.
|
||||||
|
# This is useful for inspecting and debugging mmap generation visually.
|
||||||
|
#
|
||||||
|
# My workflow:
|
||||||
|
# 1. Install RecastDemo. I'm building it from the source of this fork: https://github.com/jackpoz/recastnavigation
|
||||||
|
# 2. In-game, move your character to the area you want to debug.
|
||||||
|
# 3. Type `.mmap loc` in chat. This will output:
|
||||||
|
# - The current tile file name (e.g., `04832.mmtile`)
|
||||||
|
# - The Recast config values used to generate that tile
|
||||||
|
# 4. Enable `debugOutput` and regenerate mmaps (preferably just the tile from step 3).
|
||||||
|
# - To regenerate only one tile, delete it from the `mmaps` folder.
|
||||||
|
# 5. After generation, you will find debug files in the `meshes` folder, including an OBJ file (e.g., `map0004832.obj`)
|
||||||
|
# 6. Copy these debug files to the `Meshes` folder used by RecastDemo.
|
||||||
|
# - RecastDemo expects this folder to be in the same directory as its executable.
|
||||||
|
# 7. In RecastDemo:
|
||||||
|
# - Click "Input Mesh" and select the `.obj` file
|
||||||
|
# - Choose "Solo Mesh" in the Sample selector
|
||||||
|
# 8. (Optional) Reuse the Recast config values from step 3:
|
||||||
|
# - `cellSizeHorizontal` → "Cell Size"
|
||||||
|
# - `walkableSlopeAngle` → "Max Slope"
|
||||||
|
# - `walkableClimb` → "Max Climb"
|
||||||
|
# - and so on
|
||||||
|
# 9. Scroll to the bottom of RecastDemo UI and press "Build" to generate the navigation mesh
|
||||||
|
debugOutput: false
|
||||||
Reference in New Issue
Block a user