mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-15 23:20:36 +01:00
Tools/mmaps_generator: Move TileBuilder to its own file
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
#include "Log.h"
|
||||
#include "MMapDefines.h"
|
||||
#include "Memory.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace MMAP
|
||||
{
|
||||
|
||||
@@ -88,6 +88,13 @@ namespace VMAP
|
||||
thread_safe_environment = false;
|
||||
}
|
||||
|
||||
void VMapManager2::InitializeThreadUnsafe(uint32 mapId, int32 parentMapId)
|
||||
{
|
||||
iInstanceMapTrees[mapId] = nullptr;
|
||||
if (parentMapId >= 0)
|
||||
iParentMapData[mapId] = parentMapId;
|
||||
}
|
||||
|
||||
Vector3 VMapManager2::convertPositionToInternalRep(float x, float y, float z) const
|
||||
{
|
||||
Vector3 pos;
|
||||
|
||||
@@ -84,6 +84,7 @@ namespace VMAP
|
||||
~VMapManager2();
|
||||
|
||||
void InitializeThreadUnsafe(std::unordered_map<uint32, std::vector<uint32>> const& mapData);
|
||||
void InitializeThreadUnsafe(uint32 mapId, int32 parentMapId);
|
||||
|
||||
LoadResult loadMap(char const* pBasePath, unsigned int mapId, int x, int y) override;
|
||||
|
||||
|
||||
@@ -16,21 +16,16 @@
|
||||
*/
|
||||
|
||||
#include "MapBuilder.h"
|
||||
#include "IntermediateValues.h"
|
||||
#include "Log.h"
|
||||
#include "MapDefines.h"
|
||||
#include "MapTree.h"
|
||||
#include "MapUtils.h"
|
||||
#include "Memory.h"
|
||||
#include "MMapDefines.h"
|
||||
#include "ModelInstance.h"
|
||||
#include "PathCommon.h"
|
||||
#include "StringConvert.h"
|
||||
#include "StringFormat.h"
|
||||
#include <DetourNavMesh.h>
|
||||
#include <DetourNavMeshBuilder.h>
|
||||
#include <boost/filesystem/directory.hpp>
|
||||
#include <climits>
|
||||
|
||||
namespace FileExtensions
|
||||
{
|
||||
@@ -40,36 +35,33 @@ static boost::filesystem::path vmtile = ".vmtile";
|
||||
|
||||
namespace MMAP
|
||||
{
|
||||
TileBuilder::TileBuilder(MapBuilder* mapBuilder, bool skipLiquid, bool bigBaseUnit, bool debugOutput) :
|
||||
m_bigBaseUnit(bigBaseUnit),
|
||||
m_debugOutput(debugOutput),
|
||||
MapTileBuilder::MapTileBuilder(MapBuilder* mapBuilder, Optional<float> maxWalkableAngle, Optional<float> maxWalkableAngleNotSteep,
|
||||
bool skipLiquid, bool bigBaseUnit, bool debugOutput, std::vector<OffMeshData> const* offMeshConnections) :
|
||||
TileBuilder(maxWalkableAngle, maxWalkableAngleNotSteep, skipLiquid, bigBaseUnit, debugOutput, offMeshConnections),
|
||||
m_mapBuilder(mapBuilder),
|
||||
m_terrainBuilder(nullptr),
|
||||
m_workerThread(&TileBuilder::WorkerThread, this),
|
||||
m_rcContext(nullptr)
|
||||
m_workerThread(&MapTileBuilder::WorkerThread, this)
|
||||
{
|
||||
m_terrainBuilder = new TerrainBuilder(skipLiquid);
|
||||
m_rcContext = new rcContext(false);
|
||||
}
|
||||
|
||||
TileBuilder::~TileBuilder()
|
||||
MapTileBuilder::~MapTileBuilder()
|
||||
{
|
||||
WaitCompletion();
|
||||
|
||||
delete m_terrainBuilder;
|
||||
delete m_rcContext;
|
||||
}
|
||||
|
||||
void TileBuilder::WaitCompletion()
|
||||
void MapTileBuilder::WaitCompletion()
|
||||
{
|
||||
if (m_workerThread.joinable())
|
||||
m_workerThread.join();
|
||||
}
|
||||
|
||||
void MapTileBuilder::OnTileDone()
|
||||
{
|
||||
++m_mapBuilder->m_totalTilesProcessed;
|
||||
}
|
||||
|
||||
MapBuilder::MapBuilder(Optional<float> maxWalkableAngle, Optional<float> maxWalkableAngleNotSteep, bool skipLiquid,
|
||||
bool skipContinents, bool skipJunkMaps, bool skipBattlegrounds,
|
||||
bool debugOutput, bool bigBaseUnit, int mapid, char const* offMeshFilePath, unsigned int threads) :
|
||||
m_terrainBuilder (nullptr),
|
||||
m_debugOutput (debugOutput),
|
||||
m_threads (threads),
|
||||
m_skipContinents (skipContinents),
|
||||
@@ -82,12 +74,8 @@ namespace MMAP
|
||||
m_mapid (mapid),
|
||||
m_totalTiles (0u),
|
||||
m_totalTilesProcessed(0u),
|
||||
m_rcContext (nullptr),
|
||||
_cancelationToken (false)
|
||||
{
|
||||
m_terrainBuilder = new TerrainBuilder(skipLiquid);
|
||||
|
||||
m_rcContext = new rcContext(false);
|
||||
|
||||
// At least 1 thread is needed
|
||||
m_threads = std::max(1u, m_threads);
|
||||
@@ -109,9 +97,6 @@ namespace MMAP
|
||||
|
||||
m_tileBuilders.clear();
|
||||
m_tiles.clear();
|
||||
|
||||
delete m_terrainBuilder;
|
||||
delete m_rcContext;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
@@ -238,7 +223,7 @@ namespace MMAP
|
||||
|
||||
/**************************************************************************/
|
||||
|
||||
void TileBuilder::WorkerThread()
|
||||
void MapTileBuilder::WorkerThread()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
@@ -269,7 +254,8 @@ 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 MapTileBuilder(this, m_maxWalkableAngle, m_maxWalkableAngleNotSteep,
|
||||
m_skipLiquid, m_bigBaseUnit, m_debugOutput, &m_offMeshConnections));
|
||||
}
|
||||
|
||||
if (mapID)
|
||||
@@ -376,10 +362,11 @@ namespace MMAP
|
||||
TerrainBuilder::cleanVertices(data.solidVerts, data.solidTris);
|
||||
// get bounds of current tile
|
||||
float bmin[3], bmax[3];
|
||||
getTileBounds(tileX, tileY, data.solidVerts.getCArray(), data.solidVerts.size() / 3, bmin, bmax);
|
||||
TileBuilder::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);
|
||||
MapTileBuilder tileBuilder(this, m_maxWalkableAngle, m_maxWalkableAngleNotSteep,
|
||||
m_skipLiquid, m_bigBaseUnit, m_debugOutput, &m_offMeshConnections);
|
||||
tileBuilder.buildMoveMapTile(mapId, tileX, tileY, data, bmin, bmax, navMesh);
|
||||
fclose(file);
|
||||
}
|
||||
@@ -397,7 +384,8 @@ 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);
|
||||
MapTileBuilder tileBuilder(this, m_maxWalkableAngle, m_maxWalkableAngleNotSteep,
|
||||
m_skipLiquid, m_bigBaseUnit, m_debugOutput, &m_offMeshConnections);
|
||||
tileBuilder.buildTile(mapID, tileX, tileY, navMesh);
|
||||
dtFreeNavMesh(navMesh);
|
||||
|
||||
@@ -444,59 +432,6 @@ namespace MMAP
|
||||
}
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
void TileBuilder::buildTile(uint32 mapID, uint32 tileX, uint32 tileY, dtNavMesh* navMesh)
|
||||
{
|
||||
if(shouldSkipTile(mapID, tileX, tileY))
|
||||
{
|
||||
++m_mapBuilder->m_totalTilesProcessed;
|
||||
return;
|
||||
}
|
||||
|
||||
TC_LOG_INFO("maps.mmapgen", "{}% [Map {:04}] Building tile [{:02},{:02}]", m_mapBuilder->currentPercentageDone(), mapID, tileX, tileY);
|
||||
|
||||
MeshData meshData;
|
||||
|
||||
// get heightmap data
|
||||
m_terrainBuilder->loadMap(mapID, tileX, tileY, meshData);
|
||||
|
||||
// get model data
|
||||
m_terrainBuilder->loadVMap(mapID, tileY, tileX, meshData);
|
||||
|
||||
// if there is no data, give up now
|
||||
if (!meshData.solidVerts.size() && !meshData.liquidVerts.size())
|
||||
{
|
||||
++m_mapBuilder->m_totalTilesProcessed;
|
||||
return;
|
||||
}
|
||||
|
||||
// remove unused vertices
|
||||
TerrainBuilder::cleanVertices(meshData.solidVerts, meshData.solidTris);
|
||||
TerrainBuilder::cleanVertices(meshData.liquidVerts, meshData.liquidTris);
|
||||
|
||||
// gather all mesh data for final data check, and bounds calculation
|
||||
G3D::Array<float> allVerts;
|
||||
allVerts.append(meshData.liquidVerts);
|
||||
allVerts.append(meshData.solidVerts);
|
||||
|
||||
if (!allVerts.size())
|
||||
{
|
||||
++m_mapBuilder->m_totalTilesProcessed;
|
||||
return;
|
||||
}
|
||||
|
||||
// get bounds of current tile
|
||||
float bmin[3], bmax[3];
|
||||
m_mapBuilder->getTileBounds(tileX, tileY, allVerts.getCArray(), allVerts.size() / 3, bmin, bmax);
|
||||
|
||||
m_terrainBuilder->loadOffMeshConnections(mapID, tileX, tileY, meshData, m_mapBuilder->m_offMeshConnections);
|
||||
|
||||
// build navmesh tile
|
||||
buildMoveMapTile(mapID, tileX, tileY, meshData, bmin, bmax, navMesh);
|
||||
|
||||
++m_mapBuilder->m_totalTilesProcessed;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
void MapBuilder::buildNavMesh(uint32 mapID, dtNavMesh* &navMesh)
|
||||
{
|
||||
@@ -542,7 +477,7 @@ namespace MMAP
|
||||
|
||||
// use Max because '32 - tileX' is negative for values over 32
|
||||
float bmin[3], bmax[3];
|
||||
getTileBounds(tileXMax, tileYMax, nullptr, 0, bmin, bmax);
|
||||
TileBuilder::getTileBounds(tileXMax, tileYMax, nullptr, 0, bmin, bmax);
|
||||
|
||||
/*** now create the navmesh ***/
|
||||
|
||||
@@ -579,361 +514,6 @@ namespace MMAP
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
void TileBuilder::buildMoveMapTile(uint32 mapID, uint32 tileX, uint32 tileY,
|
||||
MeshData &meshData, float bmin[3], float bmax[3],
|
||||
dtNavMesh* navMesh)
|
||||
{
|
||||
// console output
|
||||
std::string tileString = Trinity::StringFormat("[Map {:04}] [{:02},{:02}]: ", mapID, tileX, tileY);
|
||||
TC_LOG_INFO("maps.mmapgen", "{} Building movemap tile...", tileString);
|
||||
|
||||
IntermediateValues iv;
|
||||
|
||||
float* tVerts = meshData.solidVerts.getCArray();
|
||||
int tVertCount = meshData.solidVerts.size() / 3;
|
||||
int* tTris = meshData.solidTris.getCArray();
|
||||
int tTriCount = meshData.solidTris.size() / 3;
|
||||
|
||||
float* lVerts = meshData.liquidVerts.getCArray();
|
||||
int lVertCount = meshData.liquidVerts.size() / 3;
|
||||
int* lTris = meshData.liquidTris.getCArray();
|
||||
int lTriCount = meshData.liquidTris.size() / 3;
|
||||
uint8* lTriFlags = meshData.liquidType.getCArray();
|
||||
|
||||
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);
|
||||
|
||||
// this sets the dimensions of the heightfield - should maybe happen before border padding
|
||||
rcCalcGridSize(config.bmin, config.bmax, config.cs, &config.width, &config.height);
|
||||
|
||||
// allocate subregions : tiles
|
||||
Tile* tiles = new Tile[TILES_PER_MAP * TILES_PER_MAP];
|
||||
|
||||
// Initialize per tile config.
|
||||
rcConfig tileCfg = config;
|
||||
tileCfg.width = config.tileSize + config.borderSize*2;
|
||||
tileCfg.height = config.tileSize + config.borderSize*2;
|
||||
|
||||
// merge per tile poly and detail meshes
|
||||
rcPolyMesh** pmmerge = new rcPolyMesh*[TILES_PER_MAP * TILES_PER_MAP];
|
||||
rcPolyMeshDetail** dmmerge = new rcPolyMeshDetail*[TILES_PER_MAP * TILES_PER_MAP];
|
||||
int nmerge = 0;
|
||||
// build all tiles
|
||||
for (int y = 0; y < TILES_PER_MAP; ++y)
|
||||
{
|
||||
for (int x = 0; x < TILES_PER_MAP; ++x)
|
||||
{
|
||||
Tile& tile = tiles[x + y * TILES_PER_MAP];
|
||||
|
||||
// Calculate the per tile bounding box.
|
||||
tileCfg.bmin[0] = config.bmin[0] + x * float(config.tileSize * config.cs);
|
||||
tileCfg.bmin[2] = config.bmin[2] + y * float(config.tileSize * config.cs);
|
||||
tileCfg.bmax[0] = config.bmin[0] + (x + 1) * float(config.tileSize * config.cs);
|
||||
tileCfg.bmax[2] = config.bmin[2] + (y + 1) * float(config.tileSize * config.cs);
|
||||
|
||||
tileCfg.bmin[0] -= tileCfg.borderSize * tileCfg.cs;
|
||||
tileCfg.bmin[2] -= tileCfg.borderSize * tileCfg.cs;
|
||||
tileCfg.bmax[0] += tileCfg.borderSize * tileCfg.cs;
|
||||
tileCfg.bmax[2] += tileCfg.borderSize * tileCfg.cs;
|
||||
|
||||
// build heightfield
|
||||
tile.solid = rcAllocHeightfield();
|
||||
if (!tile.solid || !rcCreateHeightfield(m_rcContext, *tile.solid, tileCfg.width, tileCfg.height, tileCfg.bmin, tileCfg.bmax, tileCfg.cs, tileCfg.ch))
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} Failed building heightfield!", tileString);
|
||||
continue;
|
||||
}
|
||||
|
||||
// mark all walkable tiles, both liquids and solids
|
||||
|
||||
/* we want to have triangles with slope less than walkableSlopeAngleNotSteep (<= 55) to have NAV_AREA_GROUND
|
||||
* and with slope between walkableSlopeAngleNotSteep and walkableSlopeAngle (55 < .. <= 70) to have NAV_AREA_GROUND_STEEP.
|
||||
* we achieve this using recast API: memset everything to NAV_AREA_GROUND_STEEP, call rcClearUnwalkableTriangles with 70 so
|
||||
* any area above that will get RC_NULL_AREA (unwalkable), then call rcMarkWalkableTriangles with 55 to set NAV_AREA_GROUND
|
||||
* on anything below 55 . Players and idle Creatures can use NAV_AREA_GROUND, while Creatures in combat can use NAV_AREA_GROUND_STEEP.
|
||||
*/
|
||||
unsigned char* triFlags = new unsigned char[tTriCount];
|
||||
memset(triFlags, NAV_AREA_GROUND_STEEP, tTriCount*sizeof(unsigned char));
|
||||
rcClearUnwalkableTriangles(m_rcContext, tileCfg.walkableSlopeAngle, tVerts, tVertCount, tTris, tTriCount, triFlags);
|
||||
rcMarkWalkableTriangles(m_rcContext, tileCfg.walkableSlopeAngleNotSteep, tVerts, tVertCount, tTris, tTriCount, triFlags, NAV_AREA_GROUND);
|
||||
rcRasterizeTriangles(m_rcContext, tVerts, tVertCount, tTris, triFlags, tTriCount, *tile.solid, config.walkableClimb);
|
||||
delete[] triFlags;
|
||||
|
||||
rcFilterLowHangingWalkableObstacles(m_rcContext, config.walkableClimb, *tile.solid);
|
||||
rcFilterLedgeSpans(m_rcContext, tileCfg.walkableHeight, tileCfg.walkableClimb, *tile.solid);
|
||||
rcFilterWalkableLowHeightSpans(m_rcContext, tileCfg.walkableHeight, *tile.solid);
|
||||
|
||||
// add liquid triangles
|
||||
rcRasterizeTriangles(m_rcContext, lVerts, lVertCount, lTris, lTriFlags, lTriCount, *tile.solid, config.walkableClimb);
|
||||
|
||||
// compact heightfield spans
|
||||
tile.chf = rcAllocCompactHeightfield();
|
||||
if (!tile.chf || !rcBuildCompactHeightfield(m_rcContext, tileCfg.walkableHeight, tileCfg.walkableClimb, *tile.solid, *tile.chf))
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} Failed compacting heightfield!", tileString);
|
||||
continue;
|
||||
}
|
||||
|
||||
// build polymesh intermediates
|
||||
if (!rcErodeWalkableArea(m_rcContext, config.walkableRadius, *tile.chf))
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} Failed eroding area!", tileString);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!rcMedianFilterWalkableArea(m_rcContext, *tile.chf))
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} Failed filtering area!", tileString);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!rcBuildDistanceField(m_rcContext, *tile.chf))
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} Failed building distance field!", tileString);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!rcBuildRegions(m_rcContext, *tile.chf, tileCfg.borderSize, tileCfg.minRegionArea, tileCfg.mergeRegionArea))
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} Failed building regions!", tileString);
|
||||
continue;
|
||||
}
|
||||
|
||||
tile.cset = rcAllocContourSet();
|
||||
if (!tile.cset || !rcBuildContours(m_rcContext, *tile.chf, tileCfg.maxSimplificationError, tileCfg.maxEdgeLen, *tile.cset))
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} Failed building contours!", tileString);
|
||||
continue;
|
||||
}
|
||||
|
||||
// build polymesh
|
||||
tile.pmesh = rcAllocPolyMesh();
|
||||
if (!tile.pmesh || !rcBuildPolyMesh(m_rcContext, *tile.cset, tileCfg.maxVertsPerPoly, *tile.pmesh))
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} Failed building polymesh!", tileString);
|
||||
continue;
|
||||
}
|
||||
|
||||
tile.dmesh = rcAllocPolyMeshDetail();
|
||||
if (!tile.dmesh || !rcBuildPolyMeshDetail(m_rcContext, *tile.pmesh, *tile.chf, tileCfg.detailSampleDist, tileCfg.detailSampleMaxError, *tile.dmesh))
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} Failed building polymesh detail!", tileString);
|
||||
continue;
|
||||
}
|
||||
|
||||
// free those up
|
||||
// we may want to keep them in the future for debug
|
||||
// but right now, we don't have the code to merge them
|
||||
rcFreeHeightField(tile.solid);
|
||||
tile.solid = nullptr;
|
||||
rcFreeCompactHeightfield(tile.chf);
|
||||
tile.chf = nullptr;
|
||||
rcFreeContourSet(tile.cset);
|
||||
tile.cset = nullptr;
|
||||
|
||||
pmmerge[nmerge] = tile.pmesh;
|
||||
dmmerge[nmerge] = tile.dmesh;
|
||||
nmerge++;
|
||||
}
|
||||
}
|
||||
|
||||
iv.polyMesh = rcAllocPolyMesh();
|
||||
if (!iv.polyMesh)
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} alloc iv.polyMesh FAILED!", tileString);
|
||||
delete[] pmmerge;
|
||||
delete[] dmmerge;
|
||||
delete[] tiles;
|
||||
return;
|
||||
}
|
||||
rcMergePolyMeshes(m_rcContext, pmmerge, nmerge, *iv.polyMesh);
|
||||
|
||||
iv.polyMeshDetail = rcAllocPolyMeshDetail();
|
||||
if (!iv.polyMeshDetail)
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} alloc m_dmesh FAILED!", tileString);
|
||||
delete[] pmmerge;
|
||||
delete[] dmmerge;
|
||||
delete[] tiles;
|
||||
return;
|
||||
}
|
||||
rcMergePolyMeshDetails(m_rcContext, dmmerge, nmerge, *iv.polyMeshDetail);
|
||||
|
||||
// free things up
|
||||
delete[] pmmerge;
|
||||
delete[] dmmerge;
|
||||
delete[] tiles;
|
||||
|
||||
// set polygons as walkable
|
||||
// TODO: special flags for DYNAMIC polygons, ie surfaces that can be turned on and off
|
||||
for (int i = 0; i < iv.polyMesh->npolys; ++i)
|
||||
{
|
||||
if (uint8 area = iv.polyMesh->areas[i] & NAV_AREA_ALL_MASK)
|
||||
{
|
||||
if (area >= NAV_AREA_MIN_VALUE)
|
||||
iv.polyMesh->flags[i] = 1 << (NAV_AREA_MAX_VALUE - area);
|
||||
else
|
||||
iv.polyMesh->flags[i] = NAV_GROUND; // TODO: these will be dynamic in future
|
||||
}
|
||||
}
|
||||
|
||||
// setup mesh parameters
|
||||
dtNavMeshCreateParams params;
|
||||
memset(¶ms, 0, sizeof(params));
|
||||
params.verts = iv.polyMesh->verts;
|
||||
params.vertCount = iv.polyMesh->nverts;
|
||||
params.polys = iv.polyMesh->polys;
|
||||
params.polyAreas = iv.polyMesh->areas;
|
||||
params.polyFlags = iv.polyMesh->flags;
|
||||
params.polyCount = iv.polyMesh->npolys;
|
||||
params.nvp = iv.polyMesh->nvp;
|
||||
params.detailMeshes = iv.polyMeshDetail->meshes;
|
||||
params.detailVerts = iv.polyMeshDetail->verts;
|
||||
params.detailVertsCount = iv.polyMeshDetail->nverts;
|
||||
params.detailTris = iv.polyMeshDetail->tris;
|
||||
params.detailTriCount = iv.polyMeshDetail->ntris;
|
||||
|
||||
params.offMeshConVerts = meshData.offMeshConnections.getCArray();
|
||||
params.offMeshConCount = meshData.offMeshConnections.size()/6;
|
||||
params.offMeshConRad = meshData.offMeshConnectionRads.getCArray();
|
||||
params.offMeshConDir = meshData.offMeshConnectionDirs.getCArray();
|
||||
params.offMeshConAreas = meshData.offMeshConnectionsAreas.getCArray();
|
||||
params.offMeshConFlags = meshData.offMeshConnectionsFlags.getCArray();
|
||||
|
||||
params.walkableHeight = BASE_UNIT_DIM*config.walkableHeight; // agent height
|
||||
params.walkableRadius = BASE_UNIT_DIM*config.walkableRadius; // agent radius
|
||||
params.walkableClimb = BASE_UNIT_DIM*config.walkableClimb; // keep less that walkableHeight (aka agent height)!
|
||||
params.tileX = (((bmin[0] + bmax[0]) / 2) - navMesh->getParams()->orig[0]) / GRID_SIZE;
|
||||
params.tileY = (((bmin[2] + bmax[2]) / 2) - navMesh->getParams()->orig[2]) / GRID_SIZE;
|
||||
rcVcopy(params.bmin, bmin);
|
||||
rcVcopy(params.bmax, bmax);
|
||||
params.cs = config.cs;
|
||||
params.ch = config.ch;
|
||||
params.tileLayer = 0;
|
||||
params.buildBvTree = true;
|
||||
|
||||
// will hold final navmesh
|
||||
unsigned char* navData = nullptr;
|
||||
int navDataSize = 0;
|
||||
|
||||
do
|
||||
{
|
||||
// these values are checked within dtCreateNavMeshData - handle them here
|
||||
// so we have a clear error message
|
||||
if (params.nvp > DT_VERTS_PER_POLYGON)
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} Invalid verts-per-polygon value!", tileString);
|
||||
break;
|
||||
}
|
||||
if (params.vertCount >= 0xffff)
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} Too many vertices!", tileString);
|
||||
break;
|
||||
}
|
||||
if (!params.vertCount || !params.verts)
|
||||
{
|
||||
// occurs mostly when adjacent tiles have models
|
||||
// loaded but those models don't span into this tile
|
||||
|
||||
// message is an annoyance
|
||||
//TC_LOG_ERROR("maps.mmapgen", "{} No vertices to build tile!", tileString);
|
||||
break;
|
||||
}
|
||||
if (!params.polyCount || !params.polys)
|
||||
{
|
||||
// we have flat tiles with no actual geometry - don't build those, its useless
|
||||
// keep in mind that we do output those into debug info
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} No polygons to build on tile!", tileString);
|
||||
break;
|
||||
}
|
||||
if (!params.detailMeshes || !params.detailVerts || !params.detailTris)
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} No detail mesh to build tile!", tileString);
|
||||
break;
|
||||
}
|
||||
|
||||
TC_LOG_DEBUG("maps.mmapgen", "{} Building navmesh tile...", tileString);
|
||||
if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize))
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} Failed building navmesh tile!", tileString);
|
||||
break;
|
||||
}
|
||||
|
||||
dtTileRef tileRef = 0;
|
||||
TC_LOG_DEBUG("maps.mmapgen", "{} Adding tile to navmesh...", tileString);
|
||||
// DT_TILE_FREE_DATA tells detour to unallocate memory when the tile
|
||||
// is removed via removeTile()
|
||||
dtStatus dtResult = navMesh->addTile(navData, navDataSize, DT_TILE_FREE_DATA, 0, &tileRef);
|
||||
if (!tileRef || !dtStatusSucceed(dtResult))
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} Failed adding tile to navmesh!", tileString);
|
||||
break;
|
||||
}
|
||||
|
||||
// file output
|
||||
std::string fileName = Trinity::StringFormat("mmaps/{:04}{:02}{:02}.mmtile", mapID, tileY, tileX);
|
||||
FILE* file = fopen(fileName.c_str(), "wb");
|
||||
if (!file)
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{}: [Map {:04}] Failed to open {} for writing!", strerror(errno), mapID, fileName);
|
||||
navMesh->removeTile(tileRef, nullptr, nullptr);
|
||||
break;
|
||||
}
|
||||
|
||||
TC_LOG_DEBUG("maps.mmapgen", "{} Writing to file...", tileString);
|
||||
|
||||
// write header
|
||||
MmapTileHeader header;
|
||||
header.usesLiquids = m_terrainBuilder->usesLiquids();
|
||||
header.size = uint32(navDataSize);
|
||||
fwrite(&header, sizeof(MmapTileHeader), 1, file);
|
||||
|
||||
// write data
|
||||
fwrite(navData, sizeof(unsigned char), navDataSize, file);
|
||||
fclose(file);
|
||||
|
||||
// now that tile is written to disk, we can unload it
|
||||
navMesh->removeTile(tileRef, nullptr, nullptr);
|
||||
}
|
||||
while (false);
|
||||
|
||||
if (m_debugOutput)
|
||||
{
|
||||
// restore padding so that the debug visualization is correct
|
||||
for (int i = 0; i < iv.polyMesh->nverts; ++i)
|
||||
{
|
||||
unsigned short* v = &iv.polyMesh->verts[i*3];
|
||||
v[0] += (unsigned short)config.borderSize;
|
||||
v[2] += (unsigned short)config.borderSize;
|
||||
}
|
||||
|
||||
iv.generateObjFile(mapID, tileX, tileY, meshData);
|
||||
iv.writeIV(mapID, tileX, tileY);
|
||||
}
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
void MapBuilder::getTileBounds(uint32 tileX, uint32 tileY, float* verts, int vertCount, float* bmin, float* bmax) const
|
||||
{
|
||||
// this is for elevation
|
||||
if (verts && vertCount)
|
||||
rcCalcBounds(verts, vertCount, bmin, bmax);
|
||||
else
|
||||
{
|
||||
bmin[1] = FLT_MIN;
|
||||
bmax[1] = FLT_MAX;
|
||||
}
|
||||
|
||||
// this is for width and depth
|
||||
bmax[0] = (32 - int(tileX)) * GRID_SIZE;
|
||||
bmax[2] = (32 - int(tileY)) * GRID_SIZE;
|
||||
bmin[0] = bmax[0] - GRID_SIZE;
|
||||
bmin[2] = bmax[2] - GRID_SIZE;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
bool MapBuilder::shouldSkipMap(uint32 mapID) const
|
||||
{
|
||||
@@ -1020,7 +600,7 @@ namespace MMAP
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
bool TileBuilder::shouldSkipTile(uint32 mapID, uint32 tileX, uint32 tileY) const
|
||||
bool MapTileBuilder::shouldSkipTile(uint32 mapID, uint32 tileX, uint32 tileY) const
|
||||
{
|
||||
std::string fileName = Trinity::StringFormat("mmaps/{:04}{:02}{:02}.mmtile", mapID, tileY, tileX);
|
||||
FILE* file = fopen(fileName.c_str(), "rb");
|
||||
@@ -1039,58 +619,12 @@ namespace MMAP
|
||||
if (header.mmapVersion != MMAP_VERSION)
|
||||
return false;
|
||||
|
||||
if (m_debugOutput)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
return TileBuilder::shouldSkipTile(mapID, tileX, tileY);
|
||||
}
|
||||
|
||||
rcConfig MapBuilder::GetMapSpecificConfig(uint32 mapID, float bmin[3], float bmax[3], const TileConfig &tileConfig) const
|
||||
std::string MapTileBuilder::GetProgressText() const
|
||||
{
|
||||
rcConfig config;
|
||||
memset(&config, 0, sizeof(rcConfig));
|
||||
|
||||
rcVcopy(config.bmin, bmin);
|
||||
rcVcopy(config.bmax, bmax);
|
||||
|
||||
config.maxVertsPerPoly = DT_VERTS_PER_POLYGON;
|
||||
config.cs = tileConfig.BASE_UNIT_DIM;
|
||||
config.ch = tileConfig.BASE_UNIT_DIM;
|
||||
// Keeping these 2 slope angles the same reduces a lot the number of polys.
|
||||
// 55 should be the minimum, maybe 70 is ok (keep in mind blink uses mmaps), 85 is too much for players
|
||||
config.walkableSlopeAngle = m_maxWalkableAngle ? *m_maxWalkableAngle : 55;
|
||||
config.walkableSlopeAngleNotSteep = m_maxWalkableAngleNotSteep ? *m_maxWalkableAngleNotSteep : 55;
|
||||
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.minRegionArea = rcSqr(60);
|
||||
config.mergeRegionArea = rcSqr(50);
|
||||
config.maxSimplificationError = 1.8f; // 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;
|
||||
return Trinity::StringFormat("{}%", m_mapBuilder->currentPercentageDone());
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
#include "Optional.h"
|
||||
#include "ProducerConsumerQueue.h"
|
||||
#include "TerrainBuilder.h"
|
||||
#include "TileBuilder.h"
|
||||
#include <DetourNavMesh.h>
|
||||
#include <Recast.h>
|
||||
#include <atomic>
|
||||
#include <span>
|
||||
#include <thread>
|
||||
@@ -33,45 +33,6 @@ namespace MMAP
|
||||
{
|
||||
typedef std::unordered_map<uint32, Trinity::Containers::FlatSet<uint32>> TileList;
|
||||
|
||||
struct Tile
|
||||
{
|
||||
Tile() : chf(nullptr), solid(nullptr), cset(nullptr), pmesh(nullptr), dmesh(nullptr) {}
|
||||
~Tile()
|
||||
{
|
||||
rcFreeCompactHeightfield(chf);
|
||||
rcFreeContourSet(cset);
|
||||
rcFreeHeightField(solid);
|
||||
rcFreePolyMesh(pmesh);
|
||||
rcFreePolyMeshDetail(dmesh);
|
||||
}
|
||||
rcCompactHeightfield* chf;
|
||||
rcHeightfield* solid;
|
||||
rcContourSet* cset;
|
||||
rcPolyMesh* pmesh;
|
||||
rcPolyMeshDetail* dmesh;
|
||||
};
|
||||
|
||||
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() {}
|
||||
@@ -84,46 +45,37 @@ namespace MMAP
|
||||
|
||||
// ToDo: move this to its own file. For now it will stay here to keep the changes to a minimum, especially in the cpp file
|
||||
class MapBuilder;
|
||||
class TileBuilder
|
||||
|
||||
class MapTileBuilder : public TileBuilder
|
||||
{
|
||||
public:
|
||||
TileBuilder(MapBuilder* mapBuilder,
|
||||
MapTileBuilder(MapBuilder* mapBuilder,
|
||||
Optional<float> maxWalkableAngle,
|
||||
Optional<float> maxWalkableAngleNotSteep,
|
||||
bool skipLiquid,
|
||||
bool bigBaseUnit,
|
||||
bool debugOutput);
|
||||
|
||||
TileBuilder(TileBuilder&&) = default;
|
||||
~TileBuilder();
|
||||
bool debugOutput,
|
||||
std::vector<OffMeshData> const* offMeshConnections);
|
||||
~MapTileBuilder();
|
||||
|
||||
void WorkerThread();
|
||||
void WaitCompletion();
|
||||
|
||||
void buildTile(uint32 mapID, uint32 tileX, uint32 tileY, dtNavMesh* navMesh);
|
||||
// move map building
|
||||
void buildMoveMapTile(uint32 mapID,
|
||||
uint32 tileX,
|
||||
uint32 tileY,
|
||||
MeshData& meshData,
|
||||
float bmin[3],
|
||||
float bmax[3],
|
||||
dtNavMesh* navMesh);
|
||||
bool shouldSkipTile(uint32 mapID, uint32 tileX, uint32 tileY) const override;
|
||||
|
||||
bool shouldSkipTile(uint32 mapID, uint32 tileX, uint32 tileY) const;
|
||||
std::string GetProgressText() const override;
|
||||
|
||||
void OnTileDone() override;
|
||||
|
||||
private:
|
||||
bool m_bigBaseUnit;
|
||||
bool m_debugOutput;
|
||||
|
||||
MapBuilder* m_mapBuilder;
|
||||
TerrainBuilder* m_terrainBuilder;
|
||||
std::thread m_workerThread;
|
||||
// build performance - not really used for now
|
||||
rcContext* m_rcContext;
|
||||
};
|
||||
|
||||
class MapBuilder
|
||||
{
|
||||
friend class TileBuilder;
|
||||
friend class MapTileBuilder;
|
||||
|
||||
public:
|
||||
MapBuilder(Optional<float> maxWalkableAngle,
|
||||
@@ -157,24 +109,17 @@ namespace MMAP
|
||||
|
||||
void buildNavMesh(uint32 mapID, dtNavMesh* &navMesh);
|
||||
|
||||
void getTileBounds(uint32 tileX, uint32 tileY,
|
||||
float* verts, int vertCount,
|
||||
float* bmin, float* bmax) const;
|
||||
|
||||
bool shouldSkipMap(uint32 mapID) const;
|
||||
bool isTransportMap(uint32 mapID) const;
|
||||
bool isDevMap(uint32 mapID) const;
|
||||
bool isBattlegroundMap(uint32 mapID) const;
|
||||
bool isContinentMap(uint32 mapID) const;
|
||||
|
||||
rcConfig GetMapSpecificConfig(uint32 mapID, float bmin[3], float bmax[3], const TileConfig &tileConfig) const;
|
||||
|
||||
uint32 percentageDone(uint32 totalTiles, uint32 totalTilesDone) const;
|
||||
uint32 currentPercentageDone() const;
|
||||
|
||||
void ParseOffMeshConnectionsFile(char const* offMeshFilePath);
|
||||
|
||||
TerrainBuilder* m_terrainBuilder;
|
||||
TileList m_tiles;
|
||||
|
||||
bool m_debugOutput;
|
||||
@@ -195,9 +140,6 @@ namespace MMAP
|
||||
uint32 m_totalTiles;
|
||||
std::atomic<uint32> m_totalTilesProcessed;
|
||||
|
||||
// build performance - not really used for now
|
||||
rcContext* m_rcContext;
|
||||
|
||||
std::vector<TileBuilder*> m_tileBuilders;
|
||||
ProducerConsumerQueue<TileInfo> _queue;
|
||||
std::atomic<bool> _cancelationToken;
|
||||
|
||||
@@ -141,7 +141,7 @@ namespace MMAP
|
||||
|
||||
namespace VMapFactory
|
||||
{
|
||||
std::unique_ptr<VMAP::VMapManager2> CreateVMapManager();
|
||||
std::unique_ptr<VMAP::VMapManager2> CreateVMapManager(uint32 mapId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ constexpr char Readme[] =
|
||||
namespace
|
||||
{
|
||||
std::unordered_map<uint32, uint8> _liquidTypes;
|
||||
std::unordered_map<uint32, std::vector<uint32>> _mapDataForVmapInitialization;
|
||||
}
|
||||
|
||||
namespace MMAP
|
||||
@@ -47,10 +46,21 @@ namespace MMAP
|
||||
|
||||
namespace VMapFactory
|
||||
{
|
||||
std::unique_ptr<VMAP::VMapManager2> CreateVMapManager()
|
||||
std::unique_ptr<VMAP::VMapManager2> CreateVMapManager(uint32 mapId)
|
||||
{
|
||||
std::unique_ptr<VMAP::VMapManager2> vmgr = std::make_unique<VMAP::VMapManager2>();
|
||||
vmgr->InitializeThreadUnsafe(_mapDataForVmapInitialization);
|
||||
|
||||
do
|
||||
{
|
||||
int32 parentMapId = sMapStore[mapId].ParentMapID;
|
||||
|
||||
vmgr->InitializeThreadUnsafe(mapId, parentMapId);
|
||||
if (parentMapId < 0)
|
||||
break;
|
||||
|
||||
mapId = parentMapId;
|
||||
} while (true);
|
||||
|
||||
vmgr->GetLiquidFlagsPtr = [](uint32 liquidId) -> uint32
|
||||
{
|
||||
auto itr = _liquidTypes.find(liquidId);
|
||||
@@ -362,10 +372,9 @@ std::unordered_map<uint32, uint8> LoadLiquid(std::string const& locale, bool sil
|
||||
return liquidData;
|
||||
}
|
||||
|
||||
std::unordered_map<uint32, std::vector<uint32>> LoadMap(std::string const& locale, bool silent, int32 errorExitCode)
|
||||
void LoadMap(std::string const& locale, bool silent, int32 errorExitCode)
|
||||
{
|
||||
DB2FileLoader mapDb2;
|
||||
std::unordered_map<uint32, std::vector<uint32>> mapData;
|
||||
DB2FileSystemSource mapSource((boost::filesystem::path("dbc") / locale / "Map.db2").string());
|
||||
try
|
||||
{
|
||||
@@ -376,12 +385,9 @@ std::unordered_map<uint32, std::vector<uint32>> LoadMap(std::string const& local
|
||||
if (!record)
|
||||
continue;
|
||||
|
||||
mapData.emplace(std::piecewise_construct, std::forward_as_tuple(record.GetId()), std::forward_as_tuple());
|
||||
int16 parentMapId = int16(record.GetUInt16("ParentMapID"));
|
||||
if (parentMapId < 0)
|
||||
parentMapId = int16(record.GetUInt16("CosmeticParentMapID"));
|
||||
if (parentMapId != -1)
|
||||
mapData[parentMapId].push_back(record.GetId());
|
||||
|
||||
MMAP::MapEntry& map = MMAP::sMapStore[record.GetId()];
|
||||
map.MapType = record.GetUInt8("MapType");
|
||||
@@ -397,8 +403,6 @@ std::unordered_map<uint32, std::vector<uint32>> LoadMap(std::string const& local
|
||||
|
||||
exit(finish(e.what(), errorExitCode));
|
||||
}
|
||||
|
||||
return mapData;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
@@ -451,7 +455,7 @@ int main(int argc, char** argv)
|
||||
|
||||
_liquidTypes = LoadLiquid(dbcLocales[0], silent, -5);
|
||||
|
||||
_mapDataForVmapInitialization = LoadMap(dbcLocales[0], silent, -4);
|
||||
LoadMap(dbcLocales[0], silent, -4);
|
||||
|
||||
MMAP::MapBuilder builder(maxAngle, maxAngleNotSteep, skipLiquid, skipContinents, skipJunkMaps,
|
||||
skipBattlegrounds, debugOutput, bigBaseUnit, mapnum, offMeshInputPath, threads);
|
||||
|
||||
@@ -29,10 +29,9 @@
|
||||
namespace MMAP
|
||||
{
|
||||
TerrainBuilder::TerrainBuilder(bool skipLiquid) : m_skipLiquid (skipLiquid){ }
|
||||
TerrainBuilder::~TerrainBuilder() { }
|
||||
|
||||
/**************************************************************************/
|
||||
void TerrainBuilder::getLoopVars(Spot portion, int &loopStart, int &loopEnd, int &loopInc)
|
||||
void TerrainBuilder::getLoopVars(Spot portion, int& loopStart, int& loopEnd, int& loopInc)
|
||||
{
|
||||
switch (portion)
|
||||
{
|
||||
@@ -65,31 +64,31 @@ namespace MMAP
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
void TerrainBuilder::loadMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData)
|
||||
void TerrainBuilder::loadMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData, VMAP::VMapManager2* vmapManager)
|
||||
{
|
||||
if (loadMap(mapID, tileX, tileY, meshData, ENTIRE))
|
||||
if (loadMap(mapID, tileX, tileY, meshData, vmapManager, ENTIRE))
|
||||
{
|
||||
loadMap(mapID, tileX+1, tileY, meshData, LEFT);
|
||||
loadMap(mapID, tileX-1, tileY, meshData, RIGHT);
|
||||
loadMap(mapID, tileX, tileY+1, meshData, TOP);
|
||||
loadMap(mapID, tileX, tileY-1, meshData, BOTTOM);
|
||||
loadMap(mapID, tileX+1, tileY, meshData, vmapManager, LEFT);
|
||||
loadMap(mapID, tileX-1, tileY, meshData, vmapManager, RIGHT);
|
||||
loadMap(mapID, tileX, tileY+1, meshData, vmapManager, TOP);
|
||||
loadMap(mapID, tileX, tileY-1, meshData, vmapManager, BOTTOM);
|
||||
}
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
bool TerrainBuilder::loadMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData, Spot portion)
|
||||
bool TerrainBuilder::loadMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData, VMAP::VMapManager2* vmapManager, Spot portion)
|
||||
{
|
||||
std::string mapFileName = Trinity::StringFormat("maps/{:04}_{:02}_{:02}.map", mapID, tileY, tileX);
|
||||
|
||||
FILE* mapFile = fopen(mapFileName.c_str(), "rb");
|
||||
if (!mapFile)
|
||||
{
|
||||
int32 parentMapId = sMapStore[mapID].ParentMapID;
|
||||
int32 parentMapId = vmapManager->getParentMapId(mapID);
|
||||
while (!mapFile && parentMapId != -1)
|
||||
{
|
||||
mapFileName = Trinity::StringFormat("maps/{:04}_{:02}_{:02}.map", parentMapId, tileY, tileX);
|
||||
mapFile = fopen(mapFileName.c_str(), "rb");
|
||||
parentMapId = sMapStore[parentMapId].ParentMapID;
|
||||
parentMapId = vmapManager->getParentMapId(mapID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,12 +123,9 @@ namespace MMAP
|
||||
}
|
||||
|
||||
// data used later
|
||||
uint8 holes[16][16][8];
|
||||
memset(holes, 0, sizeof(holes));
|
||||
uint16 liquid_entry[16][16];
|
||||
memset(liquid_entry, 0, sizeof(liquid_entry));
|
||||
map_liquidHeaderTypeFlags liquid_flags[16][16];
|
||||
memset(liquid_flags, 0, sizeof(liquid_flags));
|
||||
uint8 holes[16][16][8] = { };
|
||||
uint16 liquid_entry[16][16] = { };
|
||||
map_liquidHeaderTypeFlags liquid_flags[16][16] = { };
|
||||
G3D::Array<int> ltriangles;
|
||||
G3D::Array<int> ttriangles;
|
||||
|
||||
@@ -559,7 +555,7 @@ namespace MMAP
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
bool TerrainBuilder::isHole(int square, uint8 const holes[16][16][8])
|
||||
bool TerrainBuilder::isHole(int square, uint8 const (&holes)[16][16][8])
|
||||
{
|
||||
int row = square / 128;
|
||||
int col = square % 128;
|
||||
@@ -583,9 +579,8 @@ namespace MMAP
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
bool TerrainBuilder::loadVMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData)
|
||||
bool TerrainBuilder::loadVMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData, VMAP::VMapManager2* vmapManager)
|
||||
{
|
||||
std::unique_ptr<VMAP::VMapManager2> vmapManager = VMapFactory::CreateVMapManager();
|
||||
VMAP::LoadResult result = vmapManager->loadMap("vmaps", mapID, tileX, tileY);
|
||||
bool retval = false;
|
||||
|
||||
@@ -792,7 +787,7 @@ namespace MMAP
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
void TerrainBuilder::cleanVertices(G3D::Array<float> &verts, G3D::Array<int> &tris)
|
||||
void TerrainBuilder::cleanVertices(G3D::Array<float>& verts, G3D::Array<int>& tris)
|
||||
{
|
||||
std::map<int, int> vertMap;
|
||||
|
||||
|
||||
@@ -18,12 +18,16 @@
|
||||
#ifndef _MMAP_TERRAIN_BUILDER_H
|
||||
#define _MMAP_TERRAIN_BUILDER_H
|
||||
|
||||
#include "PathCommon.h"
|
||||
#include "WorldModel.h"
|
||||
|
||||
#include <G3D/Array.h>
|
||||
#include <G3D/Vector3.h>
|
||||
|
||||
namespace VMAP
|
||||
{
|
||||
class VMapManager2;
|
||||
}
|
||||
|
||||
enum class map_liquidHeaderTypeFlags : uint8;
|
||||
|
||||
namespace MMAP
|
||||
@@ -91,11 +95,10 @@ namespace MMAP
|
||||
class TerrainBuilder
|
||||
{
|
||||
public:
|
||||
TerrainBuilder(bool skipLiquid);
|
||||
~TerrainBuilder();
|
||||
explicit TerrainBuilder(bool skipLiquid);
|
||||
|
||||
void loadMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData);
|
||||
bool loadVMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData);
|
||||
void loadMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData, VMAP::VMapManager2* vmapManager);
|
||||
bool loadVMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData, VMAP::VMapManager2* vmapManager);
|
||||
void loadOffMeshConnections(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData, std::vector<OffMeshData> const& offMeshConnections);
|
||||
|
||||
bool usesLiquids() const { return !m_skipLiquid; }
|
||||
@@ -106,20 +109,17 @@ namespace MMAP
|
||||
static void copyVertices(std::vector<G3D::Vector3> const& source, G3D::Array<float>& dest);
|
||||
static void copyIndices(std::vector<VMAP::MeshTriangle> const& source, G3D::Array<int>& dest, int offset, bool flip);
|
||||
static void copyIndices(G3D::Array<int> const& source, G3D::Array<int>& dest, int offset);
|
||||
static void cleanVertices(G3D::Array<float> &verts, G3D::Array<int> &tris);
|
||||
static void cleanVertices(G3D::Array<float>& verts, G3D::Array<int>& tris);
|
||||
private:
|
||||
/// Loads a portion of a map's terrain
|
||||
bool loadMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData &meshData, Spot portion);
|
||||
bool loadMap(uint32 mapID, uint32 tileX, uint32 tileY, MeshData& meshData, VMAP::VMapManager2* vmapManager, Spot portion);
|
||||
|
||||
/// Sets loop variables for selecting only certain parts of a map's terrain
|
||||
void getLoopVars(Spot portion, int &loopStart, int &loopEnd, int &loopInc);
|
||||
void getLoopVars(Spot portion, int& loopStart, int& loopEnd, int& loopInc);
|
||||
|
||||
/// Controls whether liquids are loaded
|
||||
bool m_skipLiquid;
|
||||
|
||||
/// Load the map terrain from file
|
||||
bool loadHeightMap(uint32 mapID, uint32 tileX, uint32 tileY, G3D::Array<float> &vertices, G3D::Array<int> &triangles, Spot portion);
|
||||
|
||||
/// Get the vector coordinate for a specific position
|
||||
void getHeightCoord(int index, Grid grid, float xOffset, float yOffset, float* coord, float* v);
|
||||
|
||||
@@ -127,17 +127,13 @@ namespace MMAP
|
||||
void getHeightTriangle(int square, Spot triangle, int* indices, bool liquid = false);
|
||||
|
||||
/// Determines if the specific position's triangles should be rendered
|
||||
bool isHole(int square, uint8 const holes[16][16][8]);
|
||||
bool isHole(int square, uint8 const (&holes)[16][16][8]);
|
||||
|
||||
/// Get the liquid vector coordinate for a specific position
|
||||
void getLiquidCoord(int index, int index2, float xOffset, float yOffset, float* coord, float* v);
|
||||
|
||||
/// Get the liquid type for a specific position
|
||||
map_liquidHeaderTypeFlags getLiquidType(int square, map_liquidHeaderTypeFlags const (&liquid_type)[16][16]);
|
||||
|
||||
// hide parameterless and copy constructor
|
||||
TerrainBuilder() = delete;
|
||||
TerrainBuilder(TerrainBuilder const& tb) = delete;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
554
src/tools/mmaps_generator/TileBuilder.cpp
Normal file
554
src/tools/mmaps_generator/TileBuilder.cpp
Normal file
@@ -0,0 +1,554 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore 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 General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "TileBuilder.h"
|
||||
#include "IntermediateValues.h"
|
||||
#include "Log.h"
|
||||
#include "MMapDefines.h"
|
||||
#include "PathCommon.h"
|
||||
#include "StringFormat.h"
|
||||
#include "VMapManager2.h"
|
||||
#include <DetourNavMeshBuilder.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
struct Tile
|
||||
{
|
||||
Tile() : chf(nullptr), solid(nullptr), cset(nullptr), pmesh(nullptr), dmesh(nullptr) {}
|
||||
~Tile()
|
||||
{
|
||||
rcFreeCompactHeightfield(chf);
|
||||
rcFreeContourSet(cset);
|
||||
rcFreeHeightField(solid);
|
||||
rcFreePolyMesh(pmesh);
|
||||
rcFreePolyMeshDetail(dmesh);
|
||||
}
|
||||
rcCompactHeightfield* chf;
|
||||
rcHeightfield* solid;
|
||||
rcContourSet* cset;
|
||||
rcPolyMesh* pmesh;
|
||||
rcPolyMeshDetail* dmesh;
|
||||
};
|
||||
}
|
||||
|
||||
namespace MMAP
|
||||
{
|
||||
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;
|
||||
};
|
||||
|
||||
TileBuilder::TileBuilder(Optional<float> maxWalkableAngle, Optional<float> maxWalkableAngleNotSteep,
|
||||
bool skipLiquid, bool bigBaseUnit, bool debugOutput, std::vector<OffMeshData> const* offMeshConnections) :
|
||||
m_maxWalkableAngle(maxWalkableAngle),
|
||||
m_maxWalkableAngleNotSteep(maxWalkableAngleNotSteep),
|
||||
m_bigBaseUnit(bigBaseUnit),
|
||||
m_debugOutput(debugOutput),
|
||||
m_terrainBuilder(skipLiquid),
|
||||
m_rcContext(false),
|
||||
m_offMeshConnections(offMeshConnections)
|
||||
{
|
||||
}
|
||||
|
||||
TileBuilder::~TileBuilder() = default;
|
||||
|
||||
/**************************************************************************/
|
||||
void TileBuilder::buildTile(uint32 mapID, uint32 tileX, uint32 tileY, dtNavMesh* navMesh)
|
||||
{
|
||||
if (shouldSkipTile(mapID, tileX, tileY))
|
||||
{
|
||||
OnTileDone();
|
||||
return;
|
||||
}
|
||||
|
||||
TC_LOG_INFO("maps.mmapgen", "{} [Map {:04}] Building tile [{:02},{:02}]", GetProgressText(), mapID, tileX, tileY);
|
||||
|
||||
MeshData meshData;
|
||||
|
||||
std::unique_ptr<VMAP::VMapManager2> vmapManager = VMapFactory::CreateVMapManager(mapID);
|
||||
|
||||
// get heightmap data
|
||||
m_terrainBuilder.loadMap(mapID, tileX, tileY, meshData, vmapManager.get());
|
||||
|
||||
// get model data
|
||||
m_terrainBuilder.loadVMap(mapID, tileY, tileX, meshData, vmapManager.get());
|
||||
|
||||
// if there is no data, give up now
|
||||
if (!meshData.solidVerts.size() && !meshData.liquidVerts.size())
|
||||
{
|
||||
OnTileDone();
|
||||
return;
|
||||
}
|
||||
|
||||
// remove unused vertices
|
||||
TerrainBuilder::cleanVertices(meshData.solidVerts, meshData.solidTris);
|
||||
TerrainBuilder::cleanVertices(meshData.liquidVerts, meshData.liquidTris);
|
||||
|
||||
// gather all mesh data for final data check, and bounds calculation
|
||||
G3D::Array<float> allVerts;
|
||||
allVerts.append(meshData.liquidVerts);
|
||||
allVerts.append(meshData.solidVerts);
|
||||
|
||||
if (!allVerts.size())
|
||||
{
|
||||
OnTileDone();
|
||||
return;
|
||||
}
|
||||
|
||||
// get bounds of current tile
|
||||
float bmin[3], bmax[3];
|
||||
getTileBounds(tileX, tileY, allVerts.getCArray(), allVerts.size() / 3, bmin, bmax);
|
||||
|
||||
if (m_offMeshConnections)
|
||||
m_terrainBuilder.loadOffMeshConnections(mapID, tileX, tileY, meshData, *m_offMeshConnections);
|
||||
|
||||
// build navmesh tile
|
||||
buildMoveMapTile(mapID, tileX, tileY, meshData, bmin, bmax, navMesh);
|
||||
|
||||
OnTileDone();
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
void TileBuilder::buildMoveMapTile(uint32 mapID, uint32 tileX, uint32 tileY,
|
||||
MeshData& meshData, float (&bmin)[3], float (&bmax)[3],
|
||||
dtNavMesh* navMesh)
|
||||
{
|
||||
// console output
|
||||
std::string tileString = Trinity::StringFormat("[Map {:04}] [{:02},{:02}]:", mapID, tileX, tileY);
|
||||
TC_LOG_INFO("maps.mmapgen", "{} Building movemap tile...", tileString);
|
||||
|
||||
IntermediateValues iv;
|
||||
|
||||
float* tVerts = meshData.solidVerts.getCArray();
|
||||
int tVertCount = meshData.solidVerts.size() / 3;
|
||||
int* tTris = meshData.solidTris.getCArray();
|
||||
int tTriCount = meshData.solidTris.size() / 3;
|
||||
|
||||
float* lVerts = meshData.liquidVerts.getCArray();
|
||||
int lVertCount = meshData.liquidVerts.size() / 3;
|
||||
int* lTris = meshData.liquidTris.getCArray();
|
||||
int lTriCount = meshData.liquidTris.size() / 3;
|
||||
uint8* lTriFlags = meshData.liquidType.getCArray();
|
||||
|
||||
const TileConfig tileConfig = TileConfig(m_bigBaseUnit);
|
||||
int TILES_PER_MAP = tileConfig.TILES_PER_MAP;
|
||||
float BASE_UNIT_DIM = tileConfig.BASE_UNIT_DIM;
|
||||
rcConfig config = GetMapSpecificConfig(mapID, bmin, bmax, tileConfig);
|
||||
|
||||
// this sets the dimensions of the heightfield - should maybe happen before border padding
|
||||
rcCalcGridSize(config.bmin, config.bmax, config.cs, &config.width, &config.height);
|
||||
|
||||
// allocate subregions : tiles
|
||||
Tile* tiles = new Tile[TILES_PER_MAP * TILES_PER_MAP];
|
||||
|
||||
// Initialize per tile config.
|
||||
rcConfig tileCfg = config;
|
||||
tileCfg.width = config.tileSize + config.borderSize * 2;
|
||||
tileCfg.height = config.tileSize + config.borderSize * 2;
|
||||
|
||||
// merge per tile poly and detail meshes
|
||||
rcPolyMesh** pmmerge = new rcPolyMesh * [TILES_PER_MAP * TILES_PER_MAP];
|
||||
rcPolyMeshDetail** dmmerge = new rcPolyMeshDetail * [TILES_PER_MAP * TILES_PER_MAP];
|
||||
int nmerge = 0;
|
||||
// build all tiles
|
||||
for (int y = 0; y < TILES_PER_MAP; ++y)
|
||||
{
|
||||
for (int x = 0; x < TILES_PER_MAP; ++x)
|
||||
{
|
||||
Tile& tile = tiles[x + y * TILES_PER_MAP];
|
||||
|
||||
// Calculate the per tile bounding box.
|
||||
tileCfg.bmin[0] = config.bmin[0] + x * float(config.tileSize * config.cs);
|
||||
tileCfg.bmin[2] = config.bmin[2] + y * float(config.tileSize * config.cs);
|
||||
tileCfg.bmax[0] = config.bmin[0] + (x + 1) * float(config.tileSize * config.cs);
|
||||
tileCfg.bmax[2] = config.bmin[2] + (y + 1) * float(config.tileSize * config.cs);
|
||||
|
||||
tileCfg.bmin[0] -= tileCfg.borderSize * tileCfg.cs;
|
||||
tileCfg.bmin[2] -= tileCfg.borderSize * tileCfg.cs;
|
||||
tileCfg.bmax[0] += tileCfg.borderSize * tileCfg.cs;
|
||||
tileCfg.bmax[2] += tileCfg.borderSize * tileCfg.cs;
|
||||
|
||||
// build heightfield
|
||||
tile.solid = rcAllocHeightfield();
|
||||
if (!tile.solid || !rcCreateHeightfield(&m_rcContext, *tile.solid, tileCfg.width, tileCfg.height, tileCfg.bmin, tileCfg.bmax, tileCfg.cs, tileCfg.ch))
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} Failed building heightfield!", tileString);
|
||||
continue;
|
||||
}
|
||||
|
||||
// mark all walkable tiles, both liquids and solids
|
||||
|
||||
/* we want to have triangles with slope less than walkableSlopeAngleNotSteep (<= 55) to have NAV_AREA_GROUND
|
||||
* and with slope between walkableSlopeAngleNotSteep and walkableSlopeAngle (55 < .. <= 70) to have NAV_AREA_GROUND_STEEP.
|
||||
* we achieve this using recast API: memset everything to NAV_AREA_GROUND_STEEP, call rcClearUnwalkableTriangles with 70 so
|
||||
* any area above that will get RC_NULL_AREA (unwalkable), then call rcMarkWalkableTriangles with 55 to set NAV_AREA_GROUND
|
||||
* on anything below 55 . Players and idle Creatures can use NAV_AREA_GROUND, while Creatures in combat can use NAV_AREA_GROUND_STEEP.
|
||||
*/
|
||||
unsigned char* triFlags = new unsigned char[tTriCount];
|
||||
memset(triFlags, NAV_AREA_GROUND_STEEP, tTriCount * sizeof(unsigned char));
|
||||
rcClearUnwalkableTriangles(&m_rcContext, tileCfg.walkableSlopeAngle, tVerts, tVertCount, tTris, tTriCount, triFlags);
|
||||
rcMarkWalkableTriangles(&m_rcContext, tileCfg.walkableSlopeAngleNotSteep, tVerts, tVertCount, tTris, tTriCount, triFlags, NAV_AREA_GROUND);
|
||||
rcRasterizeTriangles(&m_rcContext, tVerts, tVertCount, tTris, triFlags, tTriCount, *tile.solid, config.walkableClimb);
|
||||
delete[] triFlags;
|
||||
|
||||
rcFilterLowHangingWalkableObstacles(&m_rcContext, config.walkableClimb, *tile.solid);
|
||||
rcFilterLedgeSpans(&m_rcContext, tileCfg.walkableHeight, tileCfg.walkableClimb, *tile.solid);
|
||||
rcFilterWalkableLowHeightSpans(&m_rcContext, tileCfg.walkableHeight, *tile.solid);
|
||||
|
||||
// add liquid triangles
|
||||
rcRasterizeTriangles(&m_rcContext, lVerts, lVertCount, lTris, lTriFlags, lTriCount, *tile.solid, config.walkableClimb);
|
||||
|
||||
// compact heightfield spans
|
||||
tile.chf = rcAllocCompactHeightfield();
|
||||
if (!tile.chf || !rcBuildCompactHeightfield(&m_rcContext, tileCfg.walkableHeight, tileCfg.walkableClimb, *tile.solid, *tile.chf))
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} Failed compacting heightfield!", tileString);
|
||||
continue;
|
||||
}
|
||||
|
||||
// build polymesh intermediates
|
||||
if (!rcErodeWalkableArea(&m_rcContext, config.walkableRadius, *tile.chf))
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} Failed eroding area!", tileString);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!rcMedianFilterWalkableArea(&m_rcContext, *tile.chf))
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} Failed filtering area!", tileString);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!rcBuildDistanceField(&m_rcContext, *tile.chf))
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} Failed building distance field!", tileString);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!rcBuildRegions(&m_rcContext, *tile.chf, tileCfg.borderSize, tileCfg.minRegionArea, tileCfg.mergeRegionArea))
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} Failed building regions!", tileString);
|
||||
continue;
|
||||
}
|
||||
|
||||
tile.cset = rcAllocContourSet();
|
||||
if (!tile.cset || !rcBuildContours(&m_rcContext, *tile.chf, tileCfg.maxSimplificationError, tileCfg.maxEdgeLen, *tile.cset))
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} Failed building contours!", tileString);
|
||||
continue;
|
||||
}
|
||||
|
||||
// build polymesh
|
||||
tile.pmesh = rcAllocPolyMesh();
|
||||
if (!tile.pmesh || !rcBuildPolyMesh(&m_rcContext, *tile.cset, tileCfg.maxVertsPerPoly, *tile.pmesh))
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} Failed building polymesh!", tileString);
|
||||
continue;
|
||||
}
|
||||
|
||||
tile.dmesh = rcAllocPolyMeshDetail();
|
||||
if (!tile.dmesh || !rcBuildPolyMeshDetail(&m_rcContext, *tile.pmesh, *tile.chf, tileCfg.detailSampleDist, tileCfg.detailSampleMaxError, *tile.dmesh))
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} Failed building polymesh detail!", tileString);
|
||||
continue;
|
||||
}
|
||||
|
||||
// free those up
|
||||
// we may want to keep them in the future for debug
|
||||
// but right now, we don't have the code to merge them
|
||||
rcFreeHeightField(tile.solid);
|
||||
tile.solid = nullptr;
|
||||
rcFreeCompactHeightfield(tile.chf);
|
||||
tile.chf = nullptr;
|
||||
rcFreeContourSet(tile.cset);
|
||||
tile.cset = nullptr;
|
||||
|
||||
pmmerge[nmerge] = tile.pmesh;
|
||||
dmmerge[nmerge] = tile.dmesh;
|
||||
nmerge++;
|
||||
}
|
||||
}
|
||||
|
||||
iv.polyMesh = rcAllocPolyMesh();
|
||||
if (!iv.polyMesh)
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} alloc iv.polyMesh FAILED!", tileString);
|
||||
delete[] pmmerge;
|
||||
delete[] dmmerge;
|
||||
delete[] tiles;
|
||||
return;
|
||||
}
|
||||
rcMergePolyMeshes(&m_rcContext, pmmerge, nmerge, *iv.polyMesh);
|
||||
|
||||
iv.polyMeshDetail = rcAllocPolyMeshDetail();
|
||||
if (!iv.polyMeshDetail)
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} alloc m_dmesh FAILED!", tileString);
|
||||
delete[] pmmerge;
|
||||
delete[] dmmerge;
|
||||
delete[] tiles;
|
||||
return;
|
||||
}
|
||||
rcMergePolyMeshDetails(&m_rcContext, dmmerge, nmerge, *iv.polyMeshDetail);
|
||||
|
||||
// free things up
|
||||
delete[] pmmerge;
|
||||
delete[] dmmerge;
|
||||
delete[] tiles;
|
||||
|
||||
// set polygons as walkable
|
||||
// TODO: special flags for DYNAMIC polygons, ie surfaces that can be turned on and off
|
||||
for (int i = 0; i < iv.polyMesh->npolys; ++i)
|
||||
{
|
||||
if (uint8 area = iv.polyMesh->areas[i] & NAV_AREA_ALL_MASK)
|
||||
{
|
||||
if (area >= NAV_AREA_MIN_VALUE)
|
||||
iv.polyMesh->flags[i] = 1 << (NAV_AREA_MAX_VALUE - area);
|
||||
else
|
||||
iv.polyMesh->flags[i] = NAV_GROUND; // TODO: these will be dynamic in future
|
||||
}
|
||||
}
|
||||
|
||||
// setup mesh parameters
|
||||
dtNavMeshCreateParams params = {};
|
||||
params.verts = iv.polyMesh->verts;
|
||||
params.vertCount = iv.polyMesh->nverts;
|
||||
params.polys = iv.polyMesh->polys;
|
||||
params.polyAreas = iv.polyMesh->areas;
|
||||
params.polyFlags = iv.polyMesh->flags;
|
||||
params.polyCount = iv.polyMesh->npolys;
|
||||
params.nvp = iv.polyMesh->nvp;
|
||||
params.detailMeshes = iv.polyMeshDetail->meshes;
|
||||
params.detailVerts = iv.polyMeshDetail->verts;
|
||||
params.detailVertsCount = iv.polyMeshDetail->nverts;
|
||||
params.detailTris = iv.polyMeshDetail->tris;
|
||||
params.detailTriCount = iv.polyMeshDetail->ntris;
|
||||
|
||||
params.offMeshConVerts = meshData.offMeshConnections.getCArray();
|
||||
params.offMeshConCount = meshData.offMeshConnections.size() / 6;
|
||||
params.offMeshConRad = meshData.offMeshConnectionRads.getCArray();
|
||||
params.offMeshConDir = meshData.offMeshConnectionDirs.getCArray();
|
||||
params.offMeshConAreas = meshData.offMeshConnectionsAreas.getCArray();
|
||||
params.offMeshConFlags = meshData.offMeshConnectionsFlags.getCArray();
|
||||
|
||||
params.walkableHeight = BASE_UNIT_DIM * config.walkableHeight; // agent height
|
||||
params.walkableRadius = BASE_UNIT_DIM * config.walkableRadius; // agent radius
|
||||
params.walkableClimb = BASE_UNIT_DIM * config.walkableClimb; // keep less that walkableHeight (aka agent height)!
|
||||
params.tileX = (((bmin[0] + bmax[0]) / 2) - navMesh->getParams()->orig[0]) / GRID_SIZE;
|
||||
params.tileY = (((bmin[2] + bmax[2]) / 2) - navMesh->getParams()->orig[2]) / GRID_SIZE;
|
||||
rcVcopy(params.bmin, bmin);
|
||||
rcVcopy(params.bmax, bmax);
|
||||
params.cs = config.cs;
|
||||
params.ch = config.ch;
|
||||
params.tileLayer = 0;
|
||||
params.buildBvTree = true;
|
||||
|
||||
// will hold final navmesh
|
||||
unsigned char* navData = nullptr;
|
||||
int navDataSize = 0;
|
||||
|
||||
do
|
||||
{
|
||||
// these values are checked within dtCreateNavMeshData - handle them here
|
||||
// so we have a clear error message
|
||||
if (params.nvp > DT_VERTS_PER_POLYGON)
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} Invalid verts-per-polygon value!", tileString);
|
||||
break;
|
||||
}
|
||||
if (params.vertCount >= 0xffff)
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} Too many vertices!", tileString);
|
||||
break;
|
||||
}
|
||||
if (!params.vertCount || !params.verts)
|
||||
{
|
||||
// occurs mostly when adjacent tiles have models
|
||||
// loaded but those models don't span into this tile
|
||||
|
||||
// message is an annoyance
|
||||
//TC_LOG_ERROR("maps.mmapgen", "{} No vertices to build tile!", tileString);
|
||||
break;
|
||||
}
|
||||
if (!params.polyCount || !params.polys)
|
||||
{
|
||||
// we have flat tiles with no actual geometry - don't build those, its useless
|
||||
// keep in mind that we do output those into debug info
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} No polygons to build on tile!", tileString);
|
||||
break;
|
||||
}
|
||||
if (!params.detailMeshes || !params.detailVerts || !params.detailTris)
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} No detail mesh to build tile!", tileString);
|
||||
break;
|
||||
}
|
||||
|
||||
TC_LOG_DEBUG("maps.mmapgen", "{} Building navmesh tile...", tileString);
|
||||
if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize))
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} Failed building navmesh tile!", tileString);
|
||||
break;
|
||||
}
|
||||
|
||||
dtTileRef tileRef = 0;
|
||||
TC_LOG_DEBUG("maps.mmapgen", "{} Adding tile to navmesh...", tileString);
|
||||
// DT_TILE_FREE_DATA tells detour to unallocate memory when the tile
|
||||
// is removed via removeTile()
|
||||
dtStatus dtResult = navMesh->addTile(navData, navDataSize, DT_TILE_FREE_DATA, 0, &tileRef);
|
||||
if (!tileRef || !dtStatusSucceed(dtResult))
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{} Failed adding tile to navmesh!", tileString);
|
||||
break;
|
||||
}
|
||||
|
||||
// file output
|
||||
std::string fileName = Trinity::StringFormat("mmaps/{:04}{:02}{:02}.mmtile", mapID, tileY, tileX);
|
||||
FILE* file = fopen(fileName.c_str(), "wb");
|
||||
if (!file)
|
||||
{
|
||||
TC_LOG_ERROR("maps.mmapgen", "{}: [Map {:04}] Failed to open {} for writing!", strerror(errno), mapID, fileName);
|
||||
navMesh->removeTile(tileRef, nullptr, nullptr);
|
||||
break;
|
||||
}
|
||||
|
||||
TC_LOG_DEBUG("maps.mmapgen", "{} Writing to file...", tileString);
|
||||
|
||||
// write header
|
||||
MmapTileHeader header;
|
||||
header.usesLiquids = m_terrainBuilder.usesLiquids();
|
||||
header.size = uint32(navDataSize);
|
||||
fwrite(&header, sizeof(MmapTileHeader), 1, file);
|
||||
|
||||
// write data
|
||||
fwrite(navData, sizeof(unsigned char), navDataSize, file);
|
||||
fclose(file);
|
||||
|
||||
// now that tile is written to disk, we can unload it
|
||||
navMesh->removeTile(tileRef, nullptr, nullptr);
|
||||
} while (false);
|
||||
|
||||
if (m_debugOutput)
|
||||
{
|
||||
// restore padding so that the debug visualization is correct
|
||||
for (int i = 0; i < iv.polyMesh->nverts; ++i)
|
||||
{
|
||||
unsigned short* v = &iv.polyMesh->verts[i * 3];
|
||||
v[0] += (unsigned short)config.borderSize;
|
||||
v[2] += (unsigned short)config.borderSize;
|
||||
}
|
||||
|
||||
iv.generateObjFile(mapID, tileX, tileY, meshData);
|
||||
iv.writeIV(mapID, tileX, tileY);
|
||||
}
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
void TileBuilder::getTileBounds(uint32 tileX, uint32 tileY, float* verts, int vertCount, float* bmin, float* bmax)
|
||||
{
|
||||
// this is for elevation
|
||||
if (verts && vertCount)
|
||||
rcCalcBounds(verts, vertCount, bmin, bmax);
|
||||
else
|
||||
{
|
||||
bmin[1] = FLT_MIN;
|
||||
bmax[1] = FLT_MAX;
|
||||
}
|
||||
|
||||
// this is for width and depth
|
||||
bmax[0] = (32 - int(tileX)) * GRID_SIZE;
|
||||
bmax[2] = (32 - int(tileY)) * GRID_SIZE;
|
||||
bmin[0] = bmax[0] - GRID_SIZE;
|
||||
bmin[2] = bmax[2] - GRID_SIZE;
|
||||
}
|
||||
|
||||
/**************************************************************************/
|
||||
bool TileBuilder::shouldSkipTile(uint32 /*mapID*/, uint32 /*tileX*/, uint32 /*tileY*/) const
|
||||
{
|
||||
if (m_debugOutput)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
rcConfig TileBuilder::GetMapSpecificConfig(uint32 mapID, float const (&bmin)[3], float const (&bmax)[3], TileConfig const& tileConfig) const
|
||||
{
|
||||
rcConfig config { };
|
||||
|
||||
rcVcopy(config.bmin, bmin);
|
||||
rcVcopy(config.bmax, bmax);
|
||||
|
||||
config.maxVertsPerPoly = DT_VERTS_PER_POLYGON;
|
||||
config.cs = tileConfig.BASE_UNIT_DIM;
|
||||
config.ch = tileConfig.BASE_UNIT_DIM;
|
||||
// Keeping these 2 slope angles the same reduces a lot the number of polys.
|
||||
// 55 should be the minimum, maybe 70 is ok (keep in mind blink uses mmaps), 85 is too much for players
|
||||
config.walkableSlopeAngle = m_maxWalkableAngle.value_or(55.0f);
|
||||
config.walkableSlopeAngleNotSteep = m_maxWalkableAngleNotSteep.value_or(55.0f);
|
||||
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.minRegionArea = rcSqr(60);
|
||||
config.mergeRegionArea = rcSqr(50);
|
||||
config.maxSimplificationError = 1.8f; // 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;
|
||||
}
|
||||
|
||||
std::string TileBuilder::GetProgressText() const
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
84
src/tools/mmaps_generator/TileBuilder.h
Normal file
84
src/tools/mmaps_generator/TileBuilder.h
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* This file is part of the TrinityCore 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 General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TRINITYCORE_TILE_BUILDER_H
|
||||
#define TRINITYCORE_TILE_BUILDER_H
|
||||
|
||||
#include "Define.h"
|
||||
#include "StringFormat.h"
|
||||
#include "TerrainBuilder.h"
|
||||
#include <DetourNavMesh.h>
|
||||
#include <Recast.h>
|
||||
|
||||
namespace MMAP
|
||||
{
|
||||
struct TileConfig;
|
||||
|
||||
class TileBuilder
|
||||
{
|
||||
public:
|
||||
TileBuilder(Optional<float> maxWalkableAngle,
|
||||
Optional<float> maxWalkableAngleNotSteep,
|
||||
bool skipLiquid,
|
||||
bool bigBaseUnit,
|
||||
bool debugOutput,
|
||||
std::vector<OffMeshData> const* offMeshConnections);
|
||||
|
||||
TileBuilder(TileBuilder const&) = delete;
|
||||
TileBuilder(TileBuilder&&) = delete;
|
||||
|
||||
TileBuilder& operator=(TileBuilder const&) = delete;
|
||||
TileBuilder& operator=(TileBuilder&&) = delete;
|
||||
|
||||
virtual ~TileBuilder();
|
||||
|
||||
void buildTile(uint32 mapID, uint32 tileX, uint32 tileY, dtNavMesh* navMesh);
|
||||
// move map building
|
||||
void buildMoveMapTile(uint32 mapID,
|
||||
uint32 tileX,
|
||||
uint32 tileY,
|
||||
MeshData& meshData,
|
||||
float (&bmin)[3],
|
||||
float (&bmax)[3],
|
||||
dtNavMesh* navMesh);
|
||||
|
||||
virtual bool shouldSkipTile(uint32 mapID, uint32 tileX, uint32 tileY) const;
|
||||
|
||||
static void getTileBounds(uint32 tileX, uint32 tileY,
|
||||
float* verts, int vertCount,
|
||||
float* bmin, float* bmax);
|
||||
|
||||
rcConfig GetMapSpecificConfig(uint32 mapID, float const (&bmin)[3], float const (&bmax)[3], TileConfig const& tileConfig) const;
|
||||
|
||||
virtual std::string GetProgressText() const;
|
||||
|
||||
virtual void OnTileDone() { }
|
||||
|
||||
private:
|
||||
Optional<float> m_maxWalkableAngle;
|
||||
Optional<float> m_maxWalkableAngleNotSteep;
|
||||
bool m_bigBaseUnit;
|
||||
bool m_debugOutput;
|
||||
|
||||
TerrainBuilder m_terrainBuilder;
|
||||
// build performance - not really used for now
|
||||
rcContext m_rcContext;
|
||||
std::vector<OffMeshData> const* m_offMeshConnections;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // TRINITYCORE_TILE_BUILDER_H
|
||||
Reference in New Issue
Block a user