feat(nav): 17.3 - a map in square tiles (nav_tiled, nav_tile_build; a tile must be a whole number of cells or its seams never join, and the shim refuses one that is not; 64-bit polygon refs for 16384 tiles), ground as a NavGround with boulders as convex footprints, one save format for one tile or many, and nav_random_near - a reachable point from the caller's seed, bit-identical on the Mac and the PC. 10 tests on both
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
9b8606483d
commit
e6893f58be
16 changed files with 436 additions and 109 deletions
|
|
@ -1,6 +1,6 @@
|
|||
// nav_build.inl - a navmesh from triangles: Recast's solo pipeline (rasterize, filter, erode,
|
||||
// regions, contours, polygons, detail) and one Detour tile. c[] is the configuration:
|
||||
// cell size, cell height, agent height, agent radius, max climb, max slope (degrees).
|
||||
// nav_build.inl - Recast's pipeline, shared by a whole mesh and a tile: rasterize the triangles,
|
||||
// filter, mark what nothing stands in, erode, regions, contours, polygons, detail, Detour's data.
|
||||
// c[] is the configuration: cell size, cell height, agent height, radius, max climb, max slope.
|
||||
|
||||
// cylinders (x, y, z, r, h each) no agent may stand in: trunks, posts, tents
|
||||
static void nav_mark_cylinders(rcContext &ctx, rcCompactHeightfield &chf, const float *cyl, int nc) {
|
||||
|
|
@ -10,12 +10,21 @@ static void nav_mark_cylinders(rcContext &ctx, rcCompactHeightfield &chf, const
|
|||
}
|
||||
}
|
||||
|
||||
// v: nv points (x, y, z); t: nt triangles; area: one per triangle, 0 not walkable, 1..62 a kind of
|
||||
// ground (its cost is the filter's); the slope clears what is too steep whatever its area
|
||||
NAV_SHIM void *nav_build(const float *v, int nv, const int *t, int nt, const unsigned char *area,
|
||||
const float *cyl, int nc, const float *c) {
|
||||
rcContext ctx(false);
|
||||
rcConfig cfg;
|
||||
// convex footprints no agent may stand in (a boulder): each is n, n points (x, z), ymin, ymax
|
||||
static void nav_mark_footprints(rcContext &ctx, rcCompactHeightfield &chf, const float *f, int nf) {
|
||||
float pts[3 * 64];
|
||||
int i = 0;
|
||||
while (i < nf) {
|
||||
int n = (int)f[i];
|
||||
if (n < 3 || n > 64 || i + 1 + n * 2 + 2 > nf) return;
|
||||
float y0 = f[i + 1 + n * 2], y1 = f[i + 2 + n * 2];
|
||||
for (int k = 0; k < n; ++k) { pts[k * 3] = f[i + 1 + k * 2]; pts[k * 3 + 1] = y0; pts[k * 3 + 2] = f[i + 2 + k * 2]; }
|
||||
rcMarkConvexPolyArea(&ctx, pts, n, y0, y1, RC_NULL_AREA, chf);
|
||||
i += 3 + n * 2;
|
||||
}
|
||||
}
|
||||
|
||||
static void nav_config(rcConfig &cfg, const float *c) {
|
||||
memset(&cfg, 0, sizeof(cfg));
|
||||
cfg.cs = c[0];
|
||||
cfg.ch = c[1];
|
||||
|
|
@ -30,11 +39,18 @@ NAV_SHIM void *nav_build(const float *v, int nv, const int *t, int nt, const uns
|
|||
cfg.maxVertsPerPoly = 6;
|
||||
cfg.detailSampleDist = cfg.cs * 6.0f;
|
||||
cfg.detailSampleMaxError = cfg.ch;
|
||||
rcCalcBounds(v, nv, cfg.bmin, cfg.bmax);
|
||||
rcCalcGridSize(cfg.bmin, cfg.bmax, cfg.cs, &cfg.width, &cfg.height);
|
||||
}
|
||||
|
||||
std::vector<unsigned char> areas(area, area + nt);
|
||||
rcClearUnwalkableTriangles(&ctx, cfg.walkableSlopeAngle, v, nv, t, nt, areas.data());
|
||||
struct NavIn {
|
||||
const float *v; int nv; const int *t; int nt; const unsigned char *area;
|
||||
const float *cyl; int nc; const float *foot; int nf; const float *c;
|
||||
};
|
||||
|
||||
// the pipeline over cfg's box; the tile's Detour data into data / size. 0 polygons is not an error
|
||||
static bool nav_pipeline(rcConfig &cfg, const NavIn &in, int tx, int tz, unsigned char **data, int *size, int *polys) {
|
||||
rcContext ctx(false);
|
||||
std::vector<unsigned char> areas(in.area, in.area + in.nt);
|
||||
rcClearUnwalkableTriangles(&ctx, cfg.walkableSlopeAngle, in.v, in.nv, in.t, in.nt, areas.data());
|
||||
rcHeightfield *hf = rcAllocHeightfield();
|
||||
rcCompactHeightfield *chf = rcAllocCompactHeightfield();
|
||||
rcContourSet *cs = rcAllocContourSet();
|
||||
|
|
@ -42,7 +58,7 @@ NAV_SHIM void *nav_build(const float *v, int nv, const int *t, int nt, const uns
|
|||
rcPolyMeshDetail *dm = rcAllocPolyMeshDetail();
|
||||
bool ok = hf && chf && cs && pm && dm &&
|
||||
rcCreateHeightfield(&ctx, *hf, cfg.width, cfg.height, cfg.bmin, cfg.bmax, cfg.cs, cfg.ch) &&
|
||||
rcRasterizeTriangles(&ctx, v, nv, t, areas.data(), nt, *hf, cfg.walkableClimb);
|
||||
rcRasterizeTriangles(&ctx, in.v, in.nv, in.t, areas.data(), in.nt, *hf, cfg.walkableClimb);
|
||||
if (ok) {
|
||||
rcFilterLowHangingWalkableObstacles(&ctx, cfg.walkableClimb, *hf);
|
||||
rcFilterLedgeSpans(&ctx, cfg.walkableHeight, cfg.walkableClimb, *hf);
|
||||
|
|
@ -50,16 +66,16 @@ NAV_SHIM void *nav_build(const float *v, int nv, const int *t, int nt, const uns
|
|||
ok = rcBuildCompactHeightfield(&ctx, cfg.walkableHeight, cfg.walkableClimb, *hf, *chf);
|
||||
}
|
||||
if (ok) {
|
||||
nav_mark_cylinders(ctx, *chf, cyl, nc);
|
||||
nav_mark_cylinders(ctx, *chf, in.cyl, in.nc);
|
||||
nav_mark_footprints(ctx, *chf, in.foot, in.nf);
|
||||
ok = rcErodeWalkableArea(&ctx, cfg.walkableRadius, *chf) && rcBuildDistanceField(&ctx, *chf) &&
|
||||
rcBuildRegions(&ctx, *chf, 0, cfg.minRegionArea, cfg.mergeRegionArea) &&
|
||||
rcBuildRegions(&ctx, *chf, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea) &&
|
||||
rcBuildContours(&ctx, *chf, cfg.maxSimplificationError, cfg.maxEdgeLen, *cs) &&
|
||||
rcBuildPolyMesh(&ctx, *cs, cfg.maxVertsPerPoly, *pm) &&
|
||||
rcBuildPolyMeshDetail(&ctx, *pm, *chf, cfg.detailSampleDist, cfg.detailSampleMaxError, *dm);
|
||||
}
|
||||
unsigned char *data = nullptr;
|
||||
int size = 0;
|
||||
if (ok) {
|
||||
*polys = ok ? pm->npolys : 0;
|
||||
if (ok && pm->npolys > 0) {
|
||||
for (int i = 0; i < pm->npolys; ++i) pm->flags[i] = pm->areas[i] ? 1 : 0;
|
||||
dtNavMeshCreateParams p;
|
||||
memset(&p, 0, sizeof(p));
|
||||
|
|
@ -67,30 +83,28 @@ NAV_SHIM void *nav_build(const float *v, int nv, const int *t, int nt, const uns
|
|||
p.polyFlags = pm->flags; p.polyCount = pm->npolys; p.nvp = pm->nvp;
|
||||
p.detailMeshes = dm->meshes; p.detailVerts = dm->verts; p.detailVertsCount = dm->nverts;
|
||||
p.detailTris = dm->tris; p.detailTriCount = dm->ntris;
|
||||
p.walkableHeight = c[2]; p.walkableRadius = c[3]; p.walkableClimb = c[4];
|
||||
p.walkableHeight = in.c[2]; p.walkableRadius = in.c[3]; p.walkableClimb = in.c[4];
|
||||
p.tileX = tx; p.tileY = tz;
|
||||
rcVcopy(p.bmin, pm->bmin); rcVcopy(p.bmax, pm->bmax);
|
||||
p.cs = cfg.cs; p.ch = cfg.ch; p.buildBvTree = true;
|
||||
ok = dtCreateNavMeshData(&p, &data, &size);
|
||||
ok = dtCreateNavMeshData(&p, data, size);
|
||||
}
|
||||
rcFreeHeightField(hf); rcFreeCompactHeightfield(chf); rcFreeContourSet(cs); rcFreePolyMesh(pm); rcFreePolyMeshDetail(dm);
|
||||
if (!ok) return nullptr;
|
||||
return nav_from(data, size);
|
||||
return ok;
|
||||
}
|
||||
|
||||
// the tile's bytes, to write to a file: first the size (buf null), then the copy
|
||||
NAV_SHIM int nav_save(void *h, unsigned char *buf, int cap) {
|
||||
Nav *n = static_cast<Nav *>(h);
|
||||
if (!buf) return n->size;
|
||||
if (cap < n->size) return -1;
|
||||
memcpy(buf, n->data, n->size);
|
||||
return n->size;
|
||||
}
|
||||
|
||||
// a navmesh from bytes nav_save wrote
|
||||
NAV_SHIM void *nav_load(const unsigned char *buf, int size) {
|
||||
unsigned char *data = static_cast<unsigned char *>(dtAlloc(size, DT_ALLOC_PERM));
|
||||
if (!data) return nullptr;
|
||||
memcpy(data, buf, size);
|
||||
// one mesh over all the triangles given: v nv points (x, y, z); t nt triangles; area a byte each,
|
||||
// 0 not walkable and 1..62 a kind of ground; the slope clears what is too steep whatever its area
|
||||
NAV_SHIM void *nav_build(const float *v, int nv, const int *t, int nt, const unsigned char *area,
|
||||
const float *cyl, int nc, const float *foot, int nf, const float *c) {
|
||||
rcConfig cfg;
|
||||
nav_config(cfg, c);
|
||||
rcCalcBounds(v, nv, cfg.bmin, cfg.bmax);
|
||||
rcCalcGridSize(cfg.bmin, cfg.bmax, cfg.cs, &cfg.width, &cfg.height);
|
||||
NavIn in = {v, nv, t, nt, area, cyl, nc, foot, nf, c};
|
||||
unsigned char *data = nullptr;
|
||||
int size = 0, polys = 0;
|
||||
if (!nav_pipeline(cfg, in, 0, 0, &data, &size, &polys) || polys == 0) return nullptr;
|
||||
return nav_from(data, size);
|
||||
}
|
||||
|
||||
|
|
@ -102,7 +116,13 @@ NAV_SHIM void nav_free(void *h) {
|
|||
delete n;
|
||||
}
|
||||
|
||||
// every polygon in every tile
|
||||
NAV_SHIM int nav_polys(void *h) {
|
||||
const dtMeshTile *t = static_cast<const dtNavMesh *>(static_cast<Nav *>(h)->mesh)->getTile(0);
|
||||
return t && t->header ? t->header->polyCount : 0;
|
||||
const dtNavMesh *m = static_cast<Nav *>(h)->mesh;
|
||||
int total = 0;
|
||||
for (int i = 0; i < m->getMaxTiles(); ++i) {
|
||||
const dtMeshTile *t = m->getTile(i);
|
||||
if (t && t->header) total += t->header->polyCount;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue