mirror of
https://github.com/azerothcore/azerothcore-wotlk.git
synced 2025-11-10 20:34:53 +08:00
feat(Core/mmaps): Add configuration file for mmaps-generator. (#22506)
This commit is contained in:
parent
10d5a3c553
commit
f2f31acdcf
1
deps/CMakeLists.txt
vendored
1
deps/CMakeLists.txt
vendored
@ -45,4 +45,5 @@ endif()
|
||||
if (BUILD_TOOLS_MAPS)
|
||||
add_subdirectory(bzip2)
|
||||
add_subdirectory(libmpq)
|
||||
add_subdirectory(fkYAML)
|
||||
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.
|
||||
https://github.com/fmtlib/fmt
|
||||
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
|
||||
{
|
||||
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()
|
||||
{
|
||||
|
||||
@ -39,6 +39,9 @@ inline void dtCustomFree(void* ptr)
|
||||
// move map related classes
|
||||
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, dtNavMeshQuery*> NavMeshQuerySet;
|
||||
|
||||
|
||||
@ -26,7 +26,40 @@
|
||||
#define SIZE_OF_GRIDS 533.3333f
|
||||
|
||||
#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
|
||||
{
|
||||
@ -37,17 +70,20 @@ struct MmapTileHeader
|
||||
char usesLiquids{true};
|
||||
char padding[3] {};
|
||||
|
||||
MmapTileRecastConfig recastConfig;
|
||||
|
||||
MmapTileHeader() : dtVersion(DT_NAVMESH_VERSION) { }
|
||||
};
|
||||
|
||||
// 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) +
|
||||
sizeof(MmapTileHeader::dtVersion) +
|
||||
sizeof(MmapTileHeader::mmapVersion) +
|
||||
sizeof(MmapTileHeader::size) +
|
||||
sizeof(MmapTileHeader::usesLiquids) +
|
||||
sizeof(MmapTileHeader::padding)), "MmapTileHeader has uninitialized padding fields");
|
||||
sizeof(MmapTileHeader::padding)+
|
||||
sizeof(MmapTileRecastConfig)), "MmapTileHeader has uninitialized padding fields");
|
||||
|
||||
enum NavTerrain
|
||||
{
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
#include "GridNotifiers.h"
|
||||
#include "GridNotifiersImpl.h"
|
||||
#include "MMapFactory.h"
|
||||
#include "MMapMgr.h"
|
||||
#include "Map.h"
|
||||
#include "PathGenerator.h"
|
||||
#include "Player.h"
|
||||
@ -136,6 +137,40 @@ public:
|
||||
GridCoord const gridCoord = Acore::ComputeGridCoord(player->GetPositionX(), player->GetPositionY());
|
||||
|
||||
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);
|
||||
|
||||
// calculate navmesh tile location
|
||||
|
||||
@ -140,7 +140,8 @@ foreach(TOOL_NAME ${TOOLS_BUILD_LIST})
|
||||
mpq
|
||||
zlib
|
||||
Recast
|
||||
g3dlib)
|
||||
g3dlib
|
||||
fkYAML)
|
||||
endif()
|
||||
|
||||
unset(TOOL_PUBLIC_INCLUDES)
|
||||
@ -170,4 +171,8 @@ foreach(TOOL_NAME ${TOOLS_BUILD_LIST})
|
||||
elseif (WIN32)
|
||||
install(TARGETS ${TOOL_PROJECT_NAME} DESTINATION "${CMAKE_INSTALL_PREFIX}")
|
||||
endif()
|
||||
|
||||
if (${TOOL_PROJECT_NAME} STREQUAL "mmaps_generator")
|
||||
install(FILES ${SOURCE_TOOL_PATH}/mmaps-config.yaml DESTINATION bin)
|
||||
endif()
|
||||
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
|
||||
|
||||
--config [file.*] The path the yaml config file
|
||||
Default: "mmaps-config.yaml"
|
||||
|
||||
--threads [#] Max number of threads used by the generator
|
||||
Default: 3
|
||||
|
||||
@ -11,39 +14,6 @@ Generator command line args
|
||||
--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 [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
|
||||
seperate number with a comma ','
|
||||
must specify a map number (see below)
|
||||
@ -58,9 +28,6 @@ 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
|
||||
|
||||
|
||||
@ -16,6 +16,8 @@
|
||||
*/
|
||||
|
||||
#include "IntermediateValues.h"
|
||||
#include <string>
|
||||
#include "StringFormat.h"
|
||||
|
||||
namespace MMAP
|
||||
{
|
||||
@ -28,15 +30,15 @@ namespace MMAP
|
||||
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];
|
||||
sprintf(tileString, "[%02u,%02u]: ", tileX, tileY);
|
||||
|
||||
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) \
|
||||
do { \
|
||||
@ -198,16 +200,19 @@ namespace MMAP
|
||||
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];
|
||||
sprintf(objFileName, "meshes/map%03u%02u%02u.obj", mapID, tileY, tileX);
|
||||
std::string objFileName = Acore::StringFormat(
|
||||
"{}/meshes/map{:03}{:02}{:02}.obj",
|
||||
dataPath,
|
||||
mapID, tileY, tileX
|
||||
);
|
||||
|
||||
FILE* objFile = fopen(objFileName, "wb");
|
||||
FILE* objFile = fopen(objFileName.c_str(), "wb");
|
||||
if (!objFile)
|
||||
{
|
||||
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);
|
||||
return;
|
||||
}
|
||||
@ -237,13 +242,17 @@ namespace MMAP
|
||||
sprintf(tileString, "[%02u,%02u]: ", tileY, tileX);
|
||||
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)
|
||||
{
|
||||
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);
|
||||
return;
|
||||
}
|
||||
@ -252,12 +261,17 @@ namespace MMAP
|
||||
fwrite(&b, sizeof(char), 1, objFile);
|
||||
fclose(objFile);
|
||||
|
||||
sprintf(objFileName, "meshes/%03u%02u%02u.mesh", mapID, tileY, tileX);
|
||||
objFile = fopen(objFileName, "wb");
|
||||
objFileName = Acore::StringFormat(
|
||||
"{}/meshes/{:03}{:02}{:02}.mesh",
|
||||
dataPath,
|
||||
mapID, tileY, tileX
|
||||
);
|
||||
|
||||
objFile = fopen(objFileName.c_str(), "wb");
|
||||
if (!objFile)
|
||||
{
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@ namespace MMAP
|
||||
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 rcCompactHeightfield* chf);
|
||||
@ -43,7 +43,7 @@ namespace MMAP
|
||||
void debugWrite(FILE* file, const rcPolyMesh* 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
|
||||
|
||||
@ -16,28 +16,28 @@
|
||||
*/
|
||||
|
||||
#include "MapBuilder.h"
|
||||
#include <DetourCommon.h>
|
||||
#include <DetourNavMesh.h>
|
||||
#include <DetourNavMeshBuilder.h>
|
||||
#include "IntermediateValues.h"
|
||||
#include "MapDefines.h"
|
||||
#include "MapTree.h"
|
||||
#include "MMapMgr.h"
|
||||
#include "ModelInstance.h"
|
||||
#include "PathCommon.h"
|
||||
#include "StringFormat.h"
|
||||
#include "VMapMgr2.h"
|
||||
#include <DetourCommon.h>
|
||||
#include <DetourNavMesh.h>
|
||||
#include <DetourNavMeshBuilder.h>
|
||||
|
||||
namespace MMAP
|
||||
{
|
||||
TileBuilder::TileBuilder(MapBuilder* mapBuilder, bool skipLiquid, bool bigBaseUnit, bool debugOutput) :
|
||||
m_bigBaseUnit(bigBaseUnit),
|
||||
TileBuilder::TileBuilder(MapBuilder* mapBuilder, bool skipLiquid, bool debugOutput) :
|
||||
m_debugOutput(debugOutput),
|
||||
m_mapBuilder(mapBuilder),
|
||||
m_terrainBuilder(nullptr),
|
||||
m_workerThread(&TileBuilder::WorkerThread, this),
|
||||
m_rcContext(nullptr)
|
||||
{
|
||||
m_terrainBuilder = new TerrainBuilder(skipLiquid);
|
||||
m_terrainBuilder = new TerrainBuilder(m_mapBuilder->getConfig().DataDirPath(), skipLiquid);
|
||||
m_rcContext = new rcContext(false);
|
||||
}
|
||||
|
||||
@ -55,26 +55,22 @@ namespace MMAP
|
||||
m_workerThread.join();
|
||||
}
|
||||
|
||||
MapBuilder::MapBuilder(float maxWalkableAngle, bool skipLiquid,
|
||||
bool skipContinents, bool skipJunkMaps, bool skipBattlegrounds,
|
||||
bool debugOutput, bool bigBaseUnit, int mapid, const char* offMeshFilePath, unsigned int threads) :
|
||||
|
||||
m_debugOutput (debugOutput),
|
||||
MapBuilder::MapBuilder(Config* config, int mapid, const char* offMeshFilePath, unsigned int threads) :
|
||||
m_config (config),
|
||||
m_debugOutput (config->IsDebugOutputEnabled()),
|
||||
m_offMeshFilePath (offMeshFilePath),
|
||||
m_threads (threads),
|
||||
m_skipContinents (skipContinents),
|
||||
m_skipJunkMaps (skipJunkMaps),
|
||||
m_skipBattlegrounds (skipBattlegrounds),
|
||||
m_skipLiquid (skipLiquid),
|
||||
m_maxWalkableAngle (maxWalkableAngle),
|
||||
m_bigBaseUnit (bigBaseUnit),
|
||||
m_skipContinents (config->ShouldSkipContinents()),
|
||||
m_skipJunkMaps (config->ShouldSkipJunkMaps()),
|
||||
m_skipBattlegrounds (config->ShouldSkipBattlegrounds()),
|
||||
m_skipLiquid (config->ShouldSkipLiquid()),
|
||||
m_mapid (mapid),
|
||||
m_totalTiles (0u),
|
||||
m_totalTilesProcessed(0u),
|
||||
|
||||
_cancelationToken (false)
|
||||
{
|
||||
m_terrainBuilder = new TerrainBuilder(skipLiquid);
|
||||
m_terrainBuilder = new TerrainBuilder(config->DataDirPath(), config->ShouldSkipLiquid());
|
||||
|
||||
m_rcContext = new rcContext(false);
|
||||
|
||||
@ -105,7 +101,7 @@ namespace MMAP
|
||||
char filter[12];
|
||||
|
||||
printf("Discovering maps... ");
|
||||
getDirContents(files, "maps");
|
||||
getDirContents(files, m_config->MapsPath());
|
||||
for (auto & file : files)
|
||||
{
|
||||
mapID = uint32(atoi(file.substr(0, file.size() - 8).c_str()));
|
||||
@ -117,7 +113,7 @@ namespace MMAP
|
||||
}
|
||||
|
||||
files.clear();
|
||||
getDirContents(files, "vmaps", "*.vmtree");
|
||||
getDirContents(files, m_config->VMapsPath(), "*.vmtree");
|
||||
for (auto & file : files)
|
||||
{
|
||||
mapID = uint32(atoi(file.substr(0, file.size() - 7).c_str()));
|
||||
@ -138,7 +134,7 @@ namespace MMAP
|
||||
|
||||
sprintf(filter, "%03u*.vmtile", mapID);
|
||||
files.clear();
|
||||
getDirContents(files, "vmaps", filter);
|
||||
getDirContents(files, m_config->VMapsPath(), filter);
|
||||
for (auto & file : files)
|
||||
{
|
||||
fsize = file.size();
|
||||
@ -153,7 +149,7 @@ namespace MMAP
|
||||
|
||||
sprintf(filter, "%03u*", mapID);
|
||||
files.clear();
|
||||
getDirContents(files, "maps", filter);
|
||||
getDirContents(files, m_config->MapsPath(), filter);
|
||||
for (auto & file : files)
|
||||
{
|
||||
fsize = file.size();
|
||||
@ -209,7 +205,7 @@ namespace MMAP
|
||||
|
||||
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)
|
||||
@ -367,7 +363,7 @@ namespace MMAP
|
||||
getTileBounds(tileX, tileY, data.solidVerts.getCArray(), data.solidVerts.size() / 3, bmin, bmax);
|
||||
|
||||
// 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);
|
||||
fclose(file);
|
||||
}
|
||||
@ -385,7 +381,7 @@ namespace MMAP
|
||||
|
||||
/// @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);
|
||||
dtFreeNavMesh(navMesh);
|
||||
|
||||
@ -567,15 +563,18 @@ namespace MMAP
|
||||
return;
|
||||
}
|
||||
|
||||
char fileName[25];
|
||||
sprintf(fileName, "mmaps/%03u.mmap", mapID);
|
||||
const std::string fileName = Acore::StringFormat(
|
||||
MAP_FILE_NAME_FORMAT,
|
||||
m_config->DataDirPath(),
|
||||
mapID
|
||||
);
|
||||
|
||||
FILE* file = fopen(fileName, "wb");
|
||||
FILE* file = fopen(fileName.c_str(), "wb");
|
||||
if (!file)
|
||||
{
|
||||
dtFreeNavMesh(navMesh);
|
||||
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);
|
||||
return;
|
||||
}
|
||||
@ -608,16 +607,17 @@ namespace MMAP
|
||||
int lTriCount = meshData.liquidTris.size() / 3;
|
||||
uint8* lTriFlags = meshData.liquidType.getCArray();
|
||||
|
||||
const TileConfig tileConfig = TileConfig(m_bigBaseUnit);
|
||||
int TILES_PER_MAP = tileConfig.TILES_PER_MAP;
|
||||
float BASE_UNIT_DIM = tileConfig.BASE_UNIT_DIM;
|
||||
rcConfig config = m_mapBuilder->GetMapSpecificConfig(mapID, bmin, bmax, tileConfig);
|
||||
ResolvedMeshConfig cfg = m_mapBuilder->getConfig().GetConfigForTile(mapID, tileX, tileY);
|
||||
int tilesPerMap = cfg.tilesPerMapEdge;
|
||||
float baseUnitDim = cfg.baseUnitDim;
|
||||
|
||||
rcConfig config = m_mapBuilder->getRecastConfig(cfg, bmin, bmax);
|
||||
|
||||
// this sets the dimensions of the heightfield - should maybe happen before border padding
|
||||
rcCalcGridSize(config.bmin, config.bmax, config.cs, &config.width, &config.height);
|
||||
|
||||
// allocate subregions : tiles
|
||||
Tile* tiles = new Tile[TILES_PER_MAP * TILES_PER_MAP];
|
||||
Tile* tiles = new Tile[tilesPerMap * tilesPerMap];
|
||||
|
||||
// Initialize per tile config.
|
||||
rcConfig tileCfg = config;
|
||||
@ -625,15 +625,16 @@ namespace MMAP
|
||||
tileCfg.height = config.tileSize + config.borderSize * 2;
|
||||
|
||||
// merge per tile poly and detail meshes
|
||||
rcPolyMesh** pmmerge = new rcPolyMesh*[TILES_PER_MAP * TILES_PER_MAP];
|
||||
rcPolyMeshDetail** dmmerge = new rcPolyMeshDetail*[TILES_PER_MAP * TILES_PER_MAP];
|
||||
rcPolyMesh** pmmerge = new rcPolyMesh*[tilesPerMap * tilesPerMap];
|
||||
rcPolyMeshDetail** dmmerge = new rcPolyMeshDetail*[tilesPerMap * tilesPerMap];
|
||||
int nmerge = 0;
|
||||
|
||||
// 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.
|
||||
tileCfg.bmin[0] = config.bmin[0] + x * float(config.tileSize * config.cs);
|
||||
@ -790,9 +791,9 @@ namespace MMAP
|
||||
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.walkableHeight = baseUnitDim * config.walkableHeight; // agent height
|
||||
params.walkableRadius = baseUnitDim * config.walkableRadius; // agent radius
|
||||
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.tileY = (((bmin[2] + bmax[2]) / 2) - navMesh->getParams()->orig[2]) / GRID_SIZE;
|
||||
rcVcopy(params.bmin, bmin);
|
||||
@ -861,13 +862,17 @@ namespace MMAP
|
||||
}
|
||||
|
||||
// file output
|
||||
char fileName[255];
|
||||
sprintf(fileName, "mmaps/%03u%02i%02i.mmtile", mapID, tileY, tileX);
|
||||
FILE* file = fopen(fileName, "wb");
|
||||
const std::string fileName = Acore::StringFormat(
|
||||
TILE_FILE_NAME_FORMAT,
|
||||
m_mapBuilder->getConfig().DataDirPath(),
|
||||
mapID, tileY, tileX
|
||||
);
|
||||
|
||||
FILE* file = fopen(fileName.c_str(), "wb");
|
||||
if (!file)
|
||||
{
|
||||
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);
|
||||
navMesh->removeTile(tileRef, nullptr, nullptr);
|
||||
break;
|
||||
@ -879,6 +884,7 @@ namespace MMAP
|
||||
MmapTileHeader header;
|
||||
header.usesLiquids = m_terrainBuilder->usesLiquids();
|
||||
header.size = uint32(navDataSize);
|
||||
header.recastConfig = cfg.toMMAPTileRecastConfig();
|
||||
fwrite(&header, sizeof(MmapTileHeader), 1, file);
|
||||
|
||||
// write data
|
||||
@ -899,8 +905,8 @@ namespace MMAP
|
||||
v[2] += (unsigned short)config.borderSize;
|
||||
}
|
||||
|
||||
iv.generateObjFile(mapID, tileX, tileY, meshData);
|
||||
iv.writeIV(mapID, tileX, tileY);
|
||||
iv.generateObjFile(m_mapBuilder->getConfig().DataDirPath(), mapID, tileX, tileY, meshData);
|
||||
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
|
||||
{
|
||||
char fileName[255];
|
||||
sprintf(fileName, "mmaps/%03u%02i%02i.mmtile", mapID, tileY, tileX);
|
||||
FILE* file = fopen(fileName, "rb");
|
||||
const std::string fileName = Acore::StringFormat(
|
||||
TILE_FILE_NAME_FORMAT,
|
||||
m_mapBuilder->getConfig().DataDirPath(),
|
||||
mapID, tileY, tileX
|
||||
);
|
||||
|
||||
FILE* file = fopen(fileName.c_str(), "rb");
|
||||
if (!file)
|
||||
return false;
|
||||
|
||||
@ -1046,10 +1056,11 @@ namespace MMAP
|
||||
if (header.mmapVersion != MMAP_VERSION)
|
||||
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;
|
||||
memset(&config, 0, sizeof(rcConfig));
|
||||
@ -1058,39 +1069,20 @@ namespace MMAP
|
||||
rcVcopy(config.bmax, bmax);
|
||||
|
||||
config.maxVertsPerPoly = DT_VERTS_PER_POLYGON;
|
||||
config.cs = tileConfig.BASE_UNIT_DIM;
|
||||
config.ch = tileConfig.BASE_UNIT_DIM;
|
||||
config.walkableSlopeAngle = m_maxWalkableAngle;
|
||||
config.tileSize = tileConfig.VERTEX_PER_TILE;
|
||||
config.walkableRadius = m_bigBaseUnit ? 1 : 2;
|
||||
config.borderSize = config.walkableRadius + 3;
|
||||
config.maxEdgeLen = tileConfig.VERTEX_PER_TILE + 1; // anything bigger than tileSize
|
||||
config.walkableHeight = m_bigBaseUnit ? 3 : 6;
|
||||
// a value >= 3|6 allows npcs to walk over some fences
|
||||
// a value >= 4|8 allows npcs to walk over all fences
|
||||
config.walkableClimb = m_bigBaseUnit ? 3 : 6;
|
||||
config.cs = cfg.cellSizeHorizontal;
|
||||
config.ch = cfg.cellSizeVertical;
|
||||
config.walkableSlopeAngle = cfg.walkableSlopeAngle;
|
||||
config.tileSize = cfg.vertexPerTileEdge;
|
||||
config.walkableRadius = cfg.walkableRadius;
|
||||
config.borderSize = cfg.walkableRadius + 3;
|
||||
config.maxEdgeLen = cfg.vertexPerTileEdge + 1; // anything bigger than tileSize
|
||||
config.walkableHeight = cfg.walkableHeight;
|
||||
config.walkableClimb = cfg.walkableClimb;
|
||||
config.minRegionArea = rcSqr(60);
|
||||
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.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;
|
||||
}
|
||||
|
||||
|
||||
@ -24,6 +24,7 @@
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "Config.h"
|
||||
#include "Optional.h"
|
||||
#include "TerrainBuilder.h"
|
||||
|
||||
@ -71,27 +72,6 @@ namespace MMAP
|
||||
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
|
||||
{
|
||||
TileInfo() : m_mapId(uint32(-1)), m_tileX(), m_tileY(), m_navMeshParams() {}
|
||||
@ -109,7 +89,6 @@ namespace MMAP
|
||||
public:
|
||||
TileBuilder(MapBuilder* mapBuilder,
|
||||
bool skipLiquid,
|
||||
bool bigBaseUnit,
|
||||
bool debugOutput);
|
||||
|
||||
TileBuilder(TileBuilder&&) = default;
|
||||
@ -131,7 +110,6 @@ namespace MMAP
|
||||
bool shouldSkipTile(uint32 mapID, uint32 tileX, uint32 tileY) const;
|
||||
|
||||
private:
|
||||
bool m_bigBaseUnit;
|
||||
bool m_debugOutput;
|
||||
|
||||
MapBuilder* m_mapBuilder;
|
||||
@ -145,13 +123,7 @@ namespace MMAP
|
||||
{
|
||||
friend class TileBuilder;
|
||||
public:
|
||||
MapBuilder(float maxWalkableAngle,
|
||||
bool skipLiquid,
|
||||
bool skipContinents,
|
||||
bool skipJunkMaps,
|
||||
bool skipBattlegrounds,
|
||||
bool debugOutput,
|
||||
bool bigBaseUnit,
|
||||
MapBuilder(Config* config,
|
||||
int mapid,
|
||||
char const* offMeshFilePath,
|
||||
unsigned int threads);
|
||||
@ -166,6 +138,7 @@ namespace MMAP
|
||||
// builds list of maps, then builds all of mmap tiles (based on the skip settings)
|
||||
void buildMaps(Optional<uint32> mapID);
|
||||
|
||||
const Config& getConfig() const { return *m_config; }
|
||||
private:
|
||||
// builds all mmap tiles for the specified map id (ignores skip settings)
|
||||
void buildMap(uint32 mapID);
|
||||
@ -184,7 +157,7 @@ namespace MMAP
|
||||
bool isTransportMap(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 currentPercentageDone() const;
|
||||
@ -201,10 +174,10 @@ namespace MMAP
|
||||
bool m_skipBattlegrounds;
|
||||
bool m_skipLiquid;
|
||||
|
||||
float m_maxWalkableAngle;
|
||||
bool m_bigBaseUnit;
|
||||
int32 m_mapid;
|
||||
|
||||
Config* m_config;
|
||||
|
||||
std::atomic<uint32> m_totalTiles;
|
||||
std::atomic<uint32> m_totalTilesProcessed;
|
||||
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <boost/dll/runtime_symbol_info.hpp>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <cstddef>
|
||||
@ -35,6 +36,11 @@
|
||||
|
||||
namespace MMAP
|
||||
{
|
||||
inline std::string executableDirectoryPath()
|
||||
{
|
||||
return boost::dll::program_location().parent_path().string();
|
||||
}
|
||||
|
||||
inline bool matchWildcardFilter(const char* filter, const char* str)
|
||||
{
|
||||
if (!filter || !str)
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Config.h"
|
||||
#include "MapBuilder.h"
|
||||
#include "PathCommon.h"
|
||||
#include "Timer.h"
|
||||
@ -23,37 +24,34 @@
|
||||
|
||||
using namespace MMAP;
|
||||
|
||||
bool checkDirectories(bool debugOutput)
|
||||
bool checkDirectories(const std::string &dataDirPath, bool debugOutput)
|
||||
{
|
||||
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");
|
||||
return false;
|
||||
}
|
||||
|
||||
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");
|
||||
return false;
|
||||
}
|
||||
|
||||
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();
|
||||
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 (no place to put debugOutput files)\n");
|
||||
return false;
|
||||
}
|
||||
printf("'meshes' directory does not exist creating...\n");
|
||||
return boost::filesystem::create_directory((std::filesystem::path(dataDirPath) / "meshes").string());
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -63,32 +61,24 @@ bool handleArgs(int argc, char** argv,
|
||||
int& mapnum,
|
||||
int& tileX,
|
||||
int& tileY,
|
||||
float& maxAngle,
|
||||
bool& skipLiquid,
|
||||
bool& skipContinents,
|
||||
bool& skipJunkMaps,
|
||||
bool& skipBattlegrounds,
|
||||
bool& debugOutput,
|
||||
std::string& configFilePath,
|
||||
bool& silent,
|
||||
bool& bigBaseUnit,
|
||||
char*& offMeshInputPath,
|
||||
char*& file,
|
||||
unsigned int& threads)
|
||||
{
|
||||
bool hasCustomConfigPath = false;
|
||||
char* param = nullptr;
|
||||
for (int i = 1; i < argc; ++i)
|
||||
{
|
||||
if (strcmp(argv[i], "--maxAngle") == 0)
|
||||
if (strcmp(argv[i], "--config") == 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");
|
||||
hasCustomConfigPath = true;
|
||||
configFilePath = param;
|
||||
}
|
||||
else if (strcmp(argv[i], "--threads") == 0)
|
||||
{
|
||||
@ -126,88 +116,10 @@ bool handleArgs(int argc, char** argv,
|
||||
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];
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -244,42 +173,36 @@ int main(int argc, char** argv)
|
||||
unsigned int threads = std::thread::hardware_concurrency();
|
||||
int mapnum = -1;
|
||||
int tileX = -1, tileY = -1;
|
||||
float maxAngle = 60.0f;
|
||||
bool skipLiquid = false,
|
||||
skipContinents = false,
|
||||
skipJunkMaps = true,
|
||||
skipBattlegrounds = false,
|
||||
debugOutput = false,
|
||||
silent = false,
|
||||
bigBaseUnit = false;
|
||||
bool silent = false;
|
||||
char* offMeshInputPath = nullptr;
|
||||
char* file = nullptr;
|
||||
|
||||
std::string configFilePath = "mmaps-config.yaml";
|
||||
bool validParam = handleArgs(argc, argv, mapnum,
|
||||
tileX, tileY, maxAngle,
|
||||
skipLiquid, skipContinents, skipJunkMaps, skipBattlegrounds,
|
||||
debugOutput, silent, bigBaseUnit, offMeshInputPath, file, threads);
|
||||
tileX, tileY, configFilePath, silent, offMeshInputPath, file, threads);
|
||||
|
||||
if (!validParam)
|
||||
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)
|
||||
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("Are you sure you want to continue? (y/n) ");
|
||||
if (getchar() != 'y')
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!checkDirectories(debugOutput))
|
||||
if (!checkDirectories(config->DataDirPath(), config->IsDebugOutputEnabled()))
|
||||
return silent ? -3 : finish("Press ENTER to close...", -3);
|
||||
|
||||
MapBuilder builder(maxAngle, skipLiquid, skipContinents, skipJunkMaps,
|
||||
skipBattlegrounds, debugOutput, bigBaseUnit, mapnum, offMeshInputPath, threads);
|
||||
MapBuilder builder(&config.value(), mapnum, offMeshInputPath, threads);
|
||||
|
||||
uint32 start = getMSTime();
|
||||
if (file)
|
||||
|
||||
@ -25,6 +25,8 @@
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
#include "StringFormat.h"
|
||||
|
||||
// ******************************************
|
||||
// Map file format defines
|
||||
// ******************************************
|
||||
@ -80,10 +82,17 @@ struct map_liquidHeader
|
||||
|
||||
namespace MMAP
|
||||
{
|
||||
static char const* const MAP_FILE_NAME_FORMAT = "{}/{:03}{:02}{:02}.map";
|
||||
|
||||
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;
|
||||
|
||||
/**************************************************************************/
|
||||
@ -134,10 +143,13 @@ namespace MMAP
|
||||
/**************************************************************************/
|
||||
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);
|
||||
const std::string mapFileName = Acore::StringFormat(
|
||||
MAP_FILE_NAME_FORMAT,
|
||||
m_mapsPath,
|
||||
mapID, tileY, tileX
|
||||
);
|
||||
|
||||
FILE* mapFile = fopen(mapFileName, "rb");
|
||||
FILE* mapFile = fopen(mapFileName.c_str(), "rb");
|
||||
if (!mapFile)
|
||||
return false;
|
||||
|
||||
@ -146,7 +158,7 @@ namespace MMAP
|
||||
fheader.versionMagic != MAP_VERSION_MAGIC)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@ -665,7 +677,7 @@ namespace MMAP
|
||||
bool TerrainBuilder::loadVMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData)
|
||||
{
|
||||
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;
|
||||
|
||||
do
|
||||
|
||||
@ -76,7 +76,7 @@ namespace MMAP
|
||||
class TerrainBuilder
|
||||
{
|
||||
public:
|
||||
TerrainBuilder(bool skipLiquid);
|
||||
TerrainBuilder(const std::string &mapsPath, bool skipLiquid);
|
||||
~TerrainBuilder();
|
||||
|
||||
TerrainBuilder(const TerrainBuilder& tb) = delete;
|
||||
@ -121,6 +121,9 @@ namespace MMAP
|
||||
|
||||
/// Get the liquid type for a specific position
|
||||
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
|
||||
Loading…
x
Reference in New Issue
Block a user