@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.
@@ -0,0 +1,97 @@
1
+ import { type Entity, type World } from '@aethervtt/shard-core';
2
+ import type { AppMethod } from '@aethervtt/shard-runtime';
3
+ /** Grids, navmeshes, and agents as data (the `nav.describe` method). */
4
+ export declare function describeNav(world: World): {
5
+ recast: boolean;
6
+ grids: {
7
+ entity: number;
8
+ path: string | null;
9
+ source: "colliders" | "data" | "tilemap";
10
+ size: number[] | null;
11
+ cellSize: number[];
12
+ origin: number[];
13
+ diagonal: "always" | "never" | "no-corners" | undefined;
14
+ walkableCells: number;
15
+ problem: string | null;
16
+ }[];
17
+ meshes: ({
18
+ entity: number;
19
+ path: string | null;
20
+ problem: string;
21
+ settings?: undefined;
22
+ tiles?: undefined;
23
+ emptyTiles?: undefined;
24
+ polygons?: undefined;
25
+ bounds?: undefined;
26
+ lastBake?: undefined;
27
+ bakes?: undefined;
28
+ tilesBuilt?: undefined;
29
+ cacheHits?: undefined;
30
+ waitingForMeshes?: undefined;
31
+ crowd?: undefined;
32
+ } | {
33
+ entity: number;
34
+ path: string | null;
35
+ settings: import("./recast.js").BakeSettings;
36
+ tiles: number;
37
+ emptyTiles: number;
38
+ polygons: number;
39
+ bounds: {
40
+ min: number[];
41
+ max: number[];
42
+ } | null;
43
+ lastBake: {
44
+ ms: number;
45
+ built: number;
46
+ fromCache: number;
47
+ kept: number;
48
+ removed: number;
49
+ };
50
+ bakes: number;
51
+ tilesBuilt: number;
52
+ cacheHits: number;
53
+ waitingForMeshes: number;
54
+ crowd: {
55
+ agents: number;
56
+ capacity: number;
57
+ } | null;
58
+ problem: string | null;
59
+ })[];
60
+ sources: {
61
+ triangles: number;
62
+ links: number;
63
+ skipped: {
64
+ entity: Entity;
65
+ reason: string;
66
+ path: string | null;
67
+ }[];
68
+ };
69
+ cache: {
70
+ tiles: number;
71
+ loadedFromDisk: number;
72
+ file: string | null;
73
+ };
74
+ agents: {
75
+ entity: number;
76
+ path: string | null;
77
+ on: {
78
+ entity: number;
79
+ path: string | null;
80
+ kind: "grid" | "mesh" | "none";
81
+ } | null;
82
+ status: "arrived" | "idle" | "moving" | "unreachable";
83
+ remaining: number;
84
+ corners: number;
85
+ velocity: number[];
86
+ destination: number[] | null;
87
+ target: {
88
+ entity: number;
89
+ path: string | null;
90
+ } | null;
91
+ drive: "character" | "transform" | "velocity" | undefined;
92
+ problem?: string | undefined;
93
+ route: number[][];
94
+ }[];
95
+ statuses: readonly ["idle", "moving", "arrived", "unreachable"];
96
+ };
97
+ export declare const navMethods: AppMethod[];
@@ -0,0 +1,179 @@
1
+ import { defineSchema, findComponent, t } from "@aethervtt/shard-core";
2
+ import { liveTileKeys, NO_RECAST, updateNavigation } from "./bake.js";
3
+ import { NavCache, saveNavCache } from "./cache.js";
4
+ import { AGENT_STATUSES, DRIVES, NavAgent, NavAgentState, NavMesh } from "./components.js";
5
+ import { DIAGONAL_MODES } from "./grid.js";
6
+ import { findPath } from "./query.js";
7
+ import { navState } from "./state.js";
8
+ const round = (x, digits = 3) => Math.round(x * 10 ** digits) / 10 ** digits;
9
+ const r3 = (v) => [round(v[0]), round(v[1]), round(v[2])];
10
+ function scenePath(world, entity) {
11
+ if (entity === null || entity < 0) return null;
12
+ const member = findComponent("scene/SceneMember");
13
+ if (!member || !world.isAlive(entity)) return null;
14
+ return world.tryGet(entity, member)?.path || null;
15
+ }
16
+ function describeNav(world) {
17
+ const nav = navState(world);
18
+ const cache = world.tryResource(NavCache);
19
+ const meshEntities = world.query({ with: [NavMesh] }).entities();
20
+ return {
21
+ recast: nav.R !== null,
22
+ grids: [...nav.grids.values()].map((g) => {
23
+ let walkable = 0;
24
+ if (g.grid) {
25
+ for (let i = 0; i < g.grid.costs.length; i++) if (g.grid.costs[i] > 0) walkable++;
26
+ }
27
+ return {
28
+ entity: g.entity,
29
+ path: scenePath(world, g.entity),
30
+ source: g.source,
31
+ size: g.grid ? [g.grid.width, g.grid.height] : null,
32
+ cellSize: [round(g.csx), round(g.csy)],
33
+ origin: [round(g.ox), round(g.oy)],
34
+ diagonal: DIAGONAL_MODES[g.diagonal],
35
+ walkableCells: walkable,
36
+ problem: g.problem
37
+ };
38
+ }),
39
+ meshes: meshEntities.map((entity) => {
40
+ const rt = nav.meshes.get(entity);
41
+ if (!rt)
42
+ return {
43
+ entity,
44
+ path: scenePath(world, entity),
45
+ problem: nav.R ? "not baked yet" : NO_RECAST
46
+ };
47
+ const walkable = [...rt.tiles.values()].filter((tile) => tile.ref);
48
+ return {
49
+ entity,
50
+ path: scenePath(world, entity),
51
+ settings: rt.settings,
52
+ tiles: walkable.length,
53
+ emptyTiles: rt.tiles.size - walkable.length,
54
+ polygons: rt.polygons(),
55
+ bounds: walkable.length > 0 ? { min: r3(rt.min), max: r3(rt.max) } : null,
56
+ lastBake: {
57
+ ms: round(rt.stats.ms, 2),
58
+ built: rt.stats.built,
59
+ fromCache: rt.stats.cached,
60
+ kept: rt.stats.kept,
61
+ removed: rt.stats.removed
62
+ },
63
+ bakes: rt.stats.bakes,
64
+ tilesBuilt: rt.stats.totalBuilt,
65
+ cacheHits: rt.stats.totalCached,
66
+ waitingForMeshes: rt.pending,
67
+ crowd: rt.crowd ? { agents: rt.crowd.getActiveAgentCount(), capacity: rt.crowdCapacity } : null,
68
+ problem: rt.problem
69
+ };
70
+ }),
71
+ sources: {
72
+ triangles: nav.soup.count,
73
+ links: nav.links.length,
74
+ skipped: nav.skipped.map((s) => ({ ...s, path: scenePath(world, s.entity) }))
75
+ },
76
+ cache: {
77
+ tiles: cache?.tiles.size ?? 0,
78
+ loadedFromDisk: cache?.loaded ?? 0,
79
+ file: cache?.fs ? cache.path : null
80
+ },
81
+ agents: [...nav.agents.values()].filter((a) => world.isAlive(a.entity) && world.has(a.entity, NavAgent)).map((a) => {
82
+ const agent = world.get(a.entity, NavAgent);
83
+ const state = world.get(a.entity, NavAgentState);
84
+ return {
85
+ entity: a.entity,
86
+ path: scenePath(world, a.entity),
87
+ on: a.nav >= 0 ? { entity: a.nav, path: scenePath(world, a.nav), kind: a.kind } : null,
88
+ status: state.status,
89
+ remaining: round(state.remaining),
90
+ corners: state.corners,
91
+ velocity: r3(state.velocity),
92
+ destination: agent.target === null ? r3(agent.destination) : null,
93
+ target: agent.target === null ? null : { entity: agent.target, path: scenePath(world, agent.target) },
94
+ drive: DRIVES[a.drive],
95
+ ...DRIVES[a.drive] !== agent.drive ? {
96
+ problem: `drive ${agent.drive} needs a ${agent.drive === "character" ? "CharacterController" : "Velocity"}; moving the Transform`
97
+ } : {},
98
+ route: Array.from(
99
+ { length: a.count },
100
+ (_, i) => r3(a.corners.subarray(i * 3, i * 3 + 3))
101
+ )
102
+ };
103
+ }),
104
+ statuses: AGENT_STATUSES
105
+ };
106
+ }
107
+ const navMethods = [
108
+ {
109
+ name: "nav.path",
110
+ description: "A path between two world points on the NavGrid or NavMesh containing `from` (or `nav`): status (complete, partial: ends at the closest reachable point, none), corners, and length. Errors nav/no-navmesh and nav/out-of-bounds say why there is none.",
111
+ params: defineSchema("nav/PathParams", {
112
+ from: t.vec3({ required: true }),
113
+ to: t.vec3({ required: true }),
114
+ nav: t.entity({ description: "The NavGrid or NavMesh entity (default: the one at from)." }),
115
+ areas: t.json({ description: 'Area costs for this query: { "1": 10 } (0 excludes).' })
116
+ }),
117
+ handler: ({ world }, p) => {
118
+ const path = findPath(world, p.from, p.to, {
119
+ nav: p.nav ?? null,
120
+ areas: p.areas ?? void 0
121
+ });
122
+ return {
123
+ status: path.status,
124
+ length: round(path.length),
125
+ corners: Array.from(
126
+ { length: path.count },
127
+ (_, i) => r3(path.corners.subarray(i * 3, i * 3 + 3))
128
+ ),
129
+ nav: path.nav,
130
+ navPath: scenePath(world, path.nav)
131
+ };
132
+ }
133
+ },
134
+ {
135
+ name: "nav.describe",
136
+ description: "Navigation state: every NavGrid (source, size, cell size, walkable cells, problem), NavMesh (tiles, polygons, bounds, last bake: ms, tiles built by Recast, from cache, kept; totals, meshes still loading), the triangles and off-mesh links baked from, the tile cache, and every NavAgent (status, remaining distance, velocity, destination or target, drive, route corners).",
137
+ params: defineSchema("nav/DescribeParams", {}),
138
+ handler: ({ world }) => describeNav(world)
139
+ },
140
+ {
141
+ name: "nav.bake",
142
+ description: "Rebakes every NavMesh now and reports tiles built by Recast and loaded from the cache; with save (default) writes the tiles to .shard/cache/nav so the next load skips Recast. force rebuilds every tile.",
143
+ params: defineSchema("nav/BakeParams", {
144
+ save: t.bool({ default: true, description: "Write the tile cache to disk." }),
145
+ force: t.bool({ description: "Ignore cached and current tiles: Recast rebuilds all." })
146
+ }),
147
+ handler: async ({ world }, p) => {
148
+ const nav = navState(world);
149
+ const cache = world.initResource(NavCache);
150
+ if (p.force) {
151
+ for (const rt of nav.meshes.values()) {
152
+ for (const tile of rt.tiles.values()) cache.tiles.delete(tile.key);
153
+ for (const tile of [...rt.tiles.values()]) rt.removeTile(tile.tx, tile.ty);
154
+ }
155
+ }
156
+ nav.dirty = true;
157
+ updateNavigation(world, 0);
158
+ const saved = p.save === false ? 0 : await saveNavCache(world, liveTileKeys(nav));
159
+ return {
160
+ meshes: [...nav.meshes.values()].map((rt) => ({
161
+ entity: rt.entity,
162
+ path: scenePath(world, rt.entity),
163
+ tiles: [...rt.tiles.values()].filter((tile) => tile.ref).length,
164
+ polygons: rt.polygons(),
165
+ built: rt.stats.built,
166
+ fromCache: rt.stats.cached,
167
+ kept: rt.stats.kept,
168
+ ms: round(rt.stats.ms, 2),
169
+ problem: rt.problem
170
+ })),
171
+ saved: saved > 0 ? { file: cache.path, bytes: saved } : null
172
+ };
173
+ }
174
+ }
175
+ ];
176
+ export {
177
+ describeNav,
178
+ navMethods
179
+ };
@@ -0,0 +1,101 @@
1
+ import type { Entity } from '@aethervtt/shard-core';
2
+ import { TriangleSoup } from './geometry.js';
3
+ import { type BakeSettings, type Recast } from './recast.js';
4
+ type RNavMesh = InstanceType<Recast['NavMesh']>;
5
+ type RQuery = InstanceType<Recast['NavMeshQuery']>;
6
+ type RCrowd = InstanceType<Recast['Crowd']>;
7
+ type RFilter = InstanceType<Recast['QueryFilter']>;
8
+ /** A tile the navmesh holds: where, the key of the geometry that built it, and its Detour ref. */
9
+ export interface TileEntry {
10
+ tx: number;
11
+ ty: number;
12
+ key: string;
13
+ /** Detour tile data, or null for a tile with nothing walkable (not added). */
14
+ bytes: Uint8Array | null;
15
+ ref: number;
16
+ polys: number;
17
+ }
18
+ export declare const packTile: (tx: number, ty: number) => number;
19
+ export interface BakeStats {
20
+ /** Tiles Recast built in the last bake. */
21
+ built: number;
22
+ /** Tiles loaded from the cache in the last bake. */
23
+ cached: number;
24
+ /** Tiles kept because their geometry didn't change. */
25
+ kept: number;
26
+ /** Tiles dropped (their geometry went away). */
27
+ removed: number;
28
+ /** Milliseconds the last bake took. */
29
+ ms: number;
30
+ /** Bakes since the navmesh appeared. */
31
+ bakes: number;
32
+ /** Tiles Recast built since the navmesh appeared. */
33
+ totalBuilt: number;
34
+ /** Tiles taken from the cache since the navmesh appeared. */
35
+ totalCached: number;
36
+ }
37
+ /**
38
+ * A NavMesh entity's Detour navmesh: tiles on a world-aligned grid (tile (0, 0) starts at the
39
+ * origin, so moving geometry never shifts other tiles), a query object, and the crowd its agents
40
+ * walk in.
41
+ */
42
+ export declare class NavMeshRuntime {
43
+ readonly R: Recast;
44
+ readonly entity: Entity;
45
+ readonly settings: BakeSettings;
46
+ readonly tileWorld: number;
47
+ navMesh: RNavMesh;
48
+ query: RQuery;
49
+ filter: RFilter;
50
+ crowd: RCrowd | null;
51
+ crowdCapacity: number;
52
+ crowdRadius: number;
53
+ maxTiles: number;
54
+ readonly tiles: Map<number, TileEntry>;
55
+ /** Bumps whenever tiles change: agents and cached paths revalidate. */
56
+ version: number;
57
+ readonly stats: BakeStats;
58
+ /** Bounds of the walkable tiles, for "which navmesh contains this point". */
59
+ readonly min: Float64Array<ArrayBuffer>;
60
+ readonly max: Float64Array<ArrayBuffer>;
61
+ /** Why the last bake didn't finish, if it didn't. */
62
+ problem: string | null;
63
+ /** Source meshes still loading; the bake reruns when they arrive. */
64
+ pending: number;
65
+ /** Bakes and queries happen in this entity's local space (null: world space). */
66
+ frame: Entity | null;
67
+ /** World → navmesh space and back (affine 3×4, row by row). */
68
+ readonly toLocal: Float64Array<ArrayBuffer>;
69
+ readonly toWorld: Float64Array<ArrayBuffer>;
70
+ /** Framed bakes: the world soup moved into navmesh space, with its hash lanes. */
71
+ readonly local: TriangleSoup;
72
+ localHashes: Uint32Array<ArrayBuffer>;
73
+ /** A world point into navmesh space (identity without a frame). */
74
+ pointIn(x: number, y: number, z: number, out: {
75
+ [i: number]: number;
76
+ }): void;
77
+ /** A navmesh-space point into world space. */
78
+ pointOut(x: number, y: number, z: number, out: {
79
+ [i: number]: number;
80
+ }): void;
81
+ /** A navmesh-space direction into world space. */
82
+ vectorOut(x: number, y: number, z: number, out: {
83
+ [i: number]: number;
84
+ }): void;
85
+ constructor(R: Recast, entity: Entity, settings: BakeSettings, maxTiles?: number);
86
+ private createNavMesh;
87
+ /** Grows the tile capacity, moving every tile into a new Detour navmesh. */
88
+ reserve(tiles: number): boolean;
89
+ private add;
90
+ /** Puts a tile's data in (null: removes it). */
91
+ setTile(tx: number, ty: number, key: string, bytes: Uint8Array | null): void;
92
+ removeTile(tx: number, ty: number): void;
93
+ /** Recomputes `min`/`max` from the tiles' headers. */
94
+ updateBounds(): void;
95
+ polygons(): number;
96
+ /** Applies area costs (0 excludes an area) to queries and the crowd. */
97
+ applyAreas(areas: Record<number, number>): void;
98
+ destroyCrowd(): void;
99
+ destroy(): void;
100
+ }
101
+ export {};
@@ -0,0 +1,194 @@
1
+ import { TriangleSoup } from "./geometry.js";
2
+ import { areaFlags, voxelSettings } from "./recast.js";
3
+ const packTile = (tx, ty) => (tx + 32768) * 65536 + (ty + 32768);
4
+ class NavMeshRuntime {
5
+ R;
6
+ entity;
7
+ settings;
8
+ tileWorld;
9
+ navMesh;
10
+ query;
11
+ filter;
12
+ crowd = null;
13
+ crowdCapacity = 0;
14
+ crowdRadius = 0;
15
+ maxTiles;
16
+ tiles = /* @__PURE__ */ new Map();
17
+ /** Bumps whenever tiles change: agents and cached paths revalidate. */
18
+ version = 0;
19
+ stats = {
20
+ built: 0,
21
+ cached: 0,
22
+ kept: 0,
23
+ removed: 0,
24
+ ms: 0,
25
+ bakes: 0,
26
+ totalBuilt: 0,
27
+ totalCached: 0
28
+ };
29
+ /** Bounds of the walkable tiles, for "which navmesh contains this point". */
30
+ min = new Float64Array([Infinity, Infinity, Infinity]);
31
+ max = new Float64Array([-Infinity, -Infinity, -Infinity]);
32
+ /** Why the last bake didn't finish, if it didn't. */
33
+ problem = null;
34
+ /** Source meshes still loading; the bake reruns when they arrive. */
35
+ pending = 0;
36
+ /** Bakes and queries happen in this entity's local space (null: world space). */
37
+ frame = null;
38
+ /** World → navmesh space and back (affine 3×4, row by row). */
39
+ toLocal = new Float64Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0]);
40
+ toWorld = new Float64Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0]);
41
+ /** Framed bakes: the world soup moved into navmesh space, with its hash lanes. */
42
+ local = new TriangleSoup();
43
+ localHashes = new Uint32Array(0);
44
+ /** A world point into navmesh space (identity without a frame). */
45
+ pointIn(x, y, z, out) {
46
+ const m = this.toLocal;
47
+ out[0] = m[0] * x + m[1] * y + m[2] * z + m[3];
48
+ out[1] = m[4] * x + m[5] * y + m[6] * z + m[7];
49
+ out[2] = m[8] * x + m[9] * y + m[10] * z + m[11];
50
+ }
51
+ /** A navmesh-space point into world space. */
52
+ pointOut(x, y, z, out) {
53
+ const m = this.toWorld;
54
+ out[0] = m[0] * x + m[1] * y + m[2] * z + m[3];
55
+ out[1] = m[4] * x + m[5] * y + m[6] * z + m[7];
56
+ out[2] = m[8] * x + m[9] * y + m[10] * z + m[11];
57
+ }
58
+ /** A navmesh-space direction into world space. */
59
+ vectorOut(x, y, z, out) {
60
+ const m = this.toWorld;
61
+ out[0] = m[0] * x + m[1] * y + m[2] * z;
62
+ out[1] = m[4] * x + m[5] * y + m[6] * z;
63
+ out[2] = m[8] * x + m[9] * y + m[10] * z;
64
+ }
65
+ constructor(R, entity, settings, maxTiles = 256) {
66
+ this.R = R;
67
+ this.entity = entity;
68
+ this.settings = { ...settings };
69
+ this.tileWorld = voxelSettings(settings).tileWorld;
70
+ this.maxTiles = maxTiles;
71
+ this.navMesh = this.createNavMesh(maxTiles);
72
+ this.query = new R.NavMeshQuery(this.navMesh, { maxNodes: 4096 });
73
+ this.filter = this.query.defaultFilter;
74
+ }
75
+ createNavMesh(maxTiles) {
76
+ const R = this.R;
77
+ const tileBits = Math.min(14, Math.max(1, Math.ceil(Math.log2(maxTiles))));
78
+ const navMesh = new R.NavMesh();
79
+ navMesh.initTiled(
80
+ R.NavMeshParams.create({
81
+ orig: { x: 0, y: 0, z: 0 },
82
+ tileWidth: this.tileWorld,
83
+ tileHeight: this.tileWorld,
84
+ maxTiles: 1 << tileBits,
85
+ maxPolys: 1 << 22 - tileBits
86
+ })
87
+ );
88
+ return navMesh;
89
+ }
90
+ /** Grows the tile capacity, moving every tile into a new Detour navmesh. */
91
+ reserve(tiles) {
92
+ if (tiles <= this.maxTiles) return false;
93
+ let cap = this.maxTiles;
94
+ while (cap < tiles) cap *= 2;
95
+ const old = this.navMesh;
96
+ this.maxTiles = cap;
97
+ this.navMesh = this.createNavMesh(cap);
98
+ this.query.destroy();
99
+ this.query = new this.R.NavMeshQuery(this.navMesh, { maxNodes: 4096 });
100
+ this.filter = this.query.defaultFilter;
101
+ for (const t of this.tiles.values()) {
102
+ t.ref = 0;
103
+ if (t.bytes) this.add(t);
104
+ }
105
+ old.destroy();
106
+ this.destroyCrowd();
107
+ this.version++;
108
+ return true;
109
+ }
110
+ add(t) {
111
+ const R = this.R;
112
+ const data = new R.UnsignedCharArray();
113
+ data.copy(t.bytes);
114
+ const res = this.navMesh.addTile(data, R.Detour.DT_TILE_FREE_DATA, 0);
115
+ if (R.statusFailed(res.status)) {
116
+ data.destroy();
117
+ t.ref = 0;
118
+ t.polys = 0;
119
+ return;
120
+ }
121
+ t.ref = res.tileRef;
122
+ const tile = this.navMesh.getTileByRef(res.tileRef);
123
+ t.polys = tile ? tile.header()?.polyCount() ?? 0 : 0;
124
+ }
125
+ /** Puts a tile's data in (null: removes it). */
126
+ setTile(tx, ty, key, bytes) {
127
+ const k = packTile(tx, ty);
128
+ const old = this.tiles.get(k);
129
+ if (old?.ref) this.navMesh.removeTile(old.ref);
130
+ const t = { tx, ty, key, bytes, ref: 0, polys: 0 };
131
+ this.tiles.set(k, t);
132
+ if (bytes) this.add(t);
133
+ this.version++;
134
+ }
135
+ removeTile(tx, ty) {
136
+ const k = packTile(tx, ty);
137
+ const old = this.tiles.get(k);
138
+ if (!old) return;
139
+ if (old.ref) this.navMesh.removeTile(old.ref);
140
+ this.tiles.delete(k);
141
+ this.version++;
142
+ }
143
+ /** Recomputes `min`/`max` from the tiles' headers. */
144
+ updateBounds() {
145
+ this.min.fill(Infinity);
146
+ this.max.fill(-Infinity);
147
+ for (const t of this.tiles.values()) {
148
+ if (!t.ref) continue;
149
+ const header = this.navMesh.getTileByRef(t.ref)?.header();
150
+ if (!header) continue;
151
+ for (let i = 0; i < 3; i++) {
152
+ this.min[i] = Math.min(this.min[i], header.bmin(i));
153
+ this.max[i] = Math.max(this.max[i], header.bmax(i));
154
+ }
155
+ }
156
+ }
157
+ polygons() {
158
+ let n = 0;
159
+ for (const t of this.tiles.values()) n += t.polys;
160
+ return n;
161
+ }
162
+ /** Applies area costs (0 excludes an area) to queries and the crowd. */
163
+ applyAreas(areas) {
164
+ let exclude = 0;
165
+ const filters = [this.filter];
166
+ if (this.crowd) filters.push(this.crowd.getFilter(0));
167
+ for (const f of filters) {
168
+ for (let a = 0; a < 63; a++) f.setAreaCost(a, 1);
169
+ for (const key of Object.keys(areas)) {
170
+ const area = Number(key);
171
+ const cost = areas[area];
172
+ if (!(area >= 0 && area < 63)) continue;
173
+ if (cost <= 0) exclude |= area < 15 ? areaFlags(area) : 0;
174
+ else f.setAreaCost(area, cost);
175
+ }
176
+ f.includeFlags = 65535 & ~exclude;
177
+ f.excludeFlags = 0;
178
+ }
179
+ }
180
+ destroyCrowd() {
181
+ this.crowd?.destroy();
182
+ this.crowd = null;
183
+ this.crowdCapacity = 0;
184
+ }
185
+ destroy() {
186
+ this.destroyCrowd();
187
+ this.query.destroy();
188
+ this.navMesh.destroy();
189
+ }
190
+ }
191
+ export {
192
+ NavMeshRuntime,
193
+ packTile
194
+ };
@@ -0,0 +1 @@
1
+ export declare const navOverlay: import("@aethervtt/shard-render").OverlayDef;