Files
Viano-Core/src/tools/mmaps_generator/MapBuilder.cpp
T
tkrokli 9e4cc26312 [3.3.5] Core/Logs: English text corrections
This is an attempt to proofread standard error messages and system messages
from both core and DB. The corrections span typos, grammar and punctuation.

Because some of these messages can have multiple meanings depending on context,
this PR will stay in "WIP" status until the corrections have been validated and approved.

You are welcome to suggest improvements and files not yet included in this list.

Thanks to @Kinzcool for suggesting this line of work. :)
2016-02-20 10:05:04 +01:00

1009 lines
36 KiB
C++

/*
* Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/>
* Copyright (C) 2005-2011 MaNGOS <http://getmangos.com/>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "PathCommon.h"
#include "MapBuilder.h"
#include "MapTree.h"
#include "DetourNavMeshBuilder.h"
#include "DetourNavMesh.h"
#include "IntermediateValues.h"
#include <limits.h>
#define MMAP_MAGIC 0x4d4d4150 // 'MMAP'
#define MMAP_VERSION 5
struct MmapTileHeader
{
uint32 mmapMagic;
uint32 dtVersion;
uint32 mmapVersion;
uint32 size;
bool usesLiquids : 1;
MmapTileHeader() : mmapMagic(MMAP_MAGIC), dtVersion(DT_NAVMESH_VERSION),
mmapVersion(MMAP_VERSION), size(0), usesLiquids(true) {}
};
namespace MMAP
{
MapBuilder::MapBuilder(float maxWalkableAngle, bool skipLiquid,
bool skipContinents, bool skipJunkMaps, bool skipBattlegrounds,
bool debugOutput, bool bigBaseUnit, const char* offMeshFilePath) :
m_terrainBuilder (NULL),
m_debugOutput (debugOutput),
m_offMeshFilePath (offMeshFilePath),
m_skipContinents (skipContinents),
m_skipJunkMaps (skipJunkMaps),
m_skipBattlegrounds (skipBattlegrounds),
m_maxWalkableAngle (maxWalkableAngle),
m_bigBaseUnit (bigBaseUnit),
m_rcContext (NULL),
_cancelationToken (false)
{
m_terrainBuilder = new TerrainBuilder(skipLiquid);
m_rcContext = new rcContext(false);
discoverTiles();
}
/**************************************************************************/
MapBuilder::~MapBuilder()
{
for (TileList::iterator it = m_tiles.begin(); it != m_tiles.end(); ++it)
{
(*it).m_tiles->clear();
delete (*it).m_tiles;
}
delete m_terrainBuilder;
delete m_rcContext;
}
/**************************************************************************/
void MapBuilder::discoverTiles()
{
std::vector<std::string> files;
uint32 mapID, tileX, tileY, tileID, count = 0;
char filter[12];
printf("Discovering maps... ");
getDirContents(files, "maps");
for (uint32 i = 0; i < files.size(); ++i)
{
mapID = uint32(atoi(files[i].substr(0,3).c_str()));
if (std::find(m_tiles.begin(), m_tiles.end(), mapID) == m_tiles.end())
{
m_tiles.emplace_back(MapTiles(mapID, new std::set<uint32>));
count++;
}
}
files.clear();
getDirContents(files, "vmaps", "*.vmtree");
for (uint32 i = 0; i < files.size(); ++i)
{
mapID = uint32(atoi(files[i].substr(0,3).c_str()));
if (std::find(m_tiles.begin(), m_tiles.end(), mapID) == m_tiles.end())
{
m_tiles.emplace_back(MapTiles(mapID, new std::set<uint32>));
count++;
}
}
printf("found %u.\n", count);
count = 0;
printf("Discovering tiles... ");
for (TileList::iterator itr = m_tiles.begin(); itr != m_tiles.end(); ++itr)
{
std::set<uint32>* tiles = (*itr).m_tiles;
mapID = (*itr).m_mapId;
sprintf(filter, "%03u*.vmtile", mapID);
files.clear();
getDirContents(files, "vmaps", filter);
for (uint32 i = 0; i < files.size(); ++i)
{
tileX = uint32(atoi(files[i].substr(7,2).c_str()));
tileY = uint32(atoi(files[i].substr(4,2).c_str()));
tileID = StaticMapTree::packTileID(tileY, tileX);
tiles->insert(tileID);
count++;
}
sprintf(filter, "%03u*", mapID);
files.clear();
getDirContents(files, "maps", filter);
for (uint32 i = 0; i < files.size(); ++i)
{
tileY = uint32(atoi(files[i].substr(3,2).c_str()));
tileX = uint32(atoi(files[i].substr(5,2).c_str()));
tileID = StaticMapTree::packTileID(tileX, tileY);
if (tiles->insert(tileID).second)
count++;
}
}
printf("found %u.\n\n", count);
}
/**************************************************************************/
std::set<uint32>* MapBuilder::getTileList(uint32 mapID)
{
TileList::iterator itr = std::find(m_tiles.begin(), m_tiles.end(), mapID);
if (itr != m_tiles.end())
return (*itr).m_tiles;
std::set<uint32>* tiles = new std::set<uint32>();
m_tiles.emplace_back(MapTiles(mapID, tiles));
return tiles;
}
/**************************************************************************/
void MapBuilder::WorkerThread()
{
while (1)
{
uint32 mapId = 0;
_queue.WaitAndPop(mapId);
if (_cancelationToken)
return;
buildMap(mapId);
}
}
void MapBuilder::buildAllMaps(int threads)
{
for (int i = 0; i < threads; ++i)
{
_workerThreads.push_back(std::thread(&MapBuilder::WorkerThread, this));
}
m_tiles.sort([](MapTiles a, MapTiles b)
{
return a.m_tiles->size() > b.m_tiles->size();
});
for (TileList::iterator it = m_tiles.begin(); it != m_tiles.end(); ++it)
{
uint32 mapId = it->m_mapId;
if (!shouldSkipMap(mapId))
{
if (threads > 0)
_queue.Push(mapId);
else
buildMap(mapId);
}
}
while (!_queue.Empty())
{
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
_cancelationToken = true;
_queue.Cancel();
for (auto& thread : _workerThreads)
{
thread.join();
}
}
/**************************************************************************/
void MapBuilder::getGridBounds(uint32 mapID, uint32 &minX, uint32 &minY, uint32 &maxX, uint32 &maxY) const
{
// min and max are initialized to invalid values so the caller iterating the [min, max] range
// will never enter the loop unless valid min/max values are found
maxX = 0;
maxY = 0;
minX = std::numeric_limits<uint32>::max();
minY = std::numeric_limits<uint32>::max();
float bmin[3] = { 0, 0, 0 };
float bmax[3] = { 0, 0, 0 };
float lmin[3] = { 0, 0, 0 };
float lmax[3] = { 0, 0, 0 };
MeshData meshData;
// make sure we process maps which don't have tiles
// initialize the static tree, which loads WDT models
if (!m_terrainBuilder->loadVMap(mapID, 64, 64, meshData))
return;
// get the coord bounds of the model data
if (meshData.solidVerts.size() + meshData.liquidVerts.size() == 0)
return;
// get the coord bounds of the model data
if (meshData.solidVerts.size() && meshData.liquidVerts.size())
{
rcCalcBounds(meshData.solidVerts.getCArray(), meshData.solidVerts.size() / 3, bmin, bmax);
rcCalcBounds(meshData.liquidVerts.getCArray(), meshData.liquidVerts.size() / 3, lmin, lmax);
rcVmin(bmin, lmin);
rcVmax(bmax, lmax);
}
else if (meshData.solidVerts.size())
rcCalcBounds(meshData.solidVerts.getCArray(), meshData.solidVerts.size() / 3, bmin, bmax);
else
rcCalcBounds(meshData.liquidVerts.getCArray(), meshData.liquidVerts.size() / 3, lmin, lmax);
// convert coord bounds to grid bounds
maxX = 32 - bmin[0] / GRID_SIZE;
maxY = 32 - bmin[2] / GRID_SIZE;
minX = 32 - bmax[0] / GRID_SIZE;
minY = 32 - bmax[2] / GRID_SIZE;
}
void MapBuilder::buildMeshFromFile(char* name)
{
FILE* file = fopen(name, "rb");
if (!file)
return;
printf("Building mesh from file\n");
int tileX, tileY, mapId;
if (fread(&mapId, sizeof(int), 1, file) != 1)
{
fclose(file);
return;
}
if (fread(&tileX, sizeof(int), 1, file) != 1)
{
fclose(file);
return;
}
if (fread(&tileY, sizeof(int), 1, file) != 1)
{
fclose(file);
return;
}
dtNavMesh* navMesh = NULL;
buildNavMesh(mapId, navMesh);
if (!navMesh)
{
printf("Failed creating navmesh! \n");
fclose(file);
return;
}
uint32 verticesCount, indicesCount;
if (fread(&verticesCount, sizeof(uint32), 1, file) != 1)
{
fclose(file);
return;
}
if (fread(&indicesCount, sizeof(uint32), 1, file) != 1)
{
fclose(file);
return;
}
float* verts = new float[verticesCount];
int* inds = new int[indicesCount];
if (fread(verts, sizeof(float), verticesCount, file) != verticesCount)
{
fclose(file);
delete[] verts;
delete[] inds;
return;
}
if (fread(inds, sizeof(int), indicesCount, file) != indicesCount)
{
fclose(file);
delete[] verts;
delete[] inds;
return;
}
MeshData data;
for (uint32 i = 0; i < verticesCount; ++i)
data.solidVerts.append(verts[i]);
delete[] verts;
for (uint32 i = 0; i < indicesCount; ++i)
data.solidTris.append(inds[i]);
delete[] inds;
TerrainBuilder::cleanVertices(data.solidVerts, data.solidTris);
// get bounds of current tile
float bmin[3], bmax[3];
getTileBounds(tileX, tileY, data.solidVerts.getCArray(), data.solidVerts.size() / 3, bmin, bmax);
// build navmesh tile
buildMoveMapTile(mapId, tileX, tileY, data, bmin, bmax, navMesh);
fclose(file);
}
/**************************************************************************/
void MapBuilder::buildSingleTile(uint32 mapID, uint32 tileX, uint32 tileY)
{
dtNavMesh* navMesh = NULL;
buildNavMesh(mapID, navMesh);
if (!navMesh)
{
printf("Failed creating navmesh! \n");
return;
}
buildTile(mapID, tileX, tileY, navMesh);
dtFreeNavMesh(navMesh);
}
/**************************************************************************/
void MapBuilder::buildMap(uint32 mapID)
{
#ifndef __APPLE__
//printf("[Thread %u] Building map %03u:\n", uint32(ACE_Thread::self()), mapID);
#endif
std::set<uint32>* tiles = getTileList(mapID);
// make sure we process maps which don't have tiles
if (!tiles->size())
{
// convert coord bounds to grid bounds
uint32 minX, minY, maxX, maxY;
getGridBounds(mapID, minX, minY, maxX, maxY);
// add all tiles within bounds to tile list.
for (uint32 i = minX; i <= maxX; ++i)
for (uint32 j = minY; j <= maxY; ++j)
tiles->insert(StaticMapTree::packTileID(i, j));
}
if (!tiles->empty())
{
// build navMesh
dtNavMesh* navMesh = NULL;
buildNavMesh(mapID, navMesh);
if (!navMesh)
{
printf("[Map %03i] Failed creating navmesh!\n", mapID);
return;
}
// now start building mmtiles for each tile
printf("[Map %03i] We have %u tiles. \n", mapID, (unsigned int)tiles->size());
for (std::set<uint32>::iterator it = tiles->begin(); it != tiles->end(); ++it)
{
uint32 tileX, tileY;
// unpack tile coords
StaticMapTree::unpackTileID((*it), tileX, tileY);
if (shouldSkipTile(mapID, tileX, tileY))
continue;
buildTile(mapID, tileX, tileY, navMesh);
}
dtFreeNavMesh(navMesh);
}
printf("[Map %03i] Complete!\n", mapID);
}
/**************************************************************************/
void MapBuilder::buildTile(uint32 mapID, uint32 tileX, uint32 tileY, dtNavMesh* navMesh)
{
printf("[Map %03i] Building tile [%02u,%02u]\n", mapID, tileX, tileY);
MeshData meshData;
// get heightmap data
m_terrainBuilder->loadMap(mapID, tileX, tileY, meshData);
// get model data
m_terrainBuilder->loadVMap(mapID, tileY, tileX, meshData);
// if there is no data, give up now
if (!meshData.solidVerts.size() && !meshData.liquidVerts.size())
return;
// remove unused vertices
TerrainBuilder::cleanVertices(meshData.solidVerts, meshData.solidTris);
TerrainBuilder::cleanVertices(meshData.liquidVerts, meshData.liquidTris);
// gather all mesh data for final data check, and bounds calculation
G3D::Array<float> allVerts;
allVerts.append(meshData.liquidVerts);
allVerts.append(meshData.solidVerts);
if (!allVerts.size())
return;
// get bounds of current tile
float bmin[3], bmax[3];
getTileBounds(tileX, tileY, allVerts.getCArray(), allVerts.size() / 3, bmin, bmax);
m_terrainBuilder->loadOffMeshConnections(mapID, tileX, tileY, meshData, m_offMeshFilePath);
// build navmesh tile
buildMoveMapTile(mapID, tileX, tileY, meshData, bmin, bmax, navMesh);
}
/**************************************************************************/
void MapBuilder::buildNavMesh(uint32 mapID, dtNavMesh* &navMesh)
{
std::set<uint32>* tiles = getTileList(mapID);
// old code for non-statically assigned bitmask sizes:
///*** calculate number of bits needed to store tiles & polys ***/
//int tileBits = dtIlog2(dtNextPow2(tiles->size()));
//if (tileBits < 1) tileBits = 1; // need at least one bit!
//int polyBits = sizeof(dtPolyRef)*8 - SALT_MIN_BITS - tileBits;
int polyBits = STATIC_POLY_BITS;
int maxTiles = tiles->size();
int maxPolysPerTile = 1 << polyBits;
/*** calculate bounds of map ***/
uint32 tileXMin = 64, tileYMin = 64, tileXMax = 0, tileYMax = 0, tileX, tileY;
for (std::set<uint32>::iterator it = tiles->begin(); it != tiles->end(); ++it)
{
StaticMapTree::unpackTileID(*it, tileX, tileY);
if (tileX > tileXMax)
tileXMax = tileX;
else if (tileX < tileXMin)
tileXMin = tileX;
if (tileY > tileYMax)
tileYMax = tileY;
else if (tileY < tileYMin)
tileYMin = tileY;
}
// use Max because '32 - tileX' is negative for values over 32
float bmin[3], bmax[3];
getTileBounds(tileXMax, tileYMax, NULL, 0, bmin, bmax);
/*** now create the navmesh ***/
// navmesh creation params
dtNavMeshParams navMeshParams;
memset(&navMeshParams, 0, sizeof(dtNavMeshParams));
navMeshParams.tileWidth = GRID_SIZE;
navMeshParams.tileHeight = GRID_SIZE;
rcVcopy(navMeshParams.orig, bmin);
navMeshParams.maxTiles = maxTiles;
navMeshParams.maxPolys = maxPolysPerTile;
navMesh = dtAllocNavMesh();
printf("[Map %03i] Creating navMesh...\n", mapID);
if (!navMesh->init(&navMeshParams))
{
printf("[Map %03i] Failed creating navmesh! \n", mapID);
return;
}
char fileName[25];
sprintf(fileName, "mmaps/%03u.mmap", mapID);
FILE* file = fopen(fileName, "wb");
if (!file)
{
dtFreeNavMesh(navMesh);
char message[1024];
sprintf(message, "[Map %03i] Failed to open %s for writing!\n", mapID, fileName);
perror(message);
return;
}
// now that we know navMesh params are valid, we can write them to file
fwrite(&navMeshParams, sizeof(dtNavMeshParams), 1, file);
fclose(file);
}
/**************************************************************************/
void MapBuilder::buildMoveMapTile(uint32 mapID, uint32 tileX, uint32 tileY,
MeshData &meshData, float bmin[3], float bmax[3],
dtNavMesh* navMesh)
{
// console output
char tileString[20];
sprintf(tileString, "[Map %03i] [%02i,%02i]: ", mapID, tileX, tileY);
printf("%s Building movemap tiles...\n", tileString);
IntermediateValues iv;
float* tVerts = meshData.solidVerts.getCArray();
int tVertCount = meshData.solidVerts.size() / 3;
int* tTris = meshData.solidTris.getCArray();
int tTriCount = meshData.solidTris.size() / 3;
float* lVerts = meshData.liquidVerts.getCArray();
int lVertCount = meshData.liquidVerts.size() / 3;
int* lTris = meshData.liquidTris.getCArray();
int lTriCount = meshData.liquidTris.size() / 3;
uint8* lTriFlags = meshData.liquidType.getCArray();
// these are WORLD UNIT based metrics
// this are basic unit dimentions
// value have to divide GRID_SIZE(533.3333f) ( aka: 0.5333, 0.2666, 0.3333, 0.1333, etc )
const static float BASE_UNIT_DIM = m_bigBaseUnit ? 0.5333333f : 0.2666666f;
// All are in UNIT metrics!
const static int VERTEX_PER_MAP = int(GRID_SIZE/BASE_UNIT_DIM + 0.5f);
const static int VERTEX_PER_TILE = m_bigBaseUnit ? 40 : 80; // must divide VERTEX_PER_MAP
const static int TILES_PER_MAP = VERTEX_PER_MAP/VERTEX_PER_TILE;
rcConfig config;
memset(&config, 0, sizeof(rcConfig));
rcVcopy(config.bmin, bmin);
rcVcopy(config.bmax, bmax);
config.maxVertsPerPoly = DT_VERTS_PER_POLYGON;
config.cs = BASE_UNIT_DIM;
config.ch = BASE_UNIT_DIM;
config.walkableSlopeAngle = m_maxWalkableAngle;
config.tileSize = VERTEX_PER_TILE;
config.walkableRadius = m_bigBaseUnit ? 1 : 2;
config.borderSize = config.walkableRadius + 3;
config.maxEdgeLen = VERTEX_PER_TILE + 1; // anything bigger than tileSize
config.walkableHeight = m_bigBaseUnit ? 3 : 6;
// 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 ? 4 : 8;
config.minRegionArea = rcSqr(60);
config.mergeRegionArea = rcSqr(50);
config.maxSimplificationError = 1.8f; // eliminates most jagged edges (tiny polygons)
config.detailSampleDist = config.cs * 64;
config.detailSampleMaxError = config.ch * 2;
// this sets the dimensions of the heightfield - should maybe happen before border padding
rcCalcGridSize(config.bmin, config.bmax, config.cs, &config.width, &config.height);
// allocate subregions : tiles
Tile* tiles = new Tile[TILES_PER_MAP * TILES_PER_MAP];
// Initialize per tile config.
rcConfig tileCfg = config;
tileCfg.width = config.tileSize + config.borderSize*2;
tileCfg.height = config.tileSize + config.borderSize*2;
// merge per tile poly and detail meshes
rcPolyMesh** pmmerge = new rcPolyMesh*[TILES_PER_MAP * TILES_PER_MAP];
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] + float(x*config.tileSize - config.borderSize)*config.cs;
tileCfg.bmin[2] = config.bmin[2] + float(y*config.tileSize - config.borderSize)*config.cs;
tileCfg.bmax[0] = config.bmin[0] + float((x+1)*config.tileSize + config.borderSize)*config.cs;
tileCfg.bmax[2] = config.bmin[2] + float((y+1)*config.tileSize + config.borderSize)*config.cs;
// build heightfield
tile.solid = rcAllocHeightfield();
if (!tile.solid || !rcCreateHeightfield(m_rcContext, *tile.solid, tileCfg.width, tileCfg.height, tileCfg.bmin, tileCfg.bmax, tileCfg.cs, tileCfg.ch))
{
printf("%s Failed building heightfield! \n", tileString);
continue;
}
// mark all walkable tiles, both liquids and solids
unsigned char* triFlags = new unsigned char[tTriCount];
memset(triFlags, NAV_GROUND, tTriCount*sizeof(unsigned char));
rcClearUnwalkableTriangles(m_rcContext, tileCfg.walkableSlopeAngle, tVerts, tVertCount, tTris, tTriCount, triFlags);
rcRasterizeTriangles(m_rcContext, tVerts, tVertCount, tTris, triFlags, tTriCount, *tile.solid, config.walkableClimb);
delete[] triFlags;
rcFilterLowHangingWalkableObstacles(m_rcContext, config.walkableClimb, *tile.solid);
rcFilterLedgeSpans(m_rcContext, tileCfg.walkableHeight, tileCfg.walkableClimb, *tile.solid);
rcFilterWalkableLowHeightSpans(m_rcContext, tileCfg.walkableHeight, *tile.solid);
rcRasterizeTriangles(m_rcContext, lVerts, lVertCount, lTris, lTriFlags, lTriCount, *tile.solid, config.walkableClimb);
// compact heightfield spans
tile.chf = rcAllocCompactHeightfield();
if (!tile.chf || !rcBuildCompactHeightfield(m_rcContext, tileCfg.walkableHeight, tileCfg.walkableClimb, *tile.solid, *tile.chf))
{
printf("%s Failed compacting heightfield! \n", tileString);
continue;
}
// build polymesh intermediates
if (!rcErodeWalkableArea(m_rcContext, config.walkableRadius, *tile.chf))
{
printf("%s Failed eroding area! \n", tileString);
continue;
}
if (!rcBuildDistanceField(m_rcContext, *tile.chf))
{
printf("%s Failed building distance field! \n", tileString);
continue;
}
if (!rcBuildRegions(m_rcContext, *tile.chf, tileCfg.borderSize, tileCfg.minRegionArea, tileCfg.mergeRegionArea))
{
printf("%s Failed building regions! \n", tileString);
continue;
}
tile.cset = rcAllocContourSet();
if (!tile.cset || !rcBuildContours(m_rcContext, *tile.chf, tileCfg.maxSimplificationError, tileCfg.maxEdgeLen, *tile.cset))
{
printf("%s Failed building contours! \n", tileString);
continue;
}
// build polymesh
tile.pmesh = rcAllocPolyMesh();
if (!tile.pmesh || !rcBuildPolyMesh(m_rcContext, *tile.cset, tileCfg.maxVertsPerPoly, *tile.pmesh))
{
printf("%s Failed building polymesh! \n", tileString);
continue;
}
tile.dmesh = rcAllocPolyMeshDetail();
if (!tile.dmesh || !rcBuildPolyMeshDetail(m_rcContext, *tile.pmesh, *tile.chf, tileCfg.detailSampleDist, tileCfg.detailSampleMaxError, *tile.dmesh))
{
printf("%s Failed building polymesh detail! \n", tileString);
continue;
}
// free those up
// we may want to keep them in the future for debug
// but right now, we don't have the code to merge them
rcFreeHeightField(tile.solid);
tile.solid = NULL;
rcFreeCompactHeightfield(tile.chf);
tile.chf = NULL;
rcFreeContourSet(tile.cset);
tile.cset = NULL;
pmmerge[nmerge] = tile.pmesh;
dmmerge[nmerge] = tile.dmesh;
nmerge++;
}
}
iv.polyMesh = rcAllocPolyMesh();
if (!iv.polyMesh)
{
printf("%s alloc iv.polyMesh FAILED!\n", tileString);
delete[] pmmerge;
delete[] dmmerge;
delete[] tiles;
return;
}
rcMergePolyMeshes(m_rcContext, pmmerge, nmerge, *iv.polyMesh);
iv.polyMeshDetail = rcAllocPolyMeshDetail();
if (!iv.polyMeshDetail)
{
printf("%s alloc m_dmesh FAILED!\n", 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 (iv.polyMesh->areas[i] & RC_WALKABLE_AREA)
iv.polyMesh->flags[i] = iv.polyMesh->areas[i];
// setup mesh parameters
dtNavMeshCreateParams params;
memset(&params, 0, sizeof(params));
params.verts = iv.polyMesh->verts;
params.vertCount = iv.polyMesh->nverts;
params.polys = iv.polyMesh->polys;
params.polyAreas = iv.polyMesh->areas;
params.polyFlags = iv.polyMesh->flags;
params.polyCount = iv.polyMesh->npolys;
params.nvp = iv.polyMesh->nvp;
params.detailMeshes = iv.polyMeshDetail->meshes;
params.detailVerts = iv.polyMeshDetail->verts;
params.detailVertsCount = iv.polyMeshDetail->nverts;
params.detailTris = iv.polyMeshDetail->tris;
params.detailTriCount = iv.polyMeshDetail->ntris;
params.offMeshConVerts = meshData.offMeshConnections.getCArray();
params.offMeshConCount = meshData.offMeshConnections.size()/6;
params.offMeshConRad = meshData.offMeshConnectionRads.getCArray();
params.offMeshConDir = meshData.offMeshConnectionDirs.getCArray();
params.offMeshConAreas = meshData.offMeshConnectionsAreas.getCArray();
params.offMeshConFlags = meshData.offMeshConnectionsFlags.getCArray();
params.walkableHeight = BASE_UNIT_DIM*config.walkableHeight; // agent height
params.walkableRadius = BASE_UNIT_DIM*config.walkableRadius; // agent radius
params.walkableClimb = BASE_UNIT_DIM*config.walkableClimb; // keep less that walkableHeight (aka agent height)!
params.tileX = (((bmin[0] + bmax[0]) / 2) - navMesh->getParams()->orig[0]) / GRID_SIZE;
params.tileY = (((bmin[2] + bmax[2]) / 2) - navMesh->getParams()->orig[2]) / GRID_SIZE;
rcVcopy(params.bmin, bmin);
rcVcopy(params.bmax, bmax);
params.cs = config.cs;
params.ch = config.ch;
params.tileLayer = 0;
params.buildBvTree = true;
// will hold final navmesh
unsigned char* navData = NULL;
int navDataSize = 0;
do
{
// these values are checked within dtCreateNavMeshData - handle them here
// so we have a clear error message
if (params.nvp > DT_VERTS_PER_POLYGON)
{
printf("%s Invalid verts-per-polygon value! \n", tileString);
break;
}
if (params.vertCount >= 0xffff)
{
printf("%s Too many vertices! \n", 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
//printf("%sNo vertices to build tile! \n", tileString);
break;
}
if (!params.polyCount || !params.polys ||
TILES_PER_MAP*TILES_PER_MAP == params.polyCount)
{
// we have flat tiles with no actual geometry - don't build those, its useless
// keep in mind that we do output those into debug info
// drop tiles with only exact count - some tiles may have geometry while having less tiles
printf("%s No polygons to build on tile! \n", tileString);
break;
}
if (!params.detailMeshes || !params.detailVerts || !params.detailTris)
{
printf("%s No detail mesh to build tile! \n", tileString);
break;
}
printf("%s Building navmesh tile...\n", tileString);
if (!dtCreateNavMeshData(&params, &navData, &navDataSize))
{
printf("%s Failed building navmesh tile! \n", tileString);
break;
}
dtTileRef tileRef = 0;
printf("%s Adding tile to navmesh...\n", tileString);
// DT_TILE_FREE_DATA tells detour to unallocate memory when the tile
// is removed via removeTile()
dtStatus dtResult = navMesh->addTile(navData, navDataSize, DT_TILE_FREE_DATA, 0, &tileRef);
if (!tileRef || dtResult != DT_SUCCESS)
{
printf("%s Failed adding tile to navmesh! \n", tileString);
break;
}
// file output
char fileName[255];
sprintf(fileName, "mmaps/%03u%02i%02i.mmtile", mapID, tileY, tileX);
FILE* file = fopen(fileName, "wb");
if (!file)
{
char message[1024];
sprintf(message, "[Map %03i] Failed to open %s for writing!\n", mapID, fileName);
perror(message);
navMesh->removeTile(tileRef, NULL, NULL);
break;
}
printf("%s Writing to file...\n", tileString);
// write header
MmapTileHeader header;
header.usesLiquids = m_terrainBuilder->usesLiquids();
header.size = uint32(navDataSize);
fwrite(&header, sizeof(MmapTileHeader), 1, file);
// write data
fwrite(navData, sizeof(unsigned char), navDataSize, file);
fclose(file);
// now that tile is written to disk, we can unload it
navMesh->removeTile(tileRef, NULL, NULL);
}
while (0);
if (m_debugOutput)
{
// restore padding so that the debug visualization is correct
for (int i = 0; i < iv.polyMesh->nverts; ++i)
{
unsigned short* v = &iv.polyMesh->verts[i*3];
v[0] += (unsigned short)config.borderSize;
v[2] += (unsigned short)config.borderSize;
}
iv.generateObjFile(mapID, tileX, tileY, meshData);
iv.writeIV(mapID, tileX, tileY);
}
}
/**************************************************************************/
void MapBuilder::getTileBounds(uint32 tileX, uint32 tileY, float* verts, int vertCount, float* bmin, float* bmax)
{
// this is for elevation
if (verts && vertCount)
rcCalcBounds(verts, vertCount, bmin, bmax);
else
{
bmin[1] = FLT_MIN;
bmax[1] = FLT_MAX;
}
// this is for width and depth
bmax[0] = (32 - int(tileX)) * GRID_SIZE;
bmax[2] = (32 - int(tileY)) * GRID_SIZE;
bmin[0] = bmax[0] - GRID_SIZE;
bmin[2] = bmax[2] - GRID_SIZE;
}
/**************************************************************************/
bool MapBuilder::shouldSkipMap(uint32 mapID)
{
if (m_skipContinents)
switch (mapID)
{
case 0:
case 1:
case 530:
case 571:
return true;
default:
break;
}
if (m_skipJunkMaps)
switch (mapID)
{
case 13: // test.wdt
case 25: // ScottTest.wdt
case 29: // Test.wdt
case 42: // Colin.wdt
case 169: // EmeraldDream.wdt (unused, and very large)
case 451: // development.wdt
case 573: // ExteriorTest.wdt
case 597: // CraigTest.wdt
case 605: // development_nonweighted.wdt
case 606: // QA_DVD.wdt
return true;
default:
if (isTransportMap(mapID))
return true;
break;
}
if (m_skipBattlegrounds)
switch (mapID)
{
case 30: // AV
case 37: // ?
case 489: // WSG
case 529: // AB
case 566: // EotS
case 607: // SotA
case 628: // IoC
return true;
default:
break;
}
return false;
}
/**************************************************************************/
bool MapBuilder::isTransportMap(uint32 mapID)
{
switch (mapID)
{
// transport maps
case 582:
case 584:
case 586:
case 587:
case 588:
case 589:
case 590:
case 591:
case 592:
case 593:
case 594:
case 596:
case 610:
case 612:
case 613:
case 614:
case 620:
case 621:
case 622:
case 623:
case 641:
case 642:
case 647:
case 672:
case 673:
case 712:
case 713:
case 718:
return true;
default:
return false;
}
}
/**************************************************************************/
bool MapBuilder::shouldSkipTile(uint32 mapID, uint32 tileX, uint32 tileY)
{
char fileName[255];
sprintf(fileName, "mmaps/%03u%02i%02i.mmtile", mapID, tileY, tileX);
FILE* file = fopen(fileName, "rb");
if (!file)
return false;
MmapTileHeader header;
int count = fread(&header, sizeof(MmapTileHeader), 1, file);
fclose(file);
if (count != 1)
return false;
if (header.mmapMagic != MMAP_MAGIC || header.dtVersion != uint32(DT_NAVMESH_VERSION))
return false;
if (header.mmapVersion != MMAP_VERSION)
return false;
return true;
}
}