@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,31 @@
1
+ import { type World } from '@aethervtt/shard-core';
2
+ import type { PlatformFileSystem } from '@aethervtt/shard-platform';
3
+ /** Where `shard bake nav` and `nav.bake` keep baked tiles, relative to the project root. */
4
+ export declare const NAV_CACHE_PATH = ".shard/cache/nav/tiles.bin";
5
+ /**
6
+ * Baked navmesh tiles by the key of the geometry and settings that made them: a tile whose key
7
+ * is here loads without running Recast. Null marks a tile that bakes to nothing walkable.
8
+ */
9
+ export declare class NavCacheStore {
10
+ readonly tiles: Map<string, Uint8Array<ArrayBufferLike> | null>;
11
+ /** Where `save` writes, once `loadNavCache` connected a file system. */
12
+ fs: PlatformFileSystem | undefined;
13
+ path: string;
14
+ /** Tiles read from disk by the last load. */
15
+ loaded: number;
16
+ /** The cache as one file: magic, format, count, then per tile an 8-byte key and its data. */
17
+ encode(keys?: Iterable<string>): Uint8Array;
18
+ /** Reads a file written by `encode` into the cache. Returns how many tiles it held. */
19
+ decode(data: Uint8Array): number;
20
+ }
21
+ export declare const NavCache: import("@aethervtt/shard-core").ResourceDef<NavCacheStore>;
22
+ /**
23
+ * Connects the cache to a project's file system and reads the tiles baked there, so navmeshes
24
+ * whose geometry hasn't changed load without running Recast. Call before loading scenes.
25
+ */
26
+ export declare function loadNavCache(world: World, fs: PlatformFileSystem, path?: string): Promise<number>;
27
+ /**
28
+ * Writes the tiles in `keys` (default: every tile in the cache) to the cache file. Returns the
29
+ * bytes written, or 0 when no writable file system is connected.
30
+ */
31
+ export declare function saveNavCache(world: World, keys?: Iterable<string>): Promise<number>;
package/dist/cache.js ADDED
@@ -0,0 +1,90 @@
1
+ import { defineResource, ShardError } from "@aethervtt/shard-core";
2
+ const NAV_CACHE_PATH = ".shard/cache/nav/tiles.bin";
3
+ const MAGIC = 1447120467;
4
+ const FORMAT = 1;
5
+ const EMPTY = 4294967295;
6
+ class NavCacheStore {
7
+ tiles = /* @__PURE__ */ new Map();
8
+ /** Where `save` writes, once `loadNavCache` connected a file system. */
9
+ fs;
10
+ path = NAV_CACHE_PATH;
11
+ /** Tiles read from disk by the last load. */
12
+ loaded = 0;
13
+ /** The cache as one file: magic, format, count, then per tile an 8-byte key and its data. */
14
+ encode(keys = this.tiles.keys()) {
15
+ const entries = [];
16
+ let size = 12;
17
+ for (const key of keys) {
18
+ const bytes = this.tiles.get(key);
19
+ if (bytes === void 0) continue;
20
+ entries.push([key, bytes]);
21
+ size += 12 + (bytes?.length ?? 0);
22
+ }
23
+ const out = new Uint8Array(size);
24
+ const view = new DataView(out.buffer);
25
+ view.setUint32(0, MAGIC, true);
26
+ view.setUint32(4, FORMAT, true);
27
+ view.setUint32(8, entries.length, true);
28
+ let o = 12;
29
+ for (const [key, bytes] of entries) {
30
+ view.setUint32(o, Number.parseInt(key.slice(0, 8), 16), true);
31
+ view.setUint32(o + 4, Number.parseInt(key.slice(8, 16), 16), true);
32
+ view.setUint32(o + 8, bytes ? bytes.length : EMPTY, true);
33
+ o += 12;
34
+ if (bytes) {
35
+ out.set(bytes, o);
36
+ o += bytes.length;
37
+ }
38
+ }
39
+ return out;
40
+ }
41
+ /** Reads a file written by `encode` into the cache. Returns how many tiles it held. */
42
+ decode(data) {
43
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
44
+ if (data.length < 12 || view.getUint32(0, true) !== MAGIC) {
45
+ throw new ShardError("nav/bad-cache", "The navmesh cache file is not a Shard nav cache", {
46
+ hint: `Delete ${this.path}; the next bake rewrites it.`
47
+ });
48
+ }
49
+ if (view.getUint32(4, true) !== FORMAT) return 0;
50
+ const count = view.getUint32(8, true);
51
+ let o = 12;
52
+ for (let i = 0; i < count; i++) {
53
+ const key = view.getUint32(o, true).toString(16).padStart(8, "0") + view.getUint32(o + 4, true).toString(16).padStart(8, "0");
54
+ const len = view.getUint32(o + 8, true);
55
+ o += 12;
56
+ if (len === EMPTY) this.tiles.set(key, null);
57
+ else {
58
+ this.tiles.set(key, data.slice(o, o + len));
59
+ o += len;
60
+ }
61
+ }
62
+ return count;
63
+ }
64
+ }
65
+ const NavCache = defineResource("nav/Cache", {
66
+ description: "Baked navmesh tiles by geometry key, loaded from and saved to .shard/cache/nav.",
67
+ init: () => new NavCacheStore()
68
+ });
69
+ async function loadNavCache(world, fs, path = NAV_CACHE_PATH) {
70
+ const cache = world.initResource(NavCache);
71
+ cache.fs = fs;
72
+ cache.path = path;
73
+ if (!await fs.exists(path)) return 0;
74
+ cache.loaded = cache.decode(await fs.readBytes(path));
75
+ return cache.loaded;
76
+ }
77
+ async function saveNavCache(world, keys) {
78
+ const cache = world.initResource(NavCache);
79
+ if (!cache.fs?.writable) return 0;
80
+ const bytes = cache.encode(keys);
81
+ await cache.fs.writeBytes(cache.path, bytes);
82
+ return bytes.length;
83
+ }
84
+ export {
85
+ NAV_CACHE_PATH,
86
+ NavCache,
87
+ NavCacheStore,
88
+ loadNavCache,
89
+ saveNavCache
90
+ };
@@ -0,0 +1,88 @@
1
+ import { AssetStore } from '@aethervtt/shard-assets';
2
+ import { type Entity } from '@aethervtt/shard-core';
3
+ import { NavGridData } from './grid.js';
4
+ export declare const GRID_SOURCES: readonly ['data', 'tilemap', 'colliders'];
5
+ export type GridSource = (typeof GRID_SOURCES)[number];
6
+ export declare const NavGrid: import("@aethervtt/shard-core").ComponentDef<{
7
+ readonly source: import("@aethervtt/shard-core").FieldType<"colliders" | "data" | "tilemap", "u8">;
8
+ readonly width: import("@aethervtt/shard-core").FieldType<number, "u32">;
9
+ readonly height: import("@aethervtt/shard-core").FieldType<number, "u32">;
10
+ readonly cellSize: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec2, "f32">;
11
+ readonly origin: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec2, "f32">;
12
+ readonly diagonal: import("@aethervtt/shard-core").FieldType<"always" | "never" | "no-corners", "u8">;
13
+ readonly tilemap: import("@aethervtt/shard-core").FieldType<number | null, "f64">;
14
+ readonly layer: import("@aethervtt/shard-core").FieldType<string, "object">;
15
+ readonly blockingTiles: import("@aethervtt/shard-core").FieldType<number[], "object">;
16
+ readonly data: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").AssetRef<"NavGridData"> | null, "object">;
17
+ readonly mask: import("@aethervtt/shard-core").FieldType<number, "u16">;
18
+ }>;
19
+ export declare const NavSource: import("@aethervtt/shard-core").ComponentDef<{
20
+ readonly area: import("@aethervtt/shard-core").FieldType<number, "u8">;
21
+ }>;
22
+ export declare const NavMesh: import("@aethervtt/shard-core").ComponentDef<{
23
+ readonly agentRadius: import("@aethervtt/shard-core").FieldType<number, "f32">;
24
+ readonly agentHeight: import("@aethervtt/shard-core").FieldType<number, "f32">;
25
+ readonly maxClimb: import("@aethervtt/shard-core").FieldType<number, "f32">;
26
+ readonly maxSlope: import("@aethervtt/shard-core").FieldType<number, "f32">;
27
+ readonly cellSize: import("@aethervtt/shard-core").FieldType<number, "f32">;
28
+ readonly cellHeight: import("@aethervtt/shard-core").FieldType<number, "f32">;
29
+ readonly tileSize: import("@aethervtt/shard-core").FieldType<number, "u16">;
30
+ readonly boundsMin: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec3, "f32">;
31
+ readonly boundsMax: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec3, "f32">;
32
+ readonly frame: import("@aethervtt/shard-core").FieldType<number | null, "f64">;
33
+ }>;
34
+ export declare const OffMeshLink: import("@aethervtt/shard-core").ComponentDef<{
35
+ readonly to: import("@aethervtt/shard-core").FieldType<number | null, "f64">;
36
+ readonly bidirectional: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
37
+ readonly radius: import("@aethervtt/shard-core").FieldType<number, "f32">;
38
+ readonly area: import("@aethervtt/shard-core").FieldType<number, "u8">;
39
+ }>;
40
+ export declare const AGENT_STATUSES: readonly ['idle', 'moving', 'arrived', 'unreachable'];
41
+ export type AgentStatus = (typeof AGENT_STATUSES)[number];
42
+ export declare const NavAgentState: import("@aethervtt/shard-core").ComponentDef<{
43
+ readonly status: import("@aethervtt/shard-core").FieldType<"arrived" | "idle" | "moving" | "unreachable", "u8">;
44
+ readonly remaining: import("@aethervtt/shard-core").FieldType<number, "f32">;
45
+ readonly corners: import("@aethervtt/shard-core").FieldType<number, "u16">;
46
+ readonly velocity: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec3, "f32">;
47
+ }>;
48
+ export declare const DRIVES: readonly ['character', 'transform', 'velocity'];
49
+ export type Drive = (typeof DRIVES)[number];
50
+ export declare const NavAgent: import("@aethervtt/shard-core").ComponentDef<{
51
+ readonly destination: import("@aethervtt/shard-core").FieldType<import("@aethervtt/shard-core").Vec3, "f32">;
52
+ readonly target: import("@aethervtt/shard-core").FieldType<number | null, "f64">;
53
+ readonly speed: import("@aethervtt/shard-core").FieldType<number, "f32">;
54
+ readonly acceleration: import("@aethervtt/shard-core").FieldType<number, "f32">;
55
+ readonly radius: import("@aethervtt/shard-core").FieldType<number, "f32">;
56
+ readonly stoppingDistance: import("@aethervtt/shard-core").FieldType<number, "f32">;
57
+ readonly avoidance: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
58
+ readonly repathInterval: import("@aethervtt/shard-core").FieldType<number, "f32">;
59
+ readonly drive: import("@aethervtt/shard-core").FieldType<"character" | "transform" | "velocity", "u8">;
60
+ readonly stopped: import("@aethervtt/shard-core").FieldType<boolean, "u8">;
61
+ readonly nav: import("@aethervtt/shard-core").FieldType<number | null, "f64">;
62
+ }>;
63
+ export interface NavAgentEventData {
64
+ entity: Entity;
65
+ }
66
+ export declare const NavArrived: import("@aethervtt/shard-core").EventDef<NavAgentEventData>;
67
+ export declare const NavUnreachable: import("@aethervtt/shard-core").EventDef<NavAgentEventData>;
68
+ /** Cost multiplier per area code: `{ 0: 1, 1: 3 }` makes area 1 three times as slow to cross. */
69
+ export type NavAreasValue = Record<number, number>;
70
+ export declare const NavAreas: import("@aethervtt/shard-core").ResourceDef<NavAreasValue>;
71
+ export declare const NavGridDataSchema: import("@aethervtt/shard-core").ComponentDef<{
72
+ readonly width: import("@aethervtt/shard-core").FieldType<number, "u32">;
73
+ readonly height: import("@aethervtt/shard-core").FieldType<number, "u32">;
74
+ readonly costs: import("@aethervtt/shard-core").FieldType<string, "object">;
75
+ }>;
76
+ export declare function navGridFromJson(json: unknown): NavGridData;
77
+ export declare function navGridToJson(grid: NavGridData): {
78
+ width: number;
79
+ height: number;
80
+ costs: string;
81
+ };
82
+ export declare class NavGridDataStore extends AssetStore<NavGridData, 'NavGridData'> {
83
+ constructor();
84
+ }
85
+ export declare const NavGridDatas: import("@aethervtt/shard-core").ResourceDef<NavGridDataStore>;
86
+ export declare const NavGridDataAssetType: import("@aethervtt/shard-assets").AssetTypeDef<NavGridData>;
87
+ /** `*.navgrid.json`: walkable cells and costs for a NavGrid. */
88
+ export declare const NavGridDataImporter: import("@aethervtt/shard-assets").ImporterDef;
@@ -0,0 +1,279 @@
1
+ import { AssetStore, defineAssetType, defineDataAsset } from "@aethervtt/shard-assets";
2
+ import {
3
+ defineComponent,
4
+ defineEvent,
5
+ defineResource,
6
+ defineSchema,
7
+ ShardError,
8
+ t
9
+ } from "@aethervtt/shard-core";
10
+ import { Transform } from "@aethervtt/shard-transform";
11
+ import { DIAGONAL_MODES, NavGridData } from "./grid.js";
12
+ const GRID_SOURCES = ["data", "tilemap", "colliders"];
13
+ const NavGrid = defineComponent(
14
+ "nav/NavGrid",
15
+ {
16
+ source: t.enum(GRID_SOURCES, {
17
+ description: "data: costs from a *.navgrid.json (data). tilemap: a tilemap layer, cell per tile (size, cell size, and origin come from the tilemap). colliders: fixed 2D colliders rasterized into width \xD7 height cells."
18
+ }),
19
+ width: t.u32({ default: 32, min: 1, description: "colliders: cells across." }),
20
+ height: t.u32({ default: 32, min: 1, description: "colliders: cells up." }),
21
+ cellSize: t.vec2({
22
+ default: [1, 1],
23
+ min: 1e-3,
24
+ unit: "m",
25
+ description: "data, colliders: world size of one cell."
26
+ }),
27
+ origin: t.vec2({
28
+ unit: "m",
29
+ description: "data, colliders: world XY of cell (0, 0)'s lower-left corner, relative to the entity's position."
30
+ }),
31
+ diagonal: t.enum(DIAGONAL_MODES, {
32
+ description: "no-corners: diagonal steps unless they cut a blocked corner. never: four directions only. always: diagonals even past corners."
33
+ }),
34
+ tilemap: t.entity({
35
+ description: "tilemap: the Tilemap entity (default: this entity)."
36
+ }),
37
+ layer: t.string({ description: "tilemap: the layer name (default: the first layer)." }),
38
+ blockingTiles: t.list(t.u16, {
39
+ description: "tilemap: only these tiles block (empty: every non-empty tile blocks). Tile numbers as in the tilemap data (region + 1)."
40
+ }),
41
+ data: t.handle("NavGridData", { description: "data: the *.navgrid.json asset." }),
42
+ mask: t.u16({
43
+ default: 65535,
44
+ description: "colliders: collider layers (bitmask) that block cells."
45
+ })
46
+ },
47
+ {
48
+ description: "A 2D navigation grid in the XY plane: walkable cells with costs, A* with diagonal rules, paths pulled straight. Queries and NavAgents inside it use it.",
49
+ requires: [Transform]
50
+ }
51
+ );
52
+ const NavSource = defineComponent(
53
+ "nav/NavSource",
54
+ {
55
+ area: t.u8({
56
+ max: 62,
57
+ description: "Area code the triangles bake as (0: ground). Costs per area are in nav/Areas; paths prefer cheap areas."
58
+ })
59
+ },
60
+ {
61
+ description: "This entity's colliders (or, without a Collider, its Mesh3d) and those of descendants without their own NavSource feed the navmesh bake. Moving or changing one rebuilds the tiles it touches."
62
+ }
63
+ );
64
+ const NavMesh = defineComponent(
65
+ "nav/NavMesh",
66
+ {
67
+ agentRadius: t.f32({
68
+ default: 0.4,
69
+ min: 0,
70
+ unit: "m",
71
+ description: "Walls are this far from the walkable area."
72
+ }),
73
+ agentHeight: t.f32({
74
+ default: 1.8,
75
+ min: 0.01,
76
+ unit: "m",
77
+ description: "Minimum clearance under ceilings."
78
+ }),
79
+ maxClimb: t.f32({
80
+ default: 0.3,
81
+ min: 0,
82
+ unit: "m",
83
+ description: "Tallest step or ledge an agent walks up."
84
+ }),
85
+ maxSlope: t.f32({
86
+ default: 45,
87
+ min: 0,
88
+ max: 89.9,
89
+ unit: "deg",
90
+ description: "Steepest walkable slope."
91
+ }),
92
+ cellSize: t.f32({
93
+ default: 0.2,
94
+ min: 0.01,
95
+ unit: "m",
96
+ description: "Voxel size across: smaller follows edges closer and bakes slower."
97
+ }),
98
+ cellHeight: t.f32({ default: 0.1, min: 0.01, unit: "m", description: "Voxel height." }),
99
+ tileSize: t.u16({
100
+ default: 64,
101
+ min: 8,
102
+ max: 1024,
103
+ description: "Cells per tile side: the unit that rebuilds when a source changes."
104
+ }),
105
+ boundsMin: t.vec3({
106
+ unit: "m",
107
+ description: "World-space bake bounds, min corner. Equal to boundsMax (the default): bounds of every source."
108
+ }),
109
+ boundsMax: t.vec3({ unit: "m", description: "World-space bake bounds, max corner." }),
110
+ frame: t.entity({
111
+ description: "Bake and query in this entity's local space instead of the world's: its +Y is up. Positions and paths stay world-space in the API. Lets a navmesh ride a moving or rotating frame (a planet's surface) and keeps its tiles when the floating origin moves. Bounds are in this space."
112
+ })
113
+ },
114
+ {
115
+ description: "A navmesh baked with Recast from every NavSource, with OffMeshLinks. Tiles are cached by the geometry that made them. Queries and NavAgents inside it use it."
116
+ }
117
+ );
118
+ const OffMeshLink = defineComponent(
119
+ "nav/OffMeshLink",
120
+ {
121
+ to: t.entity({ description: "The link ends at this entity\u2019s position." }),
122
+ bidirectional: t.bool({ default: true, description: "Agents cross it both ways." }),
123
+ radius: t.f32({
124
+ default: 0.5,
125
+ min: 0.01,
126
+ unit: "m",
127
+ description: "How close to either end counts as on it."
128
+ }),
129
+ area: t.u8({ max: 62, description: "Area code (costs in nav/Areas)." })
130
+ },
131
+ {
132
+ description: "A jump, drop, ladder, or door between two navmesh points: from this entity\u2019s position to `to`. Agents cross it in a straight line.",
133
+ requires: [Transform]
134
+ }
135
+ );
136
+ const AGENT_STATUSES = ["idle", "moving", "arrived", "unreachable"];
137
+ const NavAgentState = defineComponent(
138
+ "nav/NavAgentState",
139
+ {
140
+ status: t.enum(AGENT_STATUSES, {
141
+ readonly: true,
142
+ description: "idle: stopped or no navigation here. moving. arrived: within stoppingDistance. unreachable: no path; it walks to the closest point it can reach."
143
+ }),
144
+ remaining: t.f32({
145
+ unit: "m",
146
+ readonly: true,
147
+ description: "Distance left along the path."
148
+ }),
149
+ corners: t.u16({ readonly: true, description: "Corners left on the path." }),
150
+ velocity: t.vec3({ unit: "m/s", readonly: true, description: "Steering velocity." })
151
+ },
152
+ { description: "What a NavAgent is doing. Written by navigation; read-only." }
153
+ );
154
+ const DRIVES = ["character", "transform", "velocity"];
155
+ const NavAgent = defineComponent(
156
+ "nav/NavAgent",
157
+ {
158
+ destination: t.vec3({
159
+ unit: "m",
160
+ description: "Where to go (world space). Writing it starts a new path."
161
+ }),
162
+ target: t.entity({
163
+ description: "Follow this entity instead of destination: repaths when it moves more than a cell or every repathInterval."
164
+ }),
165
+ speed: t.f32({ default: 3.5, min: 0, unit: "m/s", description: "Top speed." }),
166
+ acceleration: t.f32({ default: 8, min: 0, unit: "m/s\xB2" }),
167
+ radius: t.f32({
168
+ default: 0.4,
169
+ min: 0,
170
+ unit: "m",
171
+ description: "Personal space: agents steer to keep this far apart."
172
+ }),
173
+ stoppingDistance: t.f32({
174
+ default: 0.2,
175
+ min: 0,
176
+ unit: "m",
177
+ description: "Arrived this close to the destination."
178
+ }),
179
+ avoidance: t.bool({ default: true, description: "Steer around other agents." }),
180
+ repathInterval: t.f32({
181
+ default: 0.5,
182
+ min: 0,
183
+ unit: "s",
184
+ description: "With a moving target: path again at least this often."
185
+ }),
186
+ drive: t.enum(DRIVES, {
187
+ description: "character: writes CharacterIntent.move so physics walks it (slopes, steps, collisions; falls back to transform without a CharacterController). velocity: writes the rigid body\u2019s Velocity. transform: moves the Transform directly."
188
+ }),
189
+ stopped: t.bool({ description: "Hold position (status idle) until cleared." }),
190
+ nav: t.entity({
191
+ description: "The NavGrid or NavMesh to walk on (default: the one the agent stands in)."
192
+ })
193
+ },
194
+ {
195
+ description: "Walks to a destination or follows a target along navigation paths: arrives, avoids other agents, repaths. Status in NavAgentState; NavArrived and NavUnreachable on changes.",
196
+ requires: [Transform, NavAgentState]
197
+ }
198
+ );
199
+ const NavArrived = defineEvent("nav/NavArrived", {
200
+ description: "A NavAgent arrived at its destination or target."
201
+ });
202
+ const NavUnreachable = defineEvent("nav/NavUnreachable", {
203
+ description: "A NavAgent\u2019s destination has no path; it heads for the closest reachable point."
204
+ });
205
+ const NavAreas = defineResource("nav/Areas", {
206
+ description: 'Navmesh area costs by area code (NavSource.area, OffMeshLink.area): { "1": 3 } makes area 1 three times as costly. 0 excludes an area. Unlisted areas cost 1.',
207
+ init: () => ({ 0: 1 })
208
+ });
209
+ const NavGridDataSchema = defineSchema(
210
+ "nav/NavGridData",
211
+ {
212
+ width: t.u32({ min: 1, required: true, description: "Cells across." }),
213
+ height: t.u32({ min: 1, required: true, description: "Cells up." }),
214
+ costs: t.string({
215
+ description: "Base64 of width \xD7 height bytes, row by row from the bottom (y = 0): 0 blocked, 1 ground, more costs more. Empty: all 1."
216
+ })
217
+ },
218
+ { description: "Walkable cells and costs for a NavGrid with source: data." }
219
+ );
220
+ function navGridFromJson(json) {
221
+ const v = NavGridDataSchema.deserialize(json);
222
+ if (!v.costs) return new NavGridData(v.width, v.height);
223
+ const bin = atob(v.costs);
224
+ if (bin.length !== v.width * v.height) {
225
+ throw new ShardError(
226
+ "nav/invalid-grid",
227
+ `A ${v.width}\xD7${v.height} navgrid needs ${v.width * v.height} cost bytes, got ${bin.length}`,
228
+ { path: "/costs" }
229
+ );
230
+ }
231
+ const costs = new Uint8Array(bin.length);
232
+ for (let i = 0; i < bin.length; i++) costs[i] = bin.charCodeAt(i);
233
+ return new NavGridData(v.width, v.height, costs);
234
+ }
235
+ function navGridToJson(grid) {
236
+ let s = "";
237
+ for (let i = 0; i < grid.costs.length; i += 32768) {
238
+ s += String.fromCharCode(...grid.costs.subarray(i, i + 32768));
239
+ }
240
+ return { width: grid.width, height: grid.height, costs: btoa(s) };
241
+ }
242
+ class NavGridDataStore extends AssetStore {
243
+ constructor() {
244
+ super("NavGridData");
245
+ }
246
+ }
247
+ const NavGridDatas = defineResource("nav/NavGridDatas", {
248
+ description: "Loaded navgrid data by guid.",
249
+ init: () => new NavGridDataStore()
250
+ });
251
+ const NavGridDataAssetType = defineAssetType("NavGridData", {
252
+ store: NavGridDatas,
253
+ load: (artifact) => navGridFromJson(artifact.json),
254
+ update: (existing, next) => existing.copyFrom(next)
255
+ });
256
+ const NavGridDataImporter = defineDataAsset("NavGridData", NavGridDataSchema, {
257
+ extension: "navgrid"
258
+ });
259
+ export {
260
+ AGENT_STATUSES,
261
+ DRIVES,
262
+ GRID_SOURCES,
263
+ NavAgent,
264
+ NavAgentState,
265
+ NavAreas,
266
+ NavArrived,
267
+ NavGrid,
268
+ NavGridDataAssetType,
269
+ NavGridDataImporter,
270
+ NavGridDataSchema,
271
+ NavGridDataStore,
272
+ NavGridDatas,
273
+ NavMesh,
274
+ NavSource,
275
+ NavUnreachable,
276
+ OffMeshLink,
277
+ navGridFromJson,
278
+ navGridToJson
279
+ };
@@ -0,0 +1,33 @@
1
+ import type { Mesh } from '@aethervtt/shard-mesh';
2
+ /**
3
+ * World-space triangles for a bake: 9 floats per triangle (three xyz corners) and an area code
4
+ * each. Grows by doubling; `clear` keeps the memory.
5
+ */
6
+ export declare class TriangleSoup {
7
+ positions: Float32Array<ArrayBuffer>;
8
+ areas: Uint8Array<ArrayBuffer>;
9
+ count: number;
10
+ readonly min: Float64Array<ArrayBuffer>;
11
+ readonly max: Float64Array<ArrayBuffer>;
12
+ clear(): void;
13
+ private grow;
14
+ /** Adds a triangle given in world space. */
15
+ push(ax: number, ay: number, az: number, bx: number, by: number, bz: number, cx: number, cy: number, cz: number, area: number): void;
16
+ }
17
+ /**
18
+ * Local-space triangles into a soup through a world matrix (affine 3×4, row by row). Winding is
19
+ * kept, or flipped under a mirroring matrix, so upward faces stay upward.
20
+ */
21
+ export declare function pushTransformed(soup: TriangleSoup, local: ArrayLike<number>, indices: ArrayLike<number> | undefined, triangles: number, m: ArrayLike<number>, area: number): void;
22
+ export declare function boxTriangles(hx: number, hy: number, hz: number): number[];
23
+ /**
24
+ * A solid of revolution around Y from a profile of [radius, y] pairs, bottom to top, with
25
+ * `segments` sides. Rings of radius 0 close the ends.
26
+ */
27
+ export declare function latheTriangles(profile: number[][], segments?: number): number[];
28
+ export declare function sphereProfile(radius: number, center?: number, from?: number, to?: number, rings?: number): number[][];
29
+ export declare function capsuleProfile(radius: number, halfHeight: number): number[][];
30
+ /** Heightfield triangles in local space, as the physics collider lays them out. */
31
+ export declare function heightfieldTriangles(rows: number, cols: number, heights: ArrayLike<number>, hx: number, hy: number, hz: number): number[];
32
+ /** A render mesh's triangles through a world matrix. */
33
+ export declare function pushMesh(soup: TriangleSoup, mesh: Mesh, m: ArrayLike<number>, area: number): void;