@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,176 @@
1
+ class TriangleSoup {
2
+ positions = new Float32Array(9 * 256);
3
+ areas = new Uint8Array(256);
4
+ count = 0;
5
+ min = new Float64Array([Infinity, Infinity, Infinity]);
6
+ max = new Float64Array([-Infinity, -Infinity, -Infinity]);
7
+ clear() {
8
+ this.count = 0;
9
+ this.min.fill(Infinity);
10
+ this.max.fill(-Infinity);
11
+ }
12
+ grow() {
13
+ const p = new Float32Array(this.positions.length * 2);
14
+ p.set(this.positions);
15
+ this.positions = p;
16
+ const a = new Uint8Array(this.areas.length * 2);
17
+ a.set(this.areas);
18
+ this.areas = a;
19
+ }
20
+ /** Adds a triangle given in world space. */
21
+ push(ax, ay, az, bx, by, bz, cx, cy, cz, area) {
22
+ if (this.count === this.areas.length) this.grow();
23
+ const o = this.count * 9;
24
+ const p = this.positions;
25
+ p[o] = ax;
26
+ p[o + 1] = ay;
27
+ p[o + 2] = az;
28
+ p[o + 3] = bx;
29
+ p[o + 4] = by;
30
+ p[o + 5] = bz;
31
+ p[o + 6] = cx;
32
+ p[o + 7] = cy;
33
+ p[o + 8] = cz;
34
+ this.areas[this.count++] = area;
35
+ for (let k = 0; k < 9; k += 3) {
36
+ const x = p[o + k];
37
+ const y = p[o + k + 1];
38
+ const z = p[o + k + 2];
39
+ if (x < this.min[0]) this.min[0] = x;
40
+ if (y < this.min[1]) this.min[1] = y;
41
+ if (z < this.min[2]) this.min[2] = z;
42
+ if (x > this.max[0]) this.max[0] = x;
43
+ if (y > this.max[1]) this.max[1] = y;
44
+ if (z > this.max[2]) this.max[2] = z;
45
+ }
46
+ }
47
+ }
48
+ function pushTransformed(soup, local, indices, triangles, m, area) {
49
+ const det = m[0] * (m[5] * m[10] - m[6] * m[9]) - m[1] * (m[4] * m[10] - m[6] * m[8]) + m[2] * (m[4] * m[9] - m[5] * m[8]);
50
+ const flip = det < 0;
51
+ const tx = (i) => m[0] * local[i] + m[1] * local[i + 1] + m[2] * local[i + 2] + m[3];
52
+ const ty = (i) => m[4] * local[i] + m[5] * local[i + 1] + m[6] * local[i + 2] + m[7];
53
+ const tz = (i) => m[8] * local[i] + m[9] * local[i + 1] + m[10] * local[i + 2] + m[11];
54
+ for (let t = 0; t < triangles; t++) {
55
+ const a = 3 * (indices ? indices[3 * t] : 3 * t);
56
+ let b = 3 * (indices ? indices[3 * t + 1] : 3 * t + 1);
57
+ let c = 3 * (indices ? indices[3 * t + 2] : 3 * t + 2);
58
+ if (flip) {
59
+ const s = b;
60
+ b = c;
61
+ c = s;
62
+ }
63
+ soup.push(tx(a), ty(a), tz(a), tx(b), ty(b), tz(b), tx(c), ty(c), tz(c), area);
64
+ }
65
+ }
66
+ function outward(tris) {
67
+ for (let o = 0; o < tris.length; o += 9) {
68
+ const e0x = tris[o + 3] - tris[o];
69
+ const e0y = tris[o + 4] - tris[o + 1];
70
+ const e0z = tris[o + 5] - tris[o + 2];
71
+ const e1x = tris[o + 6] - tris[o];
72
+ const e1y = tris[o + 7] - tris[o + 1];
73
+ const e1z = tris[o + 8] - tris[o + 2];
74
+ const nx = e0y * e1z - e0z * e1y;
75
+ const ny = e0z * e1x - e0x * e1z;
76
+ const nz = e0x * e1y - e0y * e1x;
77
+ const cx = tris[o] + tris[o + 3] + tris[o + 6];
78
+ const cy = tris[o + 1] + tris[o + 4] + tris[o + 7];
79
+ const cz = tris[o + 2] + tris[o + 5] + tris[o + 8];
80
+ if (nx * cx + ny * cy + nz * cz < 0) {
81
+ for (let k = 0; k < 3; k++) {
82
+ const s = tris[o + 3 + k];
83
+ tris[o + 3 + k] = tris[o + 6 + k];
84
+ tris[o + 6 + k] = s;
85
+ }
86
+ }
87
+ }
88
+ return tris;
89
+ }
90
+ function boxTriangles(hx, hy, hz) {
91
+ const tris = [];
92
+ const quad = (a, b, c, d) => {
93
+ tris.push(...a, ...b, ...c, ...a, ...c, ...d);
94
+ };
95
+ const p = (x, y, z) => [x * hx, y * hy, z * hz];
96
+ quad(p(-1, 1, -1), p(-1, 1, 1), p(1, 1, 1), p(1, 1, -1));
97
+ quad(p(-1, -1, -1), p(1, -1, -1), p(1, -1, 1), p(-1, -1, 1));
98
+ quad(p(-1, -1, 1), p(1, -1, 1), p(1, 1, 1), p(-1, 1, 1));
99
+ quad(p(-1, -1, -1), p(-1, 1, -1), p(1, 1, -1), p(1, -1, -1));
100
+ quad(p(1, -1, -1), p(1, 1, -1), p(1, 1, 1), p(1, -1, 1));
101
+ quad(p(-1, -1, -1), p(-1, -1, 1), p(-1, 1, 1), p(-1, 1, -1));
102
+ return outward(tris);
103
+ }
104
+ function latheTriangles(profile, segments = 12) {
105
+ const tris = [];
106
+ const at = (ring, k) => {
107
+ const [r, y] = profile[ring];
108
+ const a = 2 * Math.PI * k / segments;
109
+ return [r * Math.cos(a), y, r * Math.sin(a)];
110
+ };
111
+ for (let i = 0; i + 1 < profile.length; i++) {
112
+ for (let k = 0; k < segments; k++) {
113
+ const a = at(i, k);
114
+ const b = at(i, k + 1);
115
+ const c = at(i + 1, k + 1);
116
+ const d = at(i + 1, k);
117
+ if (profile[i][0] > 0) tris.push(...a, ...b, ...c);
118
+ if (profile[i + 1][0] > 0) tris.push(...a, ...c, ...d);
119
+ }
120
+ }
121
+ for (const ring of [0, profile.length - 1]) {
122
+ const [r, y] = profile[ring];
123
+ if (r <= 0) continue;
124
+ for (let k = 0; k < segments; k++) tris.push(0, y, 0, ...at(ring, k), ...at(ring, k + 1));
125
+ }
126
+ const mid = (profile[0][1] + profile[profile.length - 1][1]) / 2;
127
+ for (let o = 1; o < tris.length; o += 3) tris[o] = tris[o] - mid;
128
+ outward(tris);
129
+ for (let o = 1; o < tris.length; o += 3) tris[o] = tris[o] + mid;
130
+ return tris;
131
+ }
132
+ function sphereProfile(radius, center = 0, from = -1, to = 1, rings = 6) {
133
+ const out = [];
134
+ for (let i = 0; i <= rings; i++) {
135
+ const a = Math.PI / 2 * (from + (to - from) * i / rings);
136
+ out.push([radius * Math.cos(a), center + radius * Math.sin(a)]);
137
+ }
138
+ return out;
139
+ }
140
+ function capsuleProfile(radius, halfHeight) {
141
+ return [
142
+ ...sphereProfile(radius, -halfHeight, -1, 0, 3),
143
+ ...sphereProfile(radius, halfHeight, 0, 1, 3)
144
+ ];
145
+ }
146
+ function heightfieldTriangles(rows, cols, heights, hx, hy, hz) {
147
+ const tris = [];
148
+ if (rows < 2 || cols < 2 || heights.length < rows * cols) return tris;
149
+ const p = (r, c) => [
150
+ -hx + 2 * hx * c / (cols - 1),
151
+ heights[r * cols + c] * hy,
152
+ -hz + 2 * hz * r / (rows - 1)
153
+ ];
154
+ for (let r = 0; r + 1 < rows; r++) {
155
+ for (let c = 0; c + 1 < cols; c++) {
156
+ tris.push(...p(r, c), ...p(r + 1, c), ...p(r + 1, c + 1));
157
+ tris.push(...p(r, c), ...p(r + 1, c + 1), ...p(r, c + 1));
158
+ }
159
+ }
160
+ return tris;
161
+ }
162
+ function pushMesh(soup, mesh, m, area) {
163
+ const indices = mesh.indices;
164
+ const triangles = indices ? Math.floor(indices.length / 3) : Math.floor(mesh.vertexCount / 3);
165
+ pushTransformed(soup, mesh.positions, indices, triangles, m, area);
166
+ }
167
+ export {
168
+ TriangleSoup,
169
+ boxTriangles,
170
+ capsuleProfile,
171
+ heightfieldTriangles,
172
+ latheTriangles,
173
+ pushMesh,
174
+ pushTransformed,
175
+ sphereProfile
176
+ };
package/dist/grid.d.ts ADDED
@@ -0,0 +1,96 @@
1
+ export declare const DIAGONAL_MODES: readonly ['no-corners', 'never', 'always'];
2
+ export type DiagonalMode = (typeof DIAGONAL_MODES)[number];
3
+ /** Diagonal rule as a number, for hot code: 0 no-corners, 1 never, 2 always. */
4
+ export declare const DIAGONAL_NO_CORNERS = 0;
5
+ export declare const DIAGONAL_NEVER = 1;
6
+ export declare const DIAGONAL_ALWAYS = 2;
7
+ /**
8
+ * Walkable cells and their costs, row by row from y = 0 (the bottom row, lowest world y).
9
+ * 0 is blocked; 1 is normal ground; higher values cost that many times more to cross.
10
+ */
11
+ export declare class NavGridData {
12
+ width: number;
13
+ height: number;
14
+ costs: Uint8Array;
15
+ /**
16
+ * Bumps on every edit, so grids and agents built from it know to refresh. `set` bumps it; code
17
+ * that writes `costs` directly must bump it too.
18
+ */
19
+ version: number;
20
+ private masks;
21
+ private maskVersions;
22
+ constructor(width: number, height: number, costs?: Uint8Array);
23
+ get(x: number, y: number): number;
24
+ /** Sets a cell's cost (0 blocks it). Out-of-range cells are `nav/out-of-bounds`. */
25
+ set(x: number, y: number, cost: number): void;
26
+ /**
27
+ * Per cell, a bit per direction (see `DX`, `DY`) that A* may step in under the diagonal rule:
28
+ * in bounds, walkable, and not cutting a blocked corner when that's not allowed. Cached until
29
+ * `version` changes, so searches skip walls without testing them.
30
+ */
31
+ neighbors(diagonal: number): Uint8Array;
32
+ copyFrom(other: NavGridData): void;
33
+ }
34
+ /**
35
+ * Reusable A* state for grids up to a size: a binary heap over TypedArrays and a generation
36
+ * counter per search, so a search neither allocates nor clears arrays once it has grown.
37
+ */
38
+ export declare class GridSearch {
39
+ private capacity;
40
+ private g;
41
+ private parent;
42
+ /** `gen`: seen this search (open). `gen + 1`: closed. Anything else: untouched. */
43
+ private stamp;
44
+ private heap;
45
+ private heapF;
46
+ private heapG;
47
+ private heapPos;
48
+ private stack;
49
+ private gen;
50
+ /** The last path's cells (y · width + x), start first. */
51
+ cells: Int32Array<ArrayBuffer>;
52
+ /** How many cells `cells` holds. */
53
+ count: number;
54
+ /** Path cost of the last search (world distance × cell cost). */
55
+ cost: number;
56
+ /** Whether the last search reached the goal; false means `cells` ends at the closest cell. */
57
+ reached: boolean;
58
+ /** Cells expanded by the last search. */
59
+ expanded: number;
60
+ private ensure;
61
+ /**
62
+ * Finds the cheapest path from cell (sx, sy) to (gx, gy). Moving costs the world distance
63
+ * (cell size `csx` × `csy`) times the entered cell's cost. Returns true when the goal was
64
+ * reached; otherwise `cells` leads to the reachable cell closest to the goal. Both cells must
65
+ * be walkable (see `nearestWalkable`).
66
+ */
67
+ search(grid: NavGridData, sx: number, sy: number, gx: number, gy: number, diagonal: number, csx?: number, csy?: number): boolean;
68
+ }
69
+ /** Scratch for `gridRaycast`: where the ray stopped, in cell units. */
70
+ export interface GridHit {
71
+ /** 0..1 along the segment; 1 means it reached the end. */
72
+ t: number;
73
+ x: number;
74
+ y: number;
75
+ }
76
+ /**
77
+ * Walks the segment (ax, ay) → (bx, by), in cell units, through the cells it touches
78
+ * (Amanatides–Woo). Stops at the first blocked cell, a cell costing more than `maxCost`, or a
79
+ * corner squeezed between two blocked cells (unless `always`). Returns true when the whole
80
+ * segment is clear; writes the stop point and fraction reached into `hit` when given.
81
+ */
82
+ export declare function gridTrace(grid: NavGridData, ax: number, ay: number, bx: number, by: number, diagonal: number, maxCost: number, hit?: GridHit): boolean;
83
+ /** `gridTrace` with the segment in `seg` (ax, ay, bx, by), for per-frame callers. */
84
+ export declare function traceSegment(grid: NavGridData, seg: Float64Array, diagonal: number, maxCost: number, hit?: GridHit): boolean;
85
+ /**
86
+ * String-pulls a cell path into corners: from the start point, the path runs straight to the
87
+ * farthest cell center it can see, and on from there. Points are in cell units (cell (x, y) spans
88
+ * [x, x + 1] × [y, y + 1]); `ends` holds the start and end points (sx, sy, ex, ey). Writes
89
+ * `stride`-spaced x, y pairs into `out` and returns the corner count (start and end included).
90
+ */
91
+ export declare function smoothGridPath(grid: NavGridData, cells: Int32Array, count: number, ends: Float64Array, diagonal: number, out: Float32Array, stride: number, maxCorners: number): number;
92
+ /**
93
+ * The walkable cell nearest to (x, y) within `radius` cells, as y · width + x, or -1. Searches
94
+ * rings outward and picks the closest center in the first ring that has one.
95
+ */
96
+ export declare function nearestWalkable(grid: NavGridData, x: number, y: number, radius: number): number;