362 lines
14 KiB
C++
362 lines
14 KiB
C++
#include "precompile.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include "navmeshbuilder.h"
|
|
#include "mapinstance.h"
|
|
#include "collider.h"
|
|
#include "entity.h"
|
|
#include "metamgr.h"
|
|
|
|
#include "navmeshhelper.h"
|
|
|
|
void NavMeshBuilder::Init()
|
|
{
|
|
|
|
}
|
|
|
|
void NavMeshBuilder::UnInit()
|
|
{
|
|
|
|
}
|
|
|
|
dtNavMesh* NavMeshBuilder::Build(MapInstance* map_instance)
|
|
{
|
|
BuilderParams builder_params;
|
|
InitBuilderParams(builder_params);
|
|
CreateTileCache(builder_params);
|
|
CreateNavMesh(builder_params);
|
|
BuildTiles(builder_params);
|
|
return builder_params.navmesh;
|
|
}
|
|
|
|
void NavMeshBuilder::InitBuilderParams(BuilderParams& builder_params)
|
|
{
|
|
// Init cache
|
|
rcCalcGridSize(builder_params.gemo->GetMeshBoundsMin(),
|
|
builder_params.gemo->GetMeshBoundsMax(),
|
|
builder_params.kCellSize,
|
|
&builder_params.grid_width,
|
|
&builder_params.grid_height);
|
|
builder_params.tile_size = (int)builder_params.kTileSize;
|
|
builder_params.tile_width = (builder_params.grid_width + builder_params.tile_size-1) /
|
|
builder_params.tile_size;
|
|
builder_params.tile_height = (builder_params.grid_height + builder_params.tile_size-1) /
|
|
builder_params.tile_size;
|
|
}
|
|
|
|
void NavMeshBuilder::InitTileCacheParams(BuilderParams& builder_params, dtTileCacheParams& tcparams)
|
|
{
|
|
// Tile cache params.
|
|
memset(&tcparams, 0, sizeof(tcparams));
|
|
rcVcopy(tcparams.orig, builder_params.gemo->GetMeshBoundsMin());
|
|
tcparams.cs = builder_params.kCellSize;
|
|
tcparams.ch = builder_params.kCellHeight;
|
|
tcparams.width = (int)builder_params.kTileSize;
|
|
tcparams.height = (int)builder_params.kTileSize;
|
|
tcparams.walkableHeight = builder_params.kAgentHeight;
|
|
tcparams.walkableRadius = builder_params.kAgentRadius;
|
|
tcparams.walkableClimb = builder_params.kAgentMaxClimb;
|
|
tcparams.maxSimplificationError = builder_params.kEdgeMaxError;
|
|
tcparams.maxTiles = builder_params.tile_width * builder_params.tile_height * EXPECTED_LAYERS_PER_TILE;
|
|
tcparams.maxObstacles = 128;
|
|
}
|
|
|
|
void NavMeshBuilder::InitNavMeshParams(BuilderParams& builder_params, dtNavMeshParams& params)
|
|
{
|
|
memset(¶ms, 0, sizeof(params));
|
|
rcVcopy(params.orig, builder_params.gemo->GetMeshBoundsMin());
|
|
params.tileWidth = builder_params.kTileSize * builder_params.kCellSize;
|
|
params.tileHeight = builder_params.kTileSize * builder_params.kCellSize;
|
|
params.maxTiles = builder_params.kMaxTiles;
|
|
params.maxPolys = builder_params.kMaxPolysPerTile;
|
|
}
|
|
|
|
void NavMeshBuilder::BuildTiles(BuilderParams& builder_params)
|
|
{
|
|
rcConfig cfg;
|
|
{
|
|
memset(&cfg, 0, sizeof(cfg));
|
|
cfg.cs = builder_params.kCellSize;
|
|
cfg.ch = builder_params.kCellHeight;
|
|
cfg.walkableSlopeAngle = builder_params.kAgentMaxSlope;
|
|
cfg.walkableHeight = (int)ceilf(builder_params.kAgentHeight / cfg.ch);
|
|
cfg.walkableClimb = (int)floorf(builder_params.kAgentMaxClimb / cfg.ch);
|
|
cfg.walkableRadius = (int)ceilf(builder_params.kAgentRadius / cfg.cs);
|
|
cfg.maxEdgeLen = (int)(builder_params.kEdgeMaxLen / builder_params.kCellSize);
|
|
cfg.maxSimplificationError = builder_params.kEdgeMaxError;
|
|
cfg.minRegionArea = (int)rcSqr(builder_params.kRegionMinSize); // Note: area = size*size
|
|
cfg.mergeRegionArea = (int)rcSqr(builder_params.kRegionMergeSize); // Note: area = size*size
|
|
cfg.maxVertsPerPoly = (int)builder_params.kVertsPerPoly;
|
|
cfg.tileSize = (int)builder_params.kTileSize;
|
|
cfg.borderSize = cfg.walkableRadius + 3; // Reserve enough padding.
|
|
cfg.width = cfg.tileSize + cfg.borderSize*2;
|
|
cfg.height = cfg.tileSize + cfg.borderSize*2;
|
|
cfg.detailSampleDist = builder_params.kDetailSampleDist < 0.9f ? 0 : builder_params.kCellSize * builder_params.kDetailSampleDist;
|
|
cfg.detailSampleMaxError = builder_params.kCellHeight * builder_params.kDetailSampleMaxError;
|
|
rcVcopy(cfg.bmin, builder_params.gemo->GetMeshBoundsMin());
|
|
rcVcopy(cfg.bmax, builder_params.gemo->GetMeshBoundsMax());
|
|
}
|
|
|
|
for (int y = 0; y < builder_params.tile_height; ++y) {
|
|
for (int x = 0; x < builder_params.tile_width; ++x) {
|
|
TileCacheData tiles[MAX_LAYERS];
|
|
memset(tiles, 0, sizeof(tiles));
|
|
int ntiles = RasterizeTileLayers(builder_params, x, y, cfg, tiles, MAX_LAYERS);
|
|
|
|
for (int i = 0; i < ntiles; ++i) {
|
|
TileCacheData* tile = &tiles[i];
|
|
dtStatus status = builder_params.tile_cache->addTile(tile->data,
|
|
tile->dataSize,
|
|
DT_COMPRESSEDTILE_FREE_DATA,
|
|
0);
|
|
if (dtStatusFailed(status)) {
|
|
dtFree(tile->data);
|
|
tile->data = 0;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build initial meshes
|
|
for (int y = 0; y < builder_params.tile_height; ++y) {
|
|
for (int x = 0; x < builder_params.tile_width; ++x) {
|
|
builder_params.tile_cache->buildNavMeshTilesAt(x,y, builder_params.navmesh);
|
|
}
|
|
}
|
|
}
|
|
|
|
int NavMeshBuilder::RasterizeTileLayers(BuilderParams& builder_params,
|
|
const int tx,
|
|
const int ty,
|
|
const rcConfig& cfg,
|
|
TileCacheData* tiles,
|
|
const int maxTiles)
|
|
{
|
|
rcAreaModification SAMPLE_AREAMOD_GROUND(SAMPLE_POLYAREA_TYPE_GROUND, SAMPLE_POLYAREA_TYPE_MASK);
|
|
|
|
FastLZCompressor comp;
|
|
RasterizationContext rc;
|
|
|
|
const float* verts = builder_params.gemo->GetVerts();
|
|
const int nverts = builder_params.gemo->GetVertCount();
|
|
const rcChunkyTriMesh* chunkyMesh = builder_params.gemo->GetChunkyMesh();
|
|
|
|
// Tile bounds.
|
|
const float tcs = cfg.tileSize * cfg.cs;
|
|
|
|
rcConfig tcfg;
|
|
memcpy(&tcfg, &cfg, sizeof(tcfg));
|
|
|
|
tcfg.bmin[0] = cfg.bmin[0] + tx*tcs;
|
|
tcfg.bmin[1] = cfg.bmin[1];
|
|
tcfg.bmin[2] = cfg.bmin[2] + ty*tcs;
|
|
tcfg.bmax[0] = cfg.bmin[0] + (tx+1)*tcs;
|
|
tcfg.bmax[1] = cfg.bmax[1];
|
|
tcfg.bmax[2] = cfg.bmin[2] + (ty+1)*tcs;
|
|
tcfg.bmin[0] -= tcfg.borderSize*tcfg.cs;
|
|
tcfg.bmin[2] -= tcfg.borderSize*tcfg.cs;
|
|
tcfg.bmax[0] += tcfg.borderSize*tcfg.cs;
|
|
tcfg.bmax[2] += tcfg.borderSize*tcfg.cs;
|
|
|
|
// Allocate voxel heightfield where we rasterize our input data to.
|
|
rc.solid = rcAllocHeightfield();
|
|
if (!rc.solid) {
|
|
return 0;
|
|
}
|
|
if (!rcCreateHeightfield(builder_params.ctx,
|
|
*rc.solid,
|
|
tcfg.width,
|
|
tcfg.height,
|
|
tcfg.bmin,
|
|
tcfg.bmax,
|
|
tcfg.cs,
|
|
tcfg.ch)) {
|
|
return 0;
|
|
}
|
|
|
|
// Allocate array that can hold triangle flags.
|
|
// If you have multiple meshes you need to process, allocate
|
|
// and array which can hold the max number of triangles you need to process.
|
|
rc.triareas = new unsigned char[chunkyMesh->maxTrisPerChunk];
|
|
if (!rc.triareas) {
|
|
builder_params.ctx->log(RC_LOG_ERROR,
|
|
"buildNavigation: Out of memory 'm_triareas' (%d).",
|
|
chunkyMesh->maxTrisPerChunk);
|
|
return 0;
|
|
}
|
|
|
|
float tbmin[2], tbmax[2];
|
|
tbmin[0] = tcfg.bmin[0];
|
|
tbmin[1] = tcfg.bmin[2];
|
|
tbmax[0] = tcfg.bmax[0];
|
|
tbmax[1] = tcfg.bmax[2];
|
|
int cid[512];// TODO: Make grow when returning too many items.
|
|
const int ncid = rcGetChunksOverlappingRect(chunkyMesh, tbmin, tbmax, cid, 512);
|
|
if (!ncid) {
|
|
return 0; // empty
|
|
}
|
|
|
|
for (int i = 0; i < ncid; ++i) {
|
|
const rcChunkyTriMeshNode& node = chunkyMesh->nodes[cid[i]];
|
|
const int* tris = &chunkyMesh->tris[node.i*3];
|
|
const int ntris = node.n;
|
|
|
|
memset(rc.triareas, 0, ntris*sizeof(unsigned char));
|
|
rcMarkWalkableTriangles(builder_params.ctx, tcfg.walkableSlopeAngle,
|
|
verts, nverts, tris, ntris, rc.triareas,
|
|
SAMPLE_AREAMOD_GROUND);
|
|
|
|
if (!rcRasterizeTriangles(builder_params.ctx,
|
|
verts,
|
|
nverts,
|
|
tris,
|
|
rc.triareas,
|
|
ntris,
|
|
*rc.solid,
|
|
tcfg.walkableClimb)) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Once all geometry is rasterized, we do initial pass of filtering to
|
|
// remove unwanted overhangs caused by the conservative rasterization
|
|
// as well as filter spans where the character cannot possibly stand.
|
|
if (builder_params.kFilterLowHangingObstacles) {
|
|
rcFilterLowHangingWalkableObstacles(builder_params.ctx, tcfg.walkableClimb, *rc.solid);
|
|
}
|
|
if (builder_params.kFilterLedgeSpans) {
|
|
rcFilterLedgeSpans(builder_params.ctx, tcfg.walkableHeight, tcfg.walkableClimb, *rc.solid);
|
|
}
|
|
if (builder_params.kFilterWalkableLowHeightSpans) {
|
|
rcFilterWalkableLowHeightSpans(builder_params.ctx, tcfg.walkableHeight, *rc.solid);
|
|
}
|
|
|
|
rc.chf = rcAllocCompactHeightfield();
|
|
if (!rc.chf) {
|
|
builder_params.ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'chf'.");
|
|
return 0;
|
|
}
|
|
if (!rcBuildCompactHeightfield(builder_params.ctx,
|
|
tcfg.walkableHeight,
|
|
tcfg.walkableClimb,
|
|
*rc.solid,
|
|
*rc.chf)) {
|
|
builder_params.ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build compact data.");
|
|
return 0;
|
|
}
|
|
|
|
// Erode the walkable area by agent radius.
|
|
if (!rcErodeWalkableArea(builder_params.ctx, tcfg.walkableRadius, *rc.chf)) {
|
|
builder_params.ctx->log(RC_LOG_ERROR, "buildNavigation: Could not erode.");
|
|
return 0;
|
|
}
|
|
|
|
#if 0
|
|
// (Optional) Mark areas.
|
|
const ConvexVolume* vols = builder_params.gemo->GetConvexVolumes();
|
|
for (int i = 0; i < builder_params.gemo->GetConvexVolumeCount(); ++i) {
|
|
rcMarkConvexPolyArea(builder_params.ctx, vols[i].verts, vols[i].nverts,
|
|
vols[i].hmin, vols[i].hmax,
|
|
vols[i].areaMod, *rc.chf);
|
|
}
|
|
#endif
|
|
|
|
rc.lset = rcAllocHeightfieldLayerSet();
|
|
if (!rc.lset) {
|
|
builder_params.ctx->log(RC_LOG_ERROR, "buildNavigation: Out of memory 'lset'.");
|
|
return 0;
|
|
}
|
|
if (!rcBuildHeightfieldLayers(builder_params.ctx,
|
|
*rc.chf,
|
|
tcfg.borderSize,
|
|
tcfg.walkableHeight,
|
|
*rc.lset)) {
|
|
builder_params.ctx->log(RC_LOG_ERROR, "buildNavigation: Could not build heighfield layers.");
|
|
return 0;
|
|
}
|
|
|
|
rc.ntiles = 0;
|
|
for (int i = 0; i < rcMin(rc.lset->nlayers, MAX_LAYERS); ++i) {
|
|
TileCacheData* tile = &rc.tiles[rc.ntiles++];
|
|
const rcHeightfieldLayer* layer = &rc.lset->layers[i];
|
|
|
|
// Store header
|
|
dtTileCacheLayerHeader header;
|
|
header.magic = DT_TILECACHE_MAGIC;
|
|
header.version = DT_TILECACHE_VERSION;
|
|
|
|
// Tile layer location in the navmesh.
|
|
header.tx = tx;
|
|
header.ty = ty;
|
|
header.tlayer = i;
|
|
dtVcopy(header.bmin, layer->bmin);
|
|
dtVcopy(header.bmax, layer->bmax);
|
|
|
|
// Tile info.
|
|
header.width = (unsigned char)layer->width;
|
|
header.height = (unsigned char)layer->height;
|
|
header.minx = (unsigned char)layer->minx;
|
|
header.maxx = (unsigned char)layer->maxx;
|
|
header.miny = (unsigned char)layer->miny;
|
|
header.maxy = (unsigned char)layer->maxy;
|
|
header.hmin = (unsigned short)layer->hmin;
|
|
header.hmax = (unsigned short)layer->hmax;
|
|
|
|
dtStatus status = dtBuildTileCacheLayer(&comp,
|
|
&header,
|
|
layer->heights,
|
|
layer->areas,
|
|
layer->cons,
|
|
&tile->data,
|
|
&tile->dataSize);
|
|
if (dtStatusFailed(status)) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Transfer ownsership of tile data from build context to the caller.
|
|
int n = 0;
|
|
for (int i = 0; i < rcMin(rc.ntiles, maxTiles); ++i) {
|
|
tiles[n++] = rc.tiles[i];
|
|
rc.tiles[i].data = 0;
|
|
rc.tiles[i].dataSize = 0;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
bool NavMeshBuilder::CreateTileCache(BuilderParams& builder_params)
|
|
{
|
|
LinearAllocator* talloc = nullptr;
|
|
FastLZCompressor* tcomp = nullptr;
|
|
MeshProcess* tmproc = nullptr;
|
|
|
|
dtTileCacheParams tcparams;
|
|
InitTileCacheParams(builder_params, tcparams);
|
|
builder_params.tile_cache = dtAllocTileCache();
|
|
dtStatus status = builder_params.tile_cache->init
|
|
(&tcparams,
|
|
talloc,
|
|
tcomp,
|
|
tmproc);
|
|
if (dtStatusFailed(status)) {
|
|
abort();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool NavMeshBuilder::CreateNavMesh(BuilderParams& builder_params)
|
|
{
|
|
dtNavMeshParams params;
|
|
InitNavMeshParams(builder_params, params);
|
|
|
|
builder_params.navmesh = dtAllocNavMesh();
|
|
dtStatus status = builder_params.navmesh->init(¶ms);
|
|
if (dtStatusFailed(status)) {
|
|
abort();
|
|
}
|
|
return true;
|
|
}
|