@aethervtt/shard-nav 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/query.js ADDED
@@ -0,0 +1,391 @@
1
+ import { ShardError } from "@aethervtt/shard-core";
2
+ import { GlobalTransform } from "@aethervtt/shard-transform";
3
+ import { NavAgent } from "./components.js";
4
+ import { nearestWalkable, smoothGridPath, traceSegment } from "./grid.js";
5
+ import { navState } from "./state.js";
6
+ const PATH_STATUSES = ["complete", "partial", "none"];
7
+ function createNavPath(capacity = 256) {
8
+ return { corners: new Float32Array(capacity * 3), count: 0, length: 0, status: "none", nav: null };
9
+ }
10
+ const SNAP_CELLS = 8;
11
+ function inGrid(g, x, y) {
12
+ if (!g.grid) return false;
13
+ const cx = (x - g.ox) / g.csx;
14
+ const cy = (y - g.oy) / g.csy;
15
+ return cx >= 0 && cy >= 0 && cx < g.grid.width && cy < g.grid.height;
16
+ }
17
+ function inMesh(m, x, y, z) {
18
+ const pad = m.settings.agentRadius + m.settings.cellSize * 2;
19
+ return x >= m.min[0] - pad && x <= m.max[0] + pad && z >= m.min[2] - pad && z <= m.max[2] + pad && y >= m.min[1] - m.settings.agentHeight && y <= m.max[1] + m.settings.agentHeight;
20
+ }
21
+ let foundGrid = null;
22
+ let foundMesh = null;
23
+ const inFrame = new Float64Array(3);
24
+ function locate(state, point, nav) {
25
+ foundGrid = null;
26
+ foundMesh = null;
27
+ if (nav !== null) {
28
+ foundGrid = state.grids.get(nav) ?? null;
29
+ if (foundGrid && !foundGrid.grid) foundGrid = null;
30
+ if (!foundGrid) foundMesh = state.meshes.get(nav) ?? null;
31
+ return foundGrid !== null || foundMesh !== null;
32
+ }
33
+ const x = point[0];
34
+ const y = point[1];
35
+ const z = point[2] ?? 0;
36
+ for (let i = 0; i < state.gridList.length; i++) {
37
+ const g = state.gridList[i];
38
+ if (inGrid(g, x, y)) {
39
+ foundGrid = g;
40
+ return true;
41
+ }
42
+ }
43
+ for (let i = 0; i < state.meshList.length; i++) {
44
+ const m = state.meshList[i];
45
+ if (m.frame !== null) {
46
+ m.pointIn(x, y, z, inFrame);
47
+ if (inMesh(m, inFrame[0], inFrame[1], inFrame[2])) {
48
+ foundMesh = m;
49
+ return true;
50
+ }
51
+ } else if (inMesh(m, x, y, z)) {
52
+ foundMesh = m;
53
+ return true;
54
+ }
55
+ }
56
+ return false;
57
+ }
58
+ function navAt(world, point, nav = null) {
59
+ if (!locate(navState(world), point, nav)) return void 0;
60
+ if (foundGrid) return { kind: "grid", entity: foundGrid.entity, grid: foundGrid };
61
+ return { kind: "mesh", entity: foundMesh.entity, mesh: foundMesh };
62
+ }
63
+ function requireNav(world, point, nav) {
64
+ const state = navState(world);
65
+ if (locate(state, point, nav)) return;
66
+ if (nav !== null) {
67
+ throw new ShardError("nav/no-navmesh", `Entity ${nav} has no built NavGrid or NavMesh`, {
68
+ hint: "Check nav.describe: the grid may be waiting for data, or the navmesh for sources."
69
+ });
70
+ }
71
+ if (state.grids.size === 0 && state.meshes.size === 0) {
72
+ throw new ShardError("nav/no-navmesh", "There is no NavGrid or NavMesh to query", {
73
+ hint: "Add a nav/NavGrid (2D) or a nav/NavMesh and tag level geometry with nav/NavSource."
74
+ });
75
+ }
76
+ const p = [point[0], point[1], point[2] ?? 0].map((v) => Math.round((v ?? 0) * 100) / 100);
77
+ throw new ShardError(
78
+ "nav/out-of-bounds",
79
+ `(${p.join(", ")}) is outside every NavGrid and NavMesh`,
80
+ { hint: "nav.describe lists each grid and navmesh with its bounds." }
81
+ );
82
+ }
83
+ const ends = new Float64Array(4);
84
+ let cellScratch = new Float32Array(2 * 256);
85
+ function findPath(world, from, to, options = {}) {
86
+ const out = options.out ?? createNavPath();
87
+ let navEntity = options.nav ?? null;
88
+ if (navEntity === null && options.agent != null && world.has(options.agent, NavAgent)) {
89
+ navEntity = world.get(options.agent, NavAgent).nav;
90
+ }
91
+ requireNav(world, from, navEntity);
92
+ if (foundGrid) {
93
+ out.nav = foundGrid.entity;
94
+ gridPath(navState(world), foundGrid, from, to, out);
95
+ } else {
96
+ out.nav = foundMesh.entity;
97
+ const m = foundMesh;
98
+ if (m.frame === null) meshPath(m, from, to, out, options.areas);
99
+ else {
100
+ m.pointIn(from[0], from[1], from[2] ?? 0, fromLocal);
101
+ m.pointIn(to[0], to[1], to[2] ?? 0, toLocal);
102
+ meshPath(m, fromLocal, toLocal, out, options.areas);
103
+ const c = out.corners;
104
+ for (let i = 0; i < out.count; i++) {
105
+ m.pointOut(c[i * 3], c[i * 3 + 1], c[i * 3 + 2], inFrame);
106
+ c[i * 3] = inFrame[0];
107
+ c[i * 3 + 1] = inFrame[1];
108
+ c[i * 3 + 2] = inFrame[2];
109
+ }
110
+ }
111
+ }
112
+ return out;
113
+ }
114
+ const fromLocal = new Float64Array(3);
115
+ const toLocal = new Float64Array(3);
116
+ function meshPathLocal(m, from, to, out, areas) {
117
+ out.nav = m.entity;
118
+ meshPath(m, from, to, out, areas);
119
+ }
120
+ function gridPath(state, g, from, to, out) {
121
+ const grid = g.grid;
122
+ const w = grid.width;
123
+ const fx = (from[0] - g.ox) / g.csx;
124
+ const fy = (from[1] - g.oy) / g.csy;
125
+ let tx = (to[0] - g.ox) / g.csx;
126
+ let ty = (to[1] - g.oy) / g.csy;
127
+ const z = from[2] ?? g.z;
128
+ out.count = 0;
129
+ out.length = 0;
130
+ out.status = "none";
131
+ const start = nearestWalkable(grid, fx, fy, SNAP_CELLS);
132
+ if (start === -1) return;
133
+ const outside = tx < 0 || ty < 0 || tx >= grid.width || ty >= grid.height;
134
+ if (outside) {
135
+ tx = tx < 0 ? 0.5 : tx >= grid.width ? grid.width - 0.5 : tx;
136
+ ty = ty < 0 ? 0.5 : ty >= grid.height ? grid.height - 0.5 : ty;
137
+ }
138
+ let goal = nearestWalkable(grid, tx, ty, SNAP_CELLS);
139
+ const noGoal = goal === -1;
140
+ if (noGoal) goal = start;
141
+ const search = state.search;
142
+ const sx = start % w;
143
+ const sy = (start - sx) / w;
144
+ const gx = goal % w;
145
+ const gy = (goal - gx) / w;
146
+ search.search(grid, sx, sy, gx, gy, g.diagonal, g.csx, g.csy);
147
+ const reached = search.reached && !noGoal;
148
+ const snappedStart = Math.floor(fx) !== sx || Math.floor(fy) !== sy;
149
+ ends[0] = snappedStart ? sx + 0.5 : fx;
150
+ ends[1] = snappedStart ? sy + 0.5 : fy;
151
+ const last = search.cells[search.count - 1];
152
+ const lx = last % w;
153
+ const ly = (last - lx) / w;
154
+ const exact = reached && Math.floor(tx) === lx && Math.floor(ty) === ly && !outside;
155
+ ends[2] = exact ? tx : lx + 0.5;
156
+ ends[3] = exact ? ty : ly + 0.5;
157
+ const capacity = Math.floor(out.corners.length / 3);
158
+ if (cellScratch.length < capacity * 2) cellScratch = new Float32Array(capacity * 2);
159
+ const n = smoothGridPath(
160
+ grid,
161
+ search.cells,
162
+ search.count,
163
+ ends,
164
+ g.diagonal,
165
+ cellScratch,
166
+ 2,
167
+ capacity
168
+ );
169
+ const c = out.corners;
170
+ let length = 0;
171
+ for (let i = 0; i < n; i++) {
172
+ c[i * 3] = g.ox + cellScratch[i * 2] * g.csx;
173
+ c[i * 3 + 1] = g.oy + cellScratch[i * 2 + 1] * g.csy;
174
+ c[i * 3 + 2] = z;
175
+ if (i > 0) {
176
+ const dx = c[i * 3] - c[i * 3 - 3];
177
+ const dy = c[i * 3 + 1] - c[i * 3 - 2];
178
+ length += Math.sqrt(dx * dx + dy * dy);
179
+ }
180
+ }
181
+ out.count = n;
182
+ out.length = length;
183
+ out.status = exact ? "complete" : "partial";
184
+ }
185
+ const v3 = { x: 0, y: 0, z: 0 };
186
+ const v3b = { x: 0, y: 0, z: 0 };
187
+ const extents = { x: 0, y: 0, z: 0 };
188
+ function queryExtents(m) {
189
+ extents.x = Math.max(1, m.settings.agentRadius * 4);
190
+ extents.y = Math.max(2, m.settings.agentHeight * 1.5);
191
+ extents.z = extents.x;
192
+ return extents;
193
+ }
194
+ function areaFilter(m, areas) {
195
+ const f = new m.R.QueryFilter();
196
+ for (let a = 0; a < 63; a++) f.setAreaCost(a, m.filter.getAreaCost(a));
197
+ let exclude = ~m.filter.includeFlags & 65535;
198
+ for (const key of Object.keys(areas)) {
199
+ const area = Number(key);
200
+ const cost = areas[area];
201
+ if (!(area >= 0 && area < 63)) continue;
202
+ if (cost <= 0) exclude |= area < 15 ? 1 << area : 1 << 15;
203
+ else f.setAreaCost(area, cost);
204
+ }
205
+ f.includeFlags = 65535 & ~exclude;
206
+ return f;
207
+ }
208
+ function meshPath(m, from, to, out, areas) {
209
+ out.count = 0;
210
+ out.length = 0;
211
+ out.status = "none";
212
+ const filter = areas ? areaFilter(m, areas) : m.filter;
213
+ try {
214
+ const q = m.query;
215
+ const halfExtents = queryExtents(m);
216
+ v3.x = from[0];
217
+ v3.y = from[1];
218
+ v3.z = from[2];
219
+ const start = q.findNearestPoly(v3, { filter, halfExtents });
220
+ v3b.x = to[0];
221
+ v3b.y = to[1];
222
+ v3b.z = to[2];
223
+ const end = q.findNearestPoly(v3b, { filter, halfExtents });
224
+ if (!start.success || start.nearestRef === 0) return;
225
+ const startPos = start.nearestPoint;
226
+ let endRef = end.success ? end.nearestRef : 0;
227
+ let endPos = endRef ? end.nearestPoint : v3b;
228
+ const capacity = Math.floor(out.corners.length / 3);
229
+ if (endRef === 0) {
230
+ endRef = start.nearestRef;
231
+ endPos = startPos;
232
+ }
233
+ const path = q.findPath(start.nearestRef, endRef, startPos, endPos, {
234
+ filter,
235
+ maxPathPolys: 512
236
+ });
237
+ const polys = path.polys;
238
+ try {
239
+ if (!path.success || polys.size === 0) return;
240
+ let n = 0;
241
+ while (n < polys.size && polys.get(n) !== 0) n++;
242
+ if (n === 0) return;
243
+ const lastPoly = polys.get(n - 1);
244
+ let target = endPos;
245
+ const complete = lastPoly === end.nearestRef && end.success && end.nearestRef !== 0;
246
+ if (lastPoly !== endRef) {
247
+ const closest = q.closestPointOnPoly(lastPoly, endPos);
248
+ if (closest.success) target = closest.closestPoint;
249
+ }
250
+ if (n < polys.size) polys.resize(n);
251
+ const straight = q.findStraightPath(startPos, target, polys, {
252
+ maxStraightPathPoints: capacity
253
+ });
254
+ try {
255
+ if (!straight.success) return;
256
+ const c = out.corners;
257
+ const count = straight.straightPathCount;
258
+ let length = 0;
259
+ for (let i = 0; i < count; i++) {
260
+ c[i * 3] = straight.straightPath.get(i * 3);
261
+ c[i * 3 + 1] = straight.straightPath.get(i * 3 + 1);
262
+ c[i * 3 + 2] = straight.straightPath.get(i * 3 + 2);
263
+ if (i > 0) {
264
+ const dx = c[i * 3] - c[i * 3 - 3];
265
+ const dy = c[i * 3 + 1] - c[i * 3 - 2];
266
+ const dz = c[i * 3 + 2] - c[i * 3 - 1];
267
+ length += Math.sqrt(dx * dx + dy * dy + dz * dz);
268
+ }
269
+ }
270
+ out.count = count;
271
+ out.length = length;
272
+ out.status = complete ? "complete" : "partial";
273
+ } finally {
274
+ straight.straightPath.destroy();
275
+ straight.straightPathFlags.destroy();
276
+ straight.straightPathRefs.destroy();
277
+ }
278
+ } finally {
279
+ polys.destroy();
280
+ }
281
+ } finally {
282
+ if (filter !== m.filter) m.R.Raw.destroy(filter.raw);
283
+ }
284
+ }
285
+ function nearestPoint(world, point, out, options = {}) {
286
+ requireNav(world, point, options.nav ?? null);
287
+ if (foundGrid) {
288
+ const g = foundGrid;
289
+ const grid = g.grid;
290
+ const x = (point[0] - g.ox) / g.csx;
291
+ const y = (point[1] - g.oy) / g.csy;
292
+ const cell = nearestWalkable(grid, x, y, Math.max(grid.width, grid.height));
293
+ if (cell === -1) return false;
294
+ const cx = cell % grid.width;
295
+ const cy = (cell - cx) / grid.width;
296
+ const inside = Math.floor(x) === cx && Math.floor(y) === cy;
297
+ out[0] = inside ? point[0] : g.ox + (cx + 0.5) * g.csx;
298
+ out[1] = inside ? point[1] : g.oy + (cy + 0.5) * g.csy;
299
+ out[2] = point[2] ?? g.z;
300
+ return true;
301
+ }
302
+ const m = foundMesh;
303
+ m.pointIn(point[0], point[1], point[2] ?? 0, inFrame);
304
+ v3.x = inFrame[0];
305
+ v3.y = inFrame[1];
306
+ v3.z = inFrame[2];
307
+ const r = m.query.findClosestPoint(v3, { filter: m.filter, halfExtents: queryExtents(m) });
308
+ if (!r.success || r.polyRef === 0) return false;
309
+ m.pointOut(r.point.x, r.point.y, r.point.z, inFrame);
310
+ out[0] = inFrame[0];
311
+ out[1] = inFrame[1];
312
+ out[2] = inFrame[2];
313
+ return true;
314
+ }
315
+ function createNavRayHit() {
316
+ return { t: 1, point: new Float64Array(3), normal: new Float64Array(3) };
317
+ }
318
+ const gridHit = { t: 1, x: 0, y: 0 };
319
+ const seg = new Float64Array(4);
320
+ function navRaycast(world, from, to, hit = createNavRayHit(), options = {}) {
321
+ requireNav(world, from, options.nav ?? null);
322
+ hit.normal.fill(0);
323
+ if (foundGrid) {
324
+ const g = foundGrid;
325
+ seg[0] = (from[0] - g.ox) / g.csx;
326
+ seg[1] = (from[1] - g.oy) / g.csy;
327
+ seg[2] = (to[0] - g.ox) / g.csx;
328
+ seg[3] = (to[1] - g.oy) / g.csy;
329
+ const clear = traceSegment(g.grid, seg, g.diagonal, 255, gridHit);
330
+ hit.t = gridHit.t;
331
+ hit.point[0] = g.ox + gridHit.x * g.csx;
332
+ hit.point[1] = g.oy + gridHit.y * g.csy;
333
+ hit.point[2] = from[2] ?? g.z;
334
+ if (!clear) {
335
+ const dx = seg[2] - seg[0];
336
+ const dy = seg[3] - seg[1];
337
+ const fx = gridHit.x - Math.floor(gridHit.x);
338
+ const fy = gridHit.y - Math.floor(gridHit.y);
339
+ const onX = Math.min(fx, 1 - fx) < Math.min(fy, 1 - fy);
340
+ if (onX) hit.normal[0] = dx > 0 ? -1 : 1;
341
+ else hit.normal[1] = dy > 0 ? -1 : 1;
342
+ }
343
+ return !clear;
344
+ }
345
+ const m = foundMesh;
346
+ m.pointIn(from[0], from[1], from[2] ?? 0, fromLocal);
347
+ m.pointIn(to[0], to[1], to[2] ?? 0, toLocal);
348
+ v3.x = fromLocal[0];
349
+ v3.y = fromLocal[1];
350
+ v3.z = fromLocal[2];
351
+ const start = m.query.findNearestPoly(v3, { filter: m.filter, halfExtents: queryExtents(m) });
352
+ if (!start.success || start.nearestRef === 0) {
353
+ hit.t = 0;
354
+ hit.point[0] = from[0];
355
+ hit.point[1] = from[1];
356
+ hit.point[2] = from[2];
357
+ return true;
358
+ }
359
+ v3b.x = toLocal[0];
360
+ v3b.y = toLocal[1];
361
+ v3b.z = toLocal[2];
362
+ const r = m.query.raycast(start.nearestRef, start.nearestPoint, v3b, { filter: m.filter });
363
+ const t = r.t > 1 ? 1 : r.t;
364
+ hit.t = t;
365
+ const sp = start.nearestPoint;
366
+ m.pointOut(
367
+ sp.x + (toLocal[0] - sp.x) * t,
368
+ sp.y + (toLocal[1] - sp.y) * t,
369
+ sp.z + (toLocal[2] - sp.z) * t,
370
+ hit.point
371
+ );
372
+ if (t < 1) m.vectorOut(r.hitNormal.x, r.hitNormal.y, r.hitNormal.z, hit.normal);
373
+ return t < 1;
374
+ }
375
+ function worldPos(world, e, out) {
376
+ const m = world.get(e, GlobalTransform).matrix;
377
+ out[0] = m[3];
378
+ out[1] = m[7];
379
+ out[2] = m[11];
380
+ }
381
+ export {
382
+ PATH_STATUSES,
383
+ createNavPath,
384
+ createNavRayHit,
385
+ findPath,
386
+ meshPathLocal,
387
+ navAt,
388
+ navRaycast,
389
+ nearestPoint,
390
+ worldPos
391
+ };
@@ -0,0 +1,39 @@
1
+ import type * as RecastModule from 'recast-navigation';
2
+ import type { TriangleSoup } from './geometry.js';
3
+ export type Recast = typeof RecastModule;
4
+ /** Loads and initializes Recast and Detour (WASM) once per process. */
5
+ export declare function loadRecast(): Promise<Recast>;
6
+ /** What a NavMesh bakes with, as its component fields. */
7
+ export interface BakeSettings {
8
+ agentRadius: number;
9
+ agentHeight: number;
10
+ maxClimb: number;
11
+ maxSlope: number;
12
+ cellSize: number;
13
+ cellHeight: number;
14
+ tileSize: number;
15
+ }
16
+ /** Poly flags for an area: one bit per area 0–14, so filters can exclude areas exactly. */
17
+ export declare function areaFlags(area: number): number;
18
+ export interface OffMeshLinkParams {
19
+ start: [number, number, number];
20
+ end: [number, number, number];
21
+ radius: number;
22
+ bidirectional: boolean;
23
+ area: number;
24
+ }
25
+ /** Voxel counts Recast wants, from world-unit settings. */
26
+ export declare function voxelSettings(s: BakeSettings): {
27
+ walkableRadius: number;
28
+ walkableHeight: number;
29
+ walkableClimb: number;
30
+ borderSize: number;
31
+ tileWorld: number;
32
+ };
33
+ /**
34
+ * Builds one navmesh tile with Recast: the triangles of `soup` listed in `triangles` (already
35
+ * those overlapping the tile plus its border), rasterized with their area codes, filtered for
36
+ * the agent, and turned into Detour tile data. Returns the tile's bytes, or null when nothing is
37
+ * walkable in it. Throws `nav/bake-failed` when a Recast step fails.
38
+ */
39
+ export declare function buildTile(R: Recast, s: BakeSettings, tx: number, ty: number, yMin: number, yMax: number, soup: TriangleSoup, triangles: Int32Array, count: number, links: readonly OffMeshLinkParams[]): Uint8Array | null;
package/dist/recast.js ADDED
@@ -0,0 +1,161 @@
1
+ import { ShardError } from "@aethervtt/shard-core";
2
+ let module;
3
+ function loadRecast() {
4
+ module ??= (async () => {
5
+ const R = await import("recast-navigation");
6
+ await R.init();
7
+ return R;
8
+ })();
9
+ return module;
10
+ }
11
+ const WALKABLE = 63;
12
+ function areaFlags(area) {
13
+ return 1 << (area < 15 ? area : 15);
14
+ }
15
+ function voxelSettings(s) {
16
+ const walkableRadius = Math.ceil(s.agentRadius / s.cellSize);
17
+ return {
18
+ walkableRadius,
19
+ walkableHeight: Math.max(3, Math.ceil(s.agentHeight / s.cellHeight)),
20
+ walkableClimb: Math.floor(s.maxClimb / s.cellHeight),
21
+ borderSize: walkableRadius + 3,
22
+ tileWorld: s.tileSize * s.cellSize
23
+ };
24
+ }
25
+ const scratch = { verts: new Float32Array(0), tris: new Int32Array(0), areas: new Uint8Array(0) };
26
+ function buildTile(R, s, tx, ty, yMin, yMax, soup, triangles, count, links) {
27
+ const v = voxelSettings(s);
28
+ const cs = s.cellSize;
29
+ const ch = s.cellHeight;
30
+ const border = v.borderSize;
31
+ const size = s.tileSize + border * 2;
32
+ const bmin = [
33
+ tx * v.tileWorld - border * cs,
34
+ yMin,
35
+ ty * v.tileWorld - border * cs
36
+ ];
37
+ const bmax = [
38
+ (tx + 1) * v.tileWorld + border * cs,
39
+ yMax,
40
+ (ty + 1) * v.tileWorld + border * cs
41
+ ];
42
+ if (count === 0) return null;
43
+ if (scratch.areas.length < count) {
44
+ scratch.verts = new Float32Array(count * 9);
45
+ scratch.tris = new Int32Array(count * 3);
46
+ scratch.areas = new Uint8Array(count);
47
+ }
48
+ const p = soup.positions;
49
+ for (let i = 0; i < count; i++) {
50
+ const t = triangles[i];
51
+ scratch.verts.set(p.subarray(t * 9, t * 9 + 9), i * 9);
52
+ scratch.tris[i * 3] = i * 3;
53
+ scratch.tris[i * 3 + 1] = i * 3 + 1;
54
+ scratch.tris[i * 3 + 2] = i * 3 + 2;
55
+ scratch.areas[i] = soup.areas[t];
56
+ }
57
+ const ctx = new R.RecastBuildContext(false);
58
+ const verts = new R.VerticesArray();
59
+ const tris = new R.TrianglesArray();
60
+ const areas = new R.TriangleAreasArray();
61
+ verts.copy(scratch.verts.subarray(0, count * 9));
62
+ tris.copy(scratch.tris.subarray(0, count * 3));
63
+ areas.resize(count);
64
+ let hf;
65
+ let chf;
66
+ let cset;
67
+ let pmesh;
68
+ let dmesh;
69
+ const fail = (step) => {
70
+ throw new ShardError("nav/bake-failed", `Recast failed to ${step} for tile (${tx}, ${ty})`, {
71
+ hint: "Check the NavMesh settings: cellSize and cellHeight must be positive, and tileSize small enough for its bounds."
72
+ });
73
+ };
74
+ try {
75
+ hf = R.allocHeightfield();
76
+ if (!R.createHeightfield(ctx, hf, size, size, bmin, bmax, cs, ch)) fail("create a heightfield");
77
+ R.markWalkableTriangles(ctx, s.maxSlope, verts, count * 3, tris, count, areas);
78
+ const view = areas.getHeapView();
79
+ for (let i = 0; i < count; i++) {
80
+ const a = scratch.areas[i];
81
+ if (a !== 0 && view[i] === WALKABLE) view[i] = a;
82
+ }
83
+ if (!R.rasterizeTriangles(ctx, verts, count * 3, tris, areas, count, hf, v.walkableClimb))
84
+ fail("rasterize triangles");
85
+ R.filterLowHangingWalkableObstacles(ctx, v.walkableClimb, hf);
86
+ R.filterLedgeSpans(ctx, v.walkableHeight, v.walkableClimb, hf);
87
+ R.filterWalkableLowHeightSpans(ctx, v.walkableHeight, hf);
88
+ chf = R.allocCompactHeightfield();
89
+ if (!R.buildCompactHeightfield(ctx, v.walkableHeight, v.walkableClimb, hf, chf))
90
+ fail("build the compact heightfield");
91
+ R.freeHeightfield(hf);
92
+ hf = void 0;
93
+ if (!R.erodeWalkableArea(ctx, v.walkableRadius, chf)) fail("erode the walkable area");
94
+ if (!R.buildDistanceField(ctx, chf)) fail("build the distance field");
95
+ if (!R.buildRegions(ctx, chf, border, 8 * 8, 20 * 20)) fail("build regions");
96
+ cset = R.allocContourSet();
97
+ const maxEdge = Math.ceil(s.agentRadius * 8 / cs) || 12;
98
+ if (!R.buildContours(ctx, chf, 1.3, maxEdge, cset, R.Recast.RC_CONTOUR_TESS_WALL_EDGES))
99
+ fail("trace contours");
100
+ pmesh = R.allocPolyMesh();
101
+ if (!R.buildPolyMesh(ctx, cset, 6, pmesh)) fail("triangulate contours");
102
+ dmesh = R.allocPolyMeshDetail();
103
+ if (!R.buildPolyMeshDetail(ctx, pmesh, chf, cs * 6, ch * 1, dmesh))
104
+ fail("build the detail mesh");
105
+ const npolys = pmesh.npolys();
106
+ if (npolys === 0) return null;
107
+ for (let i = 0; i < npolys; i++) {
108
+ let area = pmesh.areas(i);
109
+ if (area === WALKABLE) {
110
+ area = 0;
111
+ pmesh.setAreas(i, 0);
112
+ }
113
+ pmesh.setFlags(i, areaFlags(area));
114
+ }
115
+ const params = new R.NavMeshCreateParams();
116
+ params.setPolyMeshCreateParams(pmesh);
117
+ params.setPolyMeshDetailCreateParams(dmesh);
118
+ params.setWalkableHeight(v.walkableHeight * ch);
119
+ params.setWalkableRadius(v.walkableRadius * cs);
120
+ params.setWalkableClimb(v.walkableClimb * ch);
121
+ params.setCellSize(cs);
122
+ params.setCellHeight(ch);
123
+ params.setBuildBvTree(true);
124
+ if (links.length > 0) {
125
+ params.setOffMeshConnections(
126
+ links.map((l) => ({
127
+ startPosition: { x: l.start[0], y: l.start[1], z: l.start[2] },
128
+ endPosition: { x: l.end[0], y: l.end[1], z: l.end[2] },
129
+ radius: l.radius,
130
+ bidirectional: l.bidirectional,
131
+ area: l.area,
132
+ flags: areaFlags(l.area)
133
+ }))
134
+ );
135
+ }
136
+ params.setTileX(tx);
137
+ params.setTileY(ty);
138
+ const result = R.createNavMeshData(params);
139
+ R.Raw.destroy(params.raw);
140
+ if (!result.success) fail("create Detour tile data");
141
+ const bytes = result.navMeshData.toTypedArray().slice();
142
+ result.navMeshData.destroy();
143
+ return bytes;
144
+ } finally {
145
+ if (hf) R.freeHeightfield(hf);
146
+ if (chf) R.freeCompactHeightfield(chf);
147
+ if (cset) R.freeContourSet(cset);
148
+ if (pmesh) R.freePolyMesh(pmesh);
149
+ if (dmesh) R.freePolyMeshDetail(dmesh);
150
+ verts.destroy();
151
+ tris.destroy();
152
+ areas.destroy();
153
+ R.Raw.destroy(ctx.raw);
154
+ }
155
+ }
156
+ export {
157
+ areaFlags,
158
+ buildTile,
159
+ loadRecast,
160
+ voxelSettings
161
+ };