mirror of
https://github.com/harfang3d/harfang3d.git
synced 2024-07-16 15:02:19 +00:00
574 lines
18 KiB
C++
574 lines
18 KiB
C++
// HARFANG(R) Copyright (C) 2021 Emmanuel Julien, NWNC HARFANG. Released under GPL/LGPL/Commercial Licence, see licence.txt for details.
|
|
|
|
#include "engine/recast_detour.h"
|
|
#include "engine/assets_rw_interface.h"
|
|
#include "engine/geometry.h"
|
|
#include "engine/render_pipeline.h"
|
|
|
|
#include "foundation/cext.h"
|
|
#include "foundation/color.h"
|
|
#include "foundation/data.h"
|
|
#include "foundation/file_rw_interface.h"
|
|
#include "foundation/log.h"
|
|
|
|
#if HG_ENABLE_RECAST_DETOUR_API
|
|
|
|
#include <DetourNavMeshBuilder.h>
|
|
#include <DetourNavMeshQuery.h>
|
|
#include <Recast.h>
|
|
|
|
#include <math.h>
|
|
|
|
namespace hg {
|
|
|
|
dtNavMesh *LoadNavMesh(const char *path, const Reader &ir, const ReadProvider &ip) {
|
|
ScopedReadHandle h(ip, path);
|
|
|
|
if (!ir.is_valid(h))
|
|
return nullptr;
|
|
|
|
struct NavMeshSetHeader {
|
|
int magic;
|
|
int version;
|
|
int numTiles;
|
|
dtNavMeshParams params;
|
|
};
|
|
|
|
struct NavMeshTileHeader {
|
|
dtTileRef tileRef;
|
|
int dataSize;
|
|
};
|
|
|
|
// read header
|
|
NavMeshSetHeader header;
|
|
|
|
if (!Read(ir, h, header))
|
|
return nullptr;
|
|
|
|
if (header.magic != NAVMESHSET_MAGIC)
|
|
return nullptr;
|
|
if (header.version != NAVMESHSET_VERSION)
|
|
return nullptr;
|
|
|
|
auto mesh = dtAllocNavMesh();
|
|
if (!mesh)
|
|
return nullptr;
|
|
|
|
auto status = mesh->init(&header.params);
|
|
if (dtStatusFailed(status)) {
|
|
dtFree(mesh);
|
|
return nullptr;
|
|
}
|
|
|
|
// read tiles
|
|
for (int i = 0; i < header.numTiles; ++i) {
|
|
NavMeshTileHeader tileHeader;
|
|
|
|
if (!Read(ir, h, tileHeader)) {
|
|
dtFree(mesh);
|
|
return nullptr;
|
|
}
|
|
|
|
if (!tileHeader.tileRef || !tileHeader.dataSize)
|
|
break;
|
|
|
|
unsigned char *data = (unsigned char *)dtAlloc(tileHeader.dataSize, DT_ALLOC_PERM);
|
|
if (!data)
|
|
break;
|
|
memset(data, 0, tileHeader.dataSize);
|
|
|
|
auto readLen = ir.read(h, data, tileHeader.dataSize);
|
|
if (readLen != tileHeader.dataSize) {
|
|
dtFree(data);
|
|
dtFree(mesh);
|
|
return nullptr;
|
|
}
|
|
|
|
mesh->addTile(data, tileHeader.dataSize, DT_TILE_FREE_DATA, tileHeader.tileRef, 0);
|
|
}
|
|
|
|
return mesh;
|
|
}
|
|
|
|
dtNavMesh *LoadNavMeshFromFile(const char *path) { return LoadNavMesh(path, g_file_reader, g_file_read_provider); }
|
|
dtNavMesh *LoadNavMeshFromAssets(const char *path) { return LoadNavMesh(path, g_assets_reader, g_assets_read_provider); }
|
|
|
|
void DestroyNavMesh(dtNavMesh *mesh) { dtFree(mesh); }
|
|
|
|
//
|
|
dtNavMeshQuery *CreateNavMeshQuery(const dtNavMesh *mesh) {
|
|
auto query = dtAllocNavMeshQuery();
|
|
auto status = query->init(mesh, 2048);
|
|
|
|
if (dtStatusFailed(status)) {
|
|
dtFree(query);
|
|
query = nullptr;
|
|
}
|
|
|
|
return query;
|
|
}
|
|
|
|
void DestroyNavMeshQuery(dtNavMeshQuery *query) { dtFree(query); }
|
|
|
|
//
|
|
std::vector<Vec3> FindNavigationPathTo(const dtNavMeshQuery *query, const Vec3 &from, const Vec3 &to) {
|
|
dtQueryFilter m_filter; // map attributes flags
|
|
|
|
m_filter.setIncludeFlags(0xffff);
|
|
m_filter.setExcludeFlags(0);
|
|
|
|
static const float m_polyPickExt[3] = {2, 4, 2}; // XYZ bounding box to pick nearby polygons
|
|
|
|
const float *spos = &from.x, *epos = &to.x;
|
|
|
|
static const int MAX_POLYS = 256;
|
|
static const int MAX_SMOOTH = 2048;
|
|
|
|
dtPolyRef polys[MAX_POLYS];
|
|
int npolys = 0;
|
|
int nstraightPath = 0;
|
|
float straightPath[MAX_POLYS * 3];
|
|
unsigned char straightPathFlags[MAX_POLYS];
|
|
dtPolyRef straightPathPolys[MAX_POLYS];
|
|
int straightPathOptions = DT_STRAIGHTPATH_ALL_CROSSINGS;
|
|
|
|
dtPolyRef startRef = 0;
|
|
dtPolyRef endRef = 0;
|
|
query->findNearestPoly(spos, m_polyPickExt, &m_filter, &startRef, 0);
|
|
query->findNearestPoly(epos, m_polyPickExt, &m_filter, &endRef, 0);
|
|
|
|
if (startRef && endRef) {
|
|
query->findPath(startRef, endRef, spos, epos, &m_filter, polys, &npolys, MAX_POLYS);
|
|
if (npolys) {
|
|
// in case of a partial path, make sure the end point is clamped to the last polygon
|
|
float temp_epos[3] = {epos[0], epos[1], epos[2]};
|
|
if (polys[npolys - 1] != endRef)
|
|
query->closestPointOnPoly(polys[npolys - 1], epos, temp_epos, 0);
|
|
|
|
query->findStraightPath(
|
|
spos, temp_epos, polys, npolys, straightPath, straightPathFlags, straightPathPolys, &nstraightPath, MAX_POLYS, straightPathOptions);
|
|
}
|
|
} else {
|
|
npolys = 0;
|
|
nstraightPath = 0;
|
|
}
|
|
|
|
std::vector<Vec3> out(nstraightPath);
|
|
for (int i = 0; i < nstraightPath; ++i)
|
|
out[i] = {straightPath[i * 3], straightPath[i * 3 + 1], straightPath[i * 3 + 2]};
|
|
|
|
return out;
|
|
}
|
|
|
|
//
|
|
void DrawNavMesh(const dtNavMesh *mesh, bgfx::ViewId view_id, const bgfx::VertexLayout &vtx_layout, bgfx::ProgramHandle program,
|
|
const std::vector<UniformSetValue> &values, const std::vector<UniformSetTexture> &textures, RenderState state) {
|
|
Color col(0.54f, 0.81f, 0.84f, 0.5f);
|
|
|
|
Vertices vtx(vtx_layout, 3);
|
|
int vtx_count = 0;
|
|
|
|
for (int i = 0; i < mesh->getMaxTiles(); ++i) {
|
|
const dtMeshTile *tile = mesh->getTile(i);
|
|
if (!tile->header)
|
|
continue;
|
|
|
|
dtPolyRef base = mesh->getPolyRefBase(tile);
|
|
|
|
int tileNum = mesh->decodePolyIdTile(base);
|
|
|
|
for (int j = 0; j < tile->header->polyCount; ++j) {
|
|
const dtPoly *p = &tile->polys[j];
|
|
if (p->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) // Skip off-mesh links.
|
|
continue;
|
|
|
|
const dtPolyDetail *pd = &tile->detailMeshes[j];
|
|
|
|
for (int k = 0; k < pd->triCount; ++k) {
|
|
const unsigned char *t = &tile->detailTris[(pd->triBase + k) * 4];
|
|
for (int l = 0; l < 3; ++l) {
|
|
if (t[l] < p->vertCount) {
|
|
float *v = &tile->verts[p->verts[t[l]] * 3];
|
|
vtx.Begin(vtx_count++).SetPos(Vec3(v[0], v[1], v[2])).SetColor0(col).End();
|
|
} else {
|
|
float *v = &tile->detailVerts[(pd->vertBase + t[l] - p->vertCount) * 3];
|
|
vtx.Begin(vtx_count++).SetPos(Vec3(v[0], v[1], v[2])).SetColor0(col).End();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DrawTriangles(view_id, vtx, program, values, textures, state);
|
|
}
|
|
|
|
//
|
|
void AddGeometryToNavMeshInput(NavMeshInput &input, const Geometry &geo, const Mat4 &world) {
|
|
const auto start_index = input.vtx.size();
|
|
|
|
input.vtx.reserve(input.vtx.size() + geo.vtx.size());
|
|
for (auto &vtx : geo.vtx)
|
|
input.vtx.push_back(vtx * world);
|
|
|
|
//
|
|
const auto tri_count = ComputeTriangleCount(geo);
|
|
input.idx.reserve(input.idx.size() + tri_count * 3);
|
|
|
|
const auto pol_idx = ComputePolygonIndex(geo);
|
|
|
|
for (size_t i = 0; i < geo.pol.size(); ++i) {
|
|
const auto &pol = geo.pol[i];
|
|
const auto j = pol_idx[i];
|
|
|
|
for (size_t v = 1; v < pol.vtx_count - 1; ++v) {
|
|
input.idx.push_back(numeric_cast<int>(geo.binding[j] + start_index));
|
|
input.idx.push_back(numeric_cast<int>(geo.binding[j + v] + start_index));
|
|
input.idx.push_back(numeric_cast<int>(geo.binding[j + v + 1] + start_index));
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
dtNavMesh *CreateNavMesh(const NavMeshInput &input, float radius, float height, float slope, float climb) {
|
|
MinMax minmax = {Vec3::Max, Vec3::Min};
|
|
|
|
for (const auto &vtx : input.vtx) {
|
|
minmax.mn = Min(minmax.mn, vtx);
|
|
minmax.mx = Max(minmax.mx, vtx);
|
|
}
|
|
|
|
const float *bmin = reinterpret_cast<float *>(&minmax.mn);
|
|
const float *bmax = reinterpret_cast<float *>(&minmax.mx);
|
|
|
|
const float *verts = reinterpret_cast<const float *>(input.vtx.data());
|
|
const int nverts = numeric_cast<int>(input.vtx.size());
|
|
|
|
const int *tris = input.idx.data();
|
|
const int ntris = numeric_cast<int>(input.idx.size()) / 3;
|
|
|
|
//
|
|
// Step 1. Initialize build config.
|
|
//
|
|
|
|
// Init build configuration from GUI
|
|
|
|
enum SamplePartitionType {
|
|
SAMPLE_PARTITION_WATERSHED,
|
|
SAMPLE_PARTITION_MONOTONE,
|
|
SAMPLE_PARTITION_LAYERS,
|
|
};
|
|
|
|
float cellSize = 0.3f;
|
|
float cellHeight = 0.2f;
|
|
float agentHeight = height;
|
|
float agentRadius = radius;
|
|
float agentMaxClimb = climb;
|
|
float agentMaxSlope = slope;
|
|
float regionMinSize = 8;
|
|
float regionMergeSize = 20;
|
|
float edgeMaxLen = 12.0f;
|
|
float edgeMaxError = 1.3f;
|
|
float vertsPerPoly = 6.0f;
|
|
float detailSampleDist = 6.0f;
|
|
float detailSampleMaxError = 1.0f;
|
|
int partitionType = SAMPLE_PARTITION_WATERSHED;
|
|
|
|
rcConfig cfg;
|
|
memset(&cfg, 0, sizeof(cfg));
|
|
cfg.cs = cellSize;
|
|
cfg.ch = cellHeight;
|
|
cfg.walkableSlopeAngle = agentMaxSlope;
|
|
cfg.walkableHeight = (int)ceilf(agentHeight / cfg.ch);
|
|
cfg.walkableClimb = (int)floorf(agentMaxClimb / cfg.ch);
|
|
cfg.walkableRadius = (int)ceilf(agentRadius / cfg.cs);
|
|
cfg.maxEdgeLen = (int)(edgeMaxLen / cellSize);
|
|
cfg.maxSimplificationError = edgeMaxError;
|
|
cfg.minRegionArea = (int)rcSqr(regionMinSize); // Note: area = size*size
|
|
cfg.mergeRegionArea = (int)rcSqr(regionMergeSize); // Note: area = size*size
|
|
cfg.maxVertsPerPoly = (int)vertsPerPoly;
|
|
cfg.detailSampleDist = detailSampleDist < 0.9f ? 0 : cellSize * detailSampleDist;
|
|
cfg.detailSampleMaxError = cellHeight * detailSampleMaxError;
|
|
|
|
rcContext ctx;
|
|
|
|
// Set the area where the navigation will be build.
|
|
// Here the bounds of the input mesh are used, but the
|
|
// area could be specified by an user defined box, etc.
|
|
rcVcopy(cfg.bmin, bmin);
|
|
rcVcopy(cfg.bmax, bmax);
|
|
rcCalcGridSize(cfg.bmin, cfg.bmax, cfg.cs, &cfg.width, &cfg.height);
|
|
|
|
//
|
|
// Step 2. Rasterize input polygon soup.
|
|
//
|
|
|
|
// Allocate voxel height field where we rasterize our input data to.
|
|
std::unique_ptr<rcHeightfield> solid(rcAllocHeightfield());
|
|
|
|
if (!rcCreateHeightfield(&ctx, *solid, cfg.width, cfg.height, cfg.bmin, cfg.bmax, cfg.cs, cfg.ch)) {
|
|
error("Navigation: Could not create solid height field.");
|
|
return nullptr;
|
|
}
|
|
|
|
// Allocate array that can hold triangle area types.
|
|
// If you have multiple meshes you need to process, allocate
|
|
// and array which can hold the max number of triangles you need to process.
|
|
std::vector<unsigned char> triareas(ntris);
|
|
|
|
// Find triangles which are walkable based on their slope and rasterize them.
|
|
// If your input data is multiple meshes, you can transform them here, calculate
|
|
// the are type for each of the meshes and rasterize them.
|
|
memset(triareas.data(), 0, ntris * sizeof(unsigned char));
|
|
rcMarkWalkableTriangles(&ctx, cfg.walkableSlopeAngle, verts, nverts, tris, ntris, triareas.data());
|
|
rcRasterizeTriangles(&ctx, verts, nverts, tris, triareas.data(), ntris, *solid, cfg.walkableClimb);
|
|
|
|
triareas.clear();
|
|
|
|
//
|
|
// Step 3. Filter walkable surfaces.
|
|
//
|
|
|
|
// 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.
|
|
rcFilterLowHangingWalkableObstacles(&ctx, cfg.walkableClimb, *solid);
|
|
rcFilterLedgeSpans(&ctx, cfg.walkableHeight, cfg.walkableClimb, *solid);
|
|
rcFilterWalkableLowHeightSpans(&ctx, cfg.walkableHeight, *solid);
|
|
|
|
//
|
|
// Step 4. Partition walkable surface to simple regions.
|
|
//
|
|
|
|
// Compact the height field so that it is faster to handle from now on.
|
|
// This will result more cache coherent data as well as the neighbors
|
|
// between walkable cells will be calculated.
|
|
std::unique_ptr<rcCompactHeightfield> chf(rcAllocCompactHeightfield());
|
|
|
|
if (!rcBuildCompactHeightfield(&ctx, cfg.walkableHeight, cfg.walkableClimb, *solid, *chf)) {
|
|
error("buildNavigation: Could not build compact data.");
|
|
return nullptr;
|
|
}
|
|
|
|
solid = nullptr;
|
|
|
|
// Erode the walkable area by agent radius.
|
|
if (!rcErodeWalkableArea(&ctx, cfg.walkableRadius, *chf)) {
|
|
error("buildNavigation: Could not erode.");
|
|
return nullptr;
|
|
}
|
|
|
|
// Partition the height field so that we can use simple algorithm later to triangulate the walkable areas.
|
|
// There are 3 partitioning methods, each with some pros and cons:
|
|
// 1) Watershed partitioning
|
|
// - the classic Recast partitioning
|
|
// - creates the nicest tessellation
|
|
// - usually slowest
|
|
// - partitions the height field into nice regions without holes or overlaps
|
|
// - the are some corner cases where this method creates produces holes and overlaps
|
|
// - holes may appear when a small obstacles is close to large open area (triangulation can handle this)
|
|
// - overlaps may occur if you have narrow spiral corridors (i.e stairs), this make triangulation to fail
|
|
// * generally the best choice if you precompute the navmesh, use this if you have large open areas
|
|
// 2) Monotone partitioning
|
|
// - fastest
|
|
// - partitions the height field into regions without holes and overlaps (guaranteed)
|
|
// - creates long thin polygons, which sometimes causes paths with detours
|
|
// * use this if you want fast navmesh generation
|
|
// 3) Layer partitioning
|
|
// - quite fast
|
|
// - partitions the height field into non-overlapping regions
|
|
// - relies on the triangulation code to cope with holes (thus slower than monotone partitioning)
|
|
// - produces better triangles than monotone partitioning
|
|
// - does not have the corner cases of watershed partitioning
|
|
// - can be slow and create a bit ugly tessellation (still better than monotone)
|
|
// if you have large open areas with small obstacles (not a problem if you use tiles)
|
|
// * good choice to use for tiled navmesh with medium and small sized tiles
|
|
|
|
if (partitionType == SAMPLE_PARTITION_WATERSHED) {
|
|
// Prepare for region partitioning, by calculating distance field along the walkable surface.
|
|
if (!rcBuildDistanceField(&ctx, *chf)) {
|
|
error("buildNavigation: Could not build distance field.");
|
|
return nullptr;
|
|
}
|
|
|
|
// Partition the walkable surface into simple regions without holes.
|
|
if (!rcBuildRegions(&ctx, *chf, 0, cfg.minRegionArea, cfg.mergeRegionArea)) {
|
|
error("buildNavigation: Could not build watershed regions.");
|
|
return nullptr;
|
|
}
|
|
} else if (partitionType == SAMPLE_PARTITION_MONOTONE) {
|
|
// Partition the walkable surface into simple regions without holes.
|
|
// Monotone partitioning does not need distance field.
|
|
if (!rcBuildRegionsMonotone(&ctx, *chf, 0, cfg.minRegionArea, cfg.mergeRegionArea)) {
|
|
error("buildNavigation: Could not build monotone regions.");
|
|
return nullptr;
|
|
}
|
|
} else { // SAMPLE_PARTITION_LAYERS
|
|
// Partition the walkable surface into simple regions without holes.
|
|
if (!rcBuildLayerRegions(&ctx, *chf, 0, cfg.minRegionArea)) {
|
|
error("buildNavigation: Could not build layer regions.");
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Step 5. Trace and simplify region contours.
|
|
//
|
|
|
|
// Create contours.
|
|
std::unique_ptr<rcContourSet> cset(rcAllocContourSet());
|
|
|
|
if (!rcBuildContours(&ctx, *chf, cfg.maxSimplificationError, cfg.maxEdgeLen, *cset)) {
|
|
error("buildNavigation: Could not create contours.");
|
|
return nullptr;
|
|
}
|
|
|
|
//
|
|
// Step 6. Build polygons mesh from contours.
|
|
//
|
|
|
|
// Build polygon navmesh from the contours.
|
|
std::unique_ptr<rcPolyMesh> pmesh(rcAllocPolyMesh());
|
|
|
|
if (!rcBuildPolyMesh(&ctx, *cset, cfg.maxVertsPerPoly, *pmesh)) {
|
|
error("buildNavigation: Could not triangulate contours.");
|
|
return nullptr;
|
|
}
|
|
|
|
//
|
|
// Step 7. Create detail mesh which allows to access approximate height on each polygon.
|
|
//
|
|
|
|
std::unique_ptr<rcPolyMeshDetail> dmesh(rcAllocPolyMeshDetail());
|
|
|
|
if (!rcBuildPolyMeshDetail(&ctx, *pmesh, *chf, cfg.detailSampleDist, cfg.detailSampleMaxError, *dmesh)) {
|
|
error("buildNavigation: Could not build detail mesh.");
|
|
return nullptr;
|
|
}
|
|
|
|
rcFreeCompactHeightfield(chf.release());
|
|
rcFreeContourSet(cset.release());
|
|
|
|
// At this point the navigation mesh data is ready, you can access it from pmesh.
|
|
// See duDebugDrawPolyMesh or dtCreateNavMeshData as examples how to access the data.
|
|
|
|
//
|
|
// (Optional) Step 8. Create Detour data from Recast poly mesh.
|
|
//
|
|
|
|
// The GUI may allow more max points per polygon than Detour can handle.
|
|
// Only build the detour navmesh if we do not exceed the limit.
|
|
dtNavMesh *navMesh = nullptr;
|
|
|
|
if (cfg.maxVertsPerPoly <= DT_VERTS_PER_POLYGON) {
|
|
unsigned char *navData = 0;
|
|
int navDataSize = 0;
|
|
|
|
// Update poly flags from areas.
|
|
for (int i = 0; i < pmesh->npolys; ++i) {
|
|
if (pmesh->areas[i] == RC_WALKABLE_AREA)
|
|
pmesh->flags[i] = 1;
|
|
/*
|
|
if (pmesh->areas[i] == RC_WALKABLE_AREA)
|
|
pmesh->areas[i] = SAMPLE_POLYAREA_GROUND;
|
|
|
|
if (pmesh->areas[i] == SAMPLE_POLYAREA_GROUND || pmesh->areas[i] == SAMPLE_POLYAREA_GRASS || pmesh->areas[i] == SAMPLE_POLYAREA_ROAD) {
|
|
pmesh->flags[i] = SAMPLE_POLYFLAGS_WALK;
|
|
} else if (pmesh->areas[i] == SAMPLE_POLYAREA_WATER) {
|
|
pmesh->flags[i] = SAMPLE_POLYFLAGS_SWIM;
|
|
} else if (pmesh->areas[i] == SAMPLE_POLYAREA_DOOR) {
|
|
pmesh->flags[i] = SAMPLE_POLYFLAGS_WALK | SAMPLE_POLYFLAGS_DOOR;
|
|
}
|
|
*/
|
|
}
|
|
|
|
dtNavMeshCreateParams params;
|
|
memset(¶ms, 0, sizeof(params));
|
|
params.verts = pmesh->verts;
|
|
params.vertCount = pmesh->nverts;
|
|
params.polys = pmesh->polys;
|
|
params.polyAreas = pmesh->areas;
|
|
params.polyFlags = pmesh->flags;
|
|
params.polyCount = pmesh->npolys;
|
|
params.nvp = pmesh->nvp;
|
|
params.detailMeshes = dmesh->meshes;
|
|
params.detailVerts = dmesh->verts;
|
|
params.detailVertsCount = dmesh->nverts;
|
|
params.detailTris = dmesh->tris;
|
|
params.detailTriCount = dmesh->ntris;
|
|
/*
|
|
params.offMeshConVerts = geom->getOffMeshConnectionVerts();
|
|
params.offMeshConRad = geom->getOffMeshConnectionRads();
|
|
params.offMeshConDir = geom->getOffMeshConnectionDirs();
|
|
params.offMeshConAreas = geom->getOffMeshConnectionAreas();
|
|
params.offMeshConFlags = geom->getOffMeshConnectionFlags();
|
|
params.offMeshConUserID = geom->getOffMeshConnectionId();
|
|
params.offMeshConCount = geom->getOffMeshConnectionCount();
|
|
*/
|
|
params.walkableHeight = agentHeight;
|
|
params.walkableRadius = agentRadius;
|
|
params.walkableClimb = agentMaxClimb;
|
|
rcVcopy(params.bmin, pmesh->bmin);
|
|
rcVcopy(params.bmax, pmesh->bmax);
|
|
params.cs = cfg.cs;
|
|
params.ch = cfg.ch;
|
|
params.buildBvTree = true;
|
|
|
|
if (!dtCreateNavMeshData(¶ms, &navData, &navDataSize)) {
|
|
error("Could not build Detour navmesh.");
|
|
return nullptr;
|
|
}
|
|
|
|
navMesh = dtAllocNavMesh();
|
|
if (!navMesh) {
|
|
dtFree(navData);
|
|
error("Could not create Detour navmesh");
|
|
return nullptr;
|
|
}
|
|
|
|
dtStatus status;
|
|
|
|
status = navMesh->init(navData, navDataSize, DT_TILE_FREE_DATA);
|
|
if (dtStatusFailed(status)) {
|
|
dtFree(navData);
|
|
error("Could not init Detour navmesh");
|
|
return nullptr;
|
|
}
|
|
|
|
// free pmesh dmesh
|
|
rcFreePolyMesh(pmesh.release());
|
|
rcFreePolyMeshDetail(dmesh.release());
|
|
}
|
|
|
|
return navMesh;
|
|
}
|
|
|
|
} // namespace hg
|
|
|
|
#else
|
|
|
|
namespace hg {
|
|
|
|
dtNavMesh *LoadNavMesh(const char *path, const Reader &ir, const ReadProvider &ip) { return nullptr; }
|
|
dtNavMesh *LoadNavMeshFromFile(const char *path) { return nullptr; }
|
|
dtNavMesh *LoadNavMeshFromAssets(const char *path) { return nullptr; }
|
|
|
|
void DestroyNavMesh(dtNavMesh *mesh) {}
|
|
|
|
//
|
|
void DrawNavMesh(const dtNavMesh *mesh, bgfx::ViewId view_id, const bgfx::VertexLayout &vtx_layout, bgfx::ProgramHandle program, RenderState state) {}
|
|
|
|
//
|
|
void AddGeometryToNavMeshInput(NavMeshInput &input, const Geometry &geo, const Mat4 &world) {}
|
|
dtNavMesh *CreateNavMesh(const NavMeshInput &input, float radius, float height, float slope, float climb) { return nullptr; }
|
|
|
|
//
|
|
dtNavMeshQuery *CreateNavMeshQuery(const dtNavMesh *mesh) { return nullptr; }
|
|
void DestroyNavMeshQuery(dtNavMeshQuery *query) {}
|
|
|
|
//
|
|
std::vector<Vec3> FindNavigationPathTo(const dtNavMeshQuery *query, const Vec3 &from, const Vec3 &to) { return {}; }
|
|
|
|
} // namespace hg
|
|
|
|
#endif
|