@spatial-engine/core 0.0.1 → 0.0.3

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/README.md ADDED
@@ -0,0 +1,72 @@
1
+ # @spatial-engine/core
2
+
3
+ The foundation layer for `spatial-engine`, a high-performance spatial partitioning and query library using a Data-Oriented Design (DOD) approach. It provides cache-friendly, zero-GC spatial data structures backed by flat `Float32Array` pools.
4
+
5
+ Everything lives in contiguous memory: no per-object heap allocations, no mid-frame GC pauses.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @spatial-engine/core
11
+ # or
12
+ pnpm add @spatial-engine/core
13
+ ```
14
+
15
+ ## Features
16
+ - **Octree with automatic subdivision** — insert, update, and remove axis-aligned bounding boxes.
17
+ - **Branchless slab-method raycast** — fast `rayIntersectsAABB` implementation.
18
+ - **Box query** — `octree.queryBox()` returns all objects whose AABB overlaps a query region natively (iterative DFS, no recursion).
19
+ - **Zero GC Pools** — `AABBPool`, `RayPool`, and `OctreeNodePool` allocate once and reuse. Call `.reset()` each frame.
20
+ - **LiDAR Web Worker** — run a full multi-ray sweep entirely off the main thread using shared memory.
21
+ - **`SharedArrayBuffer` support** — pools can be shared with Web Workers without copying data.
22
+
23
+ ## Basic Usage
24
+
25
+ ```ts
26
+ import { AABBPool, RayPool, OctreeNodePool, Octree } from '@spatial-engine/core';
27
+
28
+ // --- 1. Allocate pools once (e.g. on startup) ---
29
+ const nodePool = new OctreeNodePool(512);
30
+ const aabbPool = new AABBPool(512);
31
+ const rayPool = new RayPool(64);
32
+ const octree = new Octree(nodePool, aabbPool);
33
+
34
+ octree.setBounds(-100, -100, -100, 100, 100, 100);
35
+
36
+ // --- 2. Each frame: reset pools and re-insert objects ---
37
+ nodePool.reset();
38
+ aabbPool.reset();
39
+
40
+ const idx = aabbPool.allocate();
41
+ aabbPool.set(idx, -1, -1, -1, 1, 1, 1); // minX,minY,minZ,maxX,maxY,maxZ
42
+ octree.insert(idx);
43
+
44
+ // --- 3. Raycast ---
45
+ const rayIdx = rayPool.allocate();
46
+ rayPool.set(rayIdx, 0, 0, -10, 0, 0, 1); // origin (0,0,-10), direction +Z
47
+
48
+ const rayBuf = (rayPool as any).buffer as Float32Array;
49
+ const hit = octree.raycast(rayBuf, rayIdx * 6);
50
+ // hit: { objectIndex: 0, t: 9 } | null
51
+
52
+ // --- 4. Box query ---
53
+ const hits = octree.queryBox(-2, -2, -2, 2, 2, 2);
54
+ // hits: [0] (array of aabbPool indices)
55
+ ```
56
+
57
+ ## LiDAR Web Worker
58
+ For scenes with many rays (e.g. simulated sensors), you can use the built-in worker implementation to run sweeps off the main thread using `SharedArrayBuffer` exports natively provided.
59
+
60
+ ```ts
61
+ import { AABBPool, RayPool, OctreeNodePool } from '@spatial-engine/core';
62
+ import type { LidarInitMessage, LidarSweepMessage } from '@spatial-engine/core';
63
+ // Import the robust built in web worker
64
+ const worker = new Worker(new URL('@spatial-engine/core/lidar-worker', import.meta.url), { type: 'module' });
65
+ ```
66
+ (See root monorepo README for full worker example code).
67
+
68
+ ## Use Cases
69
+ - **Game collision broad-phase**: `queryBox` for candidate pairs
70
+ - **Frustum culling**: `raycast` or `queryBox` to cull objects
71
+ - **LiDAR sensor simulation**: `createLidarProcessor` off thread
72
+ - **Physics broad-phase**: Find overlap candidates before narrow-phase
@@ -4,7 +4,7 @@ var NODE_FIRST_CHILD_OFFSET = 6;
4
4
  var NODE_PARENT_OFFSET = 7;
5
5
  var NODE_OBJECT_COUNT_OFFSET = 8;
6
6
  var NODE_OBJECTS_OFFSET = 9;
7
- var MAX_OBJECTS_PER_NODE = 8;
7
+ var MAX_OBJECTS_PER_NODE = 64;
8
8
  var NODE_STRIDE = NODE_OBJECTS_OFFSET + MAX_OBJECTS_PER_NODE;
9
9
  var OctreeNodePool = class _OctreeNodePool {
10
10
  buffer;
@@ -317,6 +317,16 @@ var Octree = class {
317
317
  if (ancestorNode === -1) ancestorNode = this.root;
318
318
  this.insertIntoNode(ancestorNode, objectIndex);
319
319
  }
320
+ /**
321
+ * Remove the object at `objectIndex` from the tree.
322
+ * Safe to call even if the object was never inserted (no-op).
323
+ */
324
+ remove(objectIndex) {
325
+ const nodeIdx = this.objectNodeMap.get(objectIndex);
326
+ if (nodeIdx === void 0) return;
327
+ this.nodePool.removeObject(nodeIdx, objectIndex);
328
+ this.objectNodeMap.delete(objectIndex);
329
+ }
320
330
  /**
321
331
  * Cast a ray through the octree and return the closest intersecting object.
322
332
  *
@@ -430,8 +440,15 @@ var Octree = class {
430
440
  np.addObject(nodeIdx, objectIndex);
431
441
  this.objectNodeMap.set(objectIndex, nodeIdx);
432
442
  } else {
433
- this.subdivide(nodeIdx);
434
- this.insertIntoNode(nodeIdx, objectIndex);
443
+ try {
444
+ this.subdivide(nodeIdx);
445
+ this.insertIntoNode(nodeIdx, objectIndex);
446
+ } catch (e) {
447
+ if (e instanceof RangeError) {
448
+ } else {
449
+ throw e;
450
+ }
451
+ }
435
452
  }
436
453
  }
437
454
  /** Split a leaf node into 8 children and redistribute its objects. */
@@ -583,4 +600,4 @@ export {
583
600
  Octree,
584
601
  createLidarProcessor
585
602
  };
586
- //# sourceMappingURL=chunk-JF4RVZK3.js.map
603
+ //# sourceMappingURL=chunk-6B5AUBFL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/octree-node.ts","../src/aabb.ts","../src/ray.ts","../src/octree.ts","../src/lidar-worker.ts"],"sourcesContent":["/**\n * Octree node stored as a flat Float32Array slice.\n *\n * Layout per node (NODE_STRIDE floats):\n * [0..5] – AABB (minX, minY, minZ, maxX, maxY, maxZ)\n * [6] – firstChild index (-1 = leaf / no children)\n * [7] – parent index (-1 = root)\n * [8] – object count\n * [9..] – object indices (MAX_OBJECTS_PER_NODE slots)\n */\n\nexport const NODE_AABB_OFFSET = 0;\nexport const NODE_FIRST_CHILD_OFFSET = 6;\nexport const NODE_PARENT_OFFSET = 7;\nexport const NODE_OBJECT_COUNT_OFFSET = 8;\nexport const NODE_OBJECTS_OFFSET = 9;\nexport const MAX_OBJECTS_PER_NODE = 64;\nexport const NODE_STRIDE = NODE_OBJECTS_OFFSET + MAX_OBJECTS_PER_NODE;\n\n/**\n * A pool-backed, zero-GC Octree node store.\n * Each node occupies NODE_STRIDE floats in the underlying Float32Array buffer.\n *\n * When a `SharedArrayBuffer` is provided, the pool's data is accessible from\n * multiple threads (e.g. a Web Worker) with no copying.\n */\nexport class OctreeNodePool {\n private readonly buffer: Float32Array;\n private count: number = 0;\n\n constructor(capacity: number, sharedBuffer?: SharedArrayBuffer) {\n this.buffer = sharedBuffer\n ? new Float32Array(sharedBuffer, 0, capacity * NODE_STRIDE)\n : new Float32Array(capacity * NODE_STRIDE);\n }\n\n /**\n * Create an OctreeNodePool backed by a new `SharedArrayBuffer`.\n * Both the pool and the underlying `SharedArrayBuffer` are returned so the\n * caller can transfer the buffer to a `Worker` via `postMessage`.\n */\n static createShared(capacity: number): { pool: OctreeNodePool; sab: SharedArrayBuffer } {\n const sab = new SharedArrayBuffer(capacity * NODE_STRIDE * Float32Array.BYTES_PER_ELEMENT);\n return { pool: new OctreeNodePool(capacity, sab), sab };\n }\n\n /** Allocate a new node slot, initialise sentinel values, and return its index. */\n allocate(): number {\n const index = this.count;\n const offset = index * NODE_STRIDE;\n // Initialise sentinel values for child/parent links and object count.\n this.buffer[offset + NODE_FIRST_CHILD_OFFSET] = -1;\n this.buffer[offset + NODE_PARENT_OFFSET] = -1;\n this.buffer[offset + NODE_OBJECT_COUNT_OFFSET] = 0;\n this.count += 1;\n return index;\n }\n\n /** Returns the number of allocated nodes. */\n get size(): number {\n return this.count;\n }\n\n /** Reset the pool (all allocations freed, no GC). */\n reset(): void {\n this.count = 0;\n }\n\n // ── AABB ──────────────────────────────────────────────────────────────────\n\n /** Set the AABB bounds for the node at the given index. */\n setAABB(\n index: number,\n minX: number,\n minY: number,\n minZ: number,\n maxX: number,\n maxY: number,\n maxZ: number,\n ): void {\n const off = index * NODE_STRIDE + NODE_AABB_OFFSET;\n this.buffer[off] = minX;\n this.buffer[off + 1] = minY;\n this.buffer[off + 2] = minZ;\n this.buffer[off + 3] = maxX;\n this.buffer[off + 4] = maxY;\n this.buffer[off + 5] = maxZ;\n }\n\n /** Read a single AABB component (0–5) from the node at the given index. */\n getAABB(index: number, component: number): number {\n return this.buffer[index * NODE_STRIDE + NODE_AABB_OFFSET + component] ?? 0;\n }\n\n // ── First-child link ──────────────────────────────────────────────────────\n\n /** Set the first-child index for the node (-1 = leaf). */\n setFirstChild(index: number, childIndex: number): void {\n this.buffer[index * NODE_STRIDE + NODE_FIRST_CHILD_OFFSET] = childIndex;\n }\n\n /** Get the first-child index for the node (-1 = leaf). */\n getFirstChild(index: number): number {\n return this.buffer[index * NODE_STRIDE + NODE_FIRST_CHILD_OFFSET] ?? -1;\n }\n\n // ── Parent link ───────────────────────────────────────────────────────────\n\n /** Set the parent index for the node (-1 = root). */\n setParent(index: number, parentIndex: number): void {\n this.buffer[index * NODE_STRIDE + NODE_PARENT_OFFSET] = parentIndex;\n }\n\n /** Get the parent index for the node (-1 = root). */\n getParent(index: number): number {\n return this.buffer[index * NODE_STRIDE + NODE_PARENT_OFFSET] ?? -1;\n }\n\n // ── Object pointers ───────────────────────────────────────────────────────\n\n /** Return the number of objects stored in the node at the given index. */\n getObjectCount(index: number): number {\n return this.buffer[index * NODE_STRIDE + NODE_OBJECT_COUNT_OFFSET] ?? 0;\n }\n\n /**\n * Append an object index to the node's object list.\n * Throws a RangeError when MAX_OBJECTS_PER_NODE is exceeded.\n */\n addObject(index: number, objectIndex: number): void {\n const countOff = index * NODE_STRIDE + NODE_OBJECT_COUNT_OFFSET;\n const count = this.buffer[countOff] ?? 0;\n if (count >= MAX_OBJECTS_PER_NODE) {\n throw new RangeError(\n `OctreeNodePool.addObject: node ${index} already contains MAX_OBJECTS_PER_NODE (${MAX_OBJECTS_PER_NODE}) objects`,\n );\n }\n this.buffer[index * NODE_STRIDE + NODE_OBJECTS_OFFSET + count] = objectIndex;\n this.buffer[countOff] = count + 1;\n }\n\n /** Return the object index stored at slot `slot` in the given node. */\n getObject(index: number, slot: number): number {\n return this.buffer[index * NODE_STRIDE + NODE_OBJECTS_OFFSET + slot] ?? 0;\n }\n\n /** Reset the object count of the given node to zero (does not zero the slots). */\n clearObjects(index: number): void {\n this.buffer[index * NODE_STRIDE + NODE_OBJECT_COUNT_OFFSET] = 0;\n }\n\n /**\n * Remove a single object index from the node's object list.\n * Uses swap-with-last to avoid shifting. Returns true if found and removed.\n */\n removeObject(index: number, objectIndex: number): boolean {\n const countOff = index * NODE_STRIDE + NODE_OBJECT_COUNT_OFFSET;\n const count = this.buffer[countOff] ?? 0;\n const baseOff = index * NODE_STRIDE + NODE_OBJECTS_OFFSET;\n for (let i = 0; i < count; i++) {\n if (this.buffer[baseOff + i] === objectIndex) {\n // Swap with the last element, then decrement count.\n this.buffer[baseOff + i] = this.buffer[baseOff + count - 1] ?? 0;\n this.buffer[countOff] = count - 1;\n return true;\n }\n }\n return false;\n }\n}\n","/**\n * AABB (Axis-Aligned Bounding Box) stored as a flat Float32Array slice.\n * Layout: [minX, minY, minZ, maxX, maxY, maxZ]\n */\nexport const AABB_STRIDE = 6;\n\n/**\n * A pool-backed, zero-GC AABB store.\n * Each AABB occupies AABB_STRIDE floats in the underlying buffer.\n *\n * When a `SharedArrayBuffer` is provided, the pool's data is accessible from\n * multiple threads (e.g. a Web Worker) with no copying.\n */\nexport class AABBPool {\n private readonly buffer: Float32Array;\n private count: number = 0;\n\n constructor(capacity: number, sharedBuffer?: SharedArrayBuffer) {\n this.buffer = sharedBuffer\n ? new Float32Array(sharedBuffer, 0, capacity * AABB_STRIDE)\n : new Float32Array(capacity * AABB_STRIDE);\n }\n\n /**\n * Create an AABBPool backed by a new `SharedArrayBuffer`.\n * Both the pool and the underlying `SharedArrayBuffer` are returned so the\n * caller can transfer the buffer to a `Worker` via `postMessage`.\n */\n static createShared(capacity: number): { pool: AABBPool; sab: SharedArrayBuffer } {\n const sab = new SharedArrayBuffer(capacity * AABB_STRIDE * Float32Array.BYTES_PER_ELEMENT);\n return { pool: new AABBPool(capacity, sab), sab };\n }\n\n /** Allocate a new AABB slot and return its index. */\n allocate(): number {\n const index = this.count;\n this.count += 1;\n return index;\n }\n\n /** Set the bounds of an AABB at the given index. */\n set(\n index: number,\n minX: number,\n minY: number,\n minZ: number,\n maxX: number,\n maxY: number,\n maxZ: number,\n ): void {\n const offset = index * AABB_STRIDE;\n this.buffer[offset] = minX;\n this.buffer[offset + 1] = minY;\n this.buffer[offset + 2] = minZ;\n this.buffer[offset + 3] = maxX;\n this.buffer[offset + 4] = maxY;\n this.buffer[offset + 5] = maxZ;\n }\n\n /** Read a single component value from the buffer. */\n get(index: number, component: number): number {\n return this.buffer[index * AABB_STRIDE + component] ?? 0;\n }\n\n /** Returns the number of allocated AABBs. */\n get size(): number {\n return this.count;\n }\n\n /** Reset the pool (all allocations freed, no GC). */\n reset(): void {\n this.count = 0;\n }\n}\n\n/**\n * Expand AABB `a` in-place so it also fully contains AABB `b`.\n */\nexport function aabbExpand(pool: AABBPool, a: number, b: number): void {\n const buf = (pool as unknown as { buffer: Float32Array }).buffer;\n const aOff = a * AABB_STRIDE;\n const bOff = b * AABB_STRIDE;\n buf[aOff] = Math.min(buf[aOff] ?? 0, buf[bOff] ?? 0);\n buf[aOff + 1] = Math.min(buf[aOff + 1] ?? 0, buf[bOff + 1] ?? 0);\n buf[aOff + 2] = Math.min(buf[aOff + 2] ?? 0, buf[bOff + 2] ?? 0);\n buf[aOff + 3] = Math.max(buf[aOff + 3] ?? 0, buf[bOff + 3] ?? 0);\n buf[aOff + 4] = Math.max(buf[aOff + 4] ?? 0, buf[bOff + 4] ?? 0);\n buf[aOff + 5] = Math.max(buf[aOff + 5] ?? 0, buf[bOff + 5] ?? 0);\n}\n\n/**\n * Merge AABBs `a` and `b` into `dest` (a pre-allocated slot), storing their union.\n */\nexport function aabbMerge(pool: AABBPool, dest: number, a: number, b: number): void {\n const buf = (pool as unknown as { buffer: Float32Array }).buffer;\n const aOff = a * AABB_STRIDE;\n const bOff = b * AABB_STRIDE;\n const dOff = dest * AABB_STRIDE;\n buf[dOff] = Math.min(buf[aOff] ?? 0, buf[bOff] ?? 0);\n buf[dOff + 1] = Math.min(buf[aOff + 1] ?? 0, buf[bOff + 1] ?? 0);\n buf[dOff + 2] = Math.min(buf[aOff + 2] ?? 0, buf[bOff + 2] ?? 0);\n buf[dOff + 3] = Math.max(buf[aOff + 3] ?? 0, buf[bOff + 3] ?? 0);\n buf[dOff + 4] = Math.max(buf[aOff + 4] ?? 0, buf[bOff + 4] ?? 0);\n buf[dOff + 5] = Math.max(buf[aOff + 5] ?? 0, buf[bOff + 5] ?? 0);\n}\n\n/**\n * Test whether two AABBs (by index in the same pool) intersect.\n */\nexport function aabbIntersects(pool: AABBPool, a: number, b: number): boolean {\n const aOff = a * AABB_STRIDE;\n const bOff = b * AABB_STRIDE;\n // Access via get() to stay safe with bounds, but direct buffer access is\n // equally valid when performance is critical.\n const buf = (pool as unknown as { buffer: Float32Array }).buffer;\n return (\n (buf[aOff] ?? 0) <= (buf[bOff + 3] ?? 0) &&\n (buf[aOff + 3] ?? 0) >= (buf[bOff] ?? 0) &&\n (buf[aOff + 1] ?? 0) <= (buf[bOff + 4] ?? 0) &&\n (buf[aOff + 4] ?? 0) >= (buf[bOff + 1] ?? 0) &&\n (buf[aOff + 2] ?? 0) <= (buf[bOff + 5] ?? 0) &&\n (buf[aOff + 5] ?? 0) >= (buf[bOff + 2] ?? 0)\n );\n}\n","/**\n * Ray stored as origin + direction (unit vector).\n * Layout: [ox, oy, oz, dx, dy, dz]\n */\nexport const RAY_STRIDE = 6;\n\n/** A pool-backed, zero-GC Ray store.\n *\n * When a `SharedArrayBuffer` is provided, the pool's data is accessible from\n * multiple threads (e.g. a Web Worker) with no copying.\n */\nexport class RayPool {\n private readonly buffer: Float32Array;\n private count: number = 0;\n\n constructor(capacity: number, sharedBuffer?: SharedArrayBuffer) {\n this.buffer = sharedBuffer\n ? new Float32Array(sharedBuffer, 0, capacity * RAY_STRIDE)\n : new Float32Array(capacity * RAY_STRIDE);\n }\n\n /**\n * Create a RayPool backed by a new `SharedArrayBuffer`.\n * Both the pool and the underlying `SharedArrayBuffer` are returned so the\n * caller can transfer the buffer to a `Worker` via `postMessage`.\n */\n static createShared(capacity: number): { pool: RayPool; sab: SharedArrayBuffer } {\n const sab = new SharedArrayBuffer(capacity * RAY_STRIDE * Float32Array.BYTES_PER_ELEMENT);\n return { pool: new RayPool(capacity, sab), sab };\n }\n\n /** Allocate a new Ray slot and return its index. */\n allocate(): number {\n const index = this.count;\n this.count += 1;\n return index;\n }\n\n /** Set origin and direction of a ray at the given index. */\n set(\n index: number,\n ox: number,\n oy: number,\n oz: number,\n dx: number,\n dy: number,\n dz: number,\n ): void {\n const offset = index * RAY_STRIDE;\n this.buffer[offset] = ox;\n this.buffer[offset + 1] = oy;\n this.buffer[offset + 2] = oz;\n this.buffer[offset + 3] = dx;\n this.buffer[offset + 4] = dy;\n this.buffer[offset + 5] = dz;\n }\n\n /** Read a single component value from the buffer. */\n get(index: number, component: number): number {\n return this.buffer[index * RAY_STRIDE + component] ?? 0;\n }\n\n /** Returns the number of allocated Rays. */\n get size(): number {\n return this.count;\n }\n\n /** Reset the pool (all allocations freed, no GC). */\n reset(): void {\n this.count = 0;\n }\n}\n\n/**\n * Optimized branchless slab-method ray–AABB intersection test.\n *\n * Operates purely on flat Float32Arrays with no per-axis direction branches.\n * When a direction component is zero, IEEE 754 produces ±Infinity for the\n * reciprocal, which propagates correctly through the min/max slab computation:\n * - origin inside the slab → [−∞, +∞] interval (no constraint)\n * - origin outside the slab → either [+∞, +∞] or [−∞, −∞] → forces a miss\n *\n * Returns the parametric hit distance `t` (>= 0) if the ray intersects the\n * AABB, or -1 if there is no intersection (miss or box entirely behind origin).\n */\nexport function rayIntersectsAABB(\n rayBuf: Float32Array,\n rayOffset: number,\n aabbBuf: Float32Array,\n aabbOffset: number,\n): number {\n const ox = rayBuf[rayOffset] ?? 0;\n const oy = rayBuf[rayOffset + 1] ?? 0;\n const oz = rayBuf[rayOffset + 2] ?? 0;\n const idx = 1 / (rayBuf[rayOffset + 3] ?? 0);\n const idy = 1 / (rayBuf[rayOffset + 4] ?? 0);\n const idz = 1 / (rayBuf[rayOffset + 5] ?? 0);\n\n const minX = aabbBuf[aabbOffset] ?? 0;\n const minY = aabbBuf[aabbOffset + 1] ?? 0;\n const minZ = aabbBuf[aabbOffset + 2] ?? 0;\n const maxX = aabbBuf[aabbOffset + 3] ?? 0;\n const maxY = aabbBuf[aabbOffset + 4] ?? 0;\n const maxZ = aabbBuf[aabbOffset + 5] ?? 0;\n\n // Branchless slab method: per-axis near/far t values.\n const t1x = (minX - ox) * idx;\n const t2x = (maxX - ox) * idx;\n const t1y = (minY - oy) * idy;\n const t2y = (maxY - oy) * idy;\n const t1z = (minZ - oz) * idz;\n const t2z = (maxZ - oz) * idz;\n\n const tmin = Math.max(Math.min(t1x, t2x), Math.min(t1y, t2y), Math.min(t1z, t2z));\n const tmax = Math.min(Math.max(t1x, t2x), Math.max(t1y, t2y), Math.max(t1z, t2z));\n\n if (tmax < 0 || !(tmin <= tmax)) return -1;\n return tmin >= 0 ? tmin : tmax;\n}\n","import { OctreeNodePool, MAX_OBJECTS_PER_NODE, NODE_STRIDE, NODE_AABB_OFFSET } from './octree-node.js';\nimport { AABBPool, AABB_STRIDE } from './aabb.js';\nimport { rayIntersectsAABB } from './ray.js';\n\n/**\n * Returns true when the AABB stored at `bufOffset` in `buf` overlaps the\n * axis-aligned box defined by the six query scalars.\n */\nfunction aabbOverlapsBox(\n buf: Float32Array,\n bufOffset: number,\n qMinX: number,\n qMinY: number,\n qMinZ: number,\n qMaxX: number,\n qMaxY: number,\n qMaxZ: number,\n): boolean {\n return (\n buf[bufOffset]! <= qMaxX &&\n buf[bufOffset + 3]! >= qMinX &&\n buf[bufOffset + 1]! <= qMaxY &&\n buf[bufOffset + 4]! >= qMinY &&\n buf[bufOffset + 2]! <= qMaxZ &&\n buf[bufOffset + 5]! >= qMinZ\n );\n}\n\n/**\n * Octree with insertion and automatic subdivision.\n *\n * - When a leaf node's object count reaches MAX_OBJECTS_PER_NODE it is\n * subdivided into 8 axis-aligned child octants.\n * - An inserted AABB that fits entirely within a child octant is pushed down.\n * - An AABB that straddles a boundary is kept in the nearest ancestor that\n * fully contains it.\n */\nexport class Octree {\n private readonly nodePool: OctreeNodePool;\n private readonly aabbPool: AABBPool;\n private readonly root: number;\n /** Tracks which node each object (by AABBPool index) is currently stored in. */\n private readonly objectNodeMap: Map<number, number> = new Map();\n /** Pre-allocated traversal stack reused across raycast calls to avoid GC pressure. */\n private readonly _stack: number[] = [];\n\n constructor(nodePool: OctreeNodePool, aabbPool: AABBPool) {\n this.nodePool = nodePool;\n this.aabbPool = aabbPool;\n this.root = nodePool.allocate();\n }\n\n /** The index of the root node in the underlying OctreeNodePool. */\n get rootIndex(): number {\n return this.root;\n }\n\n /** Set the world-space AABB that the root node covers. */\n setBounds(\n minX: number,\n minY: number,\n minZ: number,\n maxX: number,\n maxY: number,\n maxZ: number,\n ): void {\n this.nodePool.setAABB(this.root, minX, minY, minZ, maxX, maxY, maxZ);\n }\n\n /** Insert the AABB at `objectIndex` (in the AABBPool) into the tree. */\n insert(objectIndex: number): void {\n this.insertIntoNode(this.root, objectIndex);\n }\n\n /**\n * Update the AABB at `objectIndex` with new bounds and reposition it in the\n * tree without rebuilding. If the new bounds still fit in the current node\n * the object stays there; otherwise it is removed and re-inserted from the\n * lowest ancestor whose bounds fully contain the new AABB.\n */\n update(\n objectIndex: number,\n newMinX: number,\n newMinY: number,\n newMinZ: number,\n newMaxX: number,\n newMaxY: number,\n newMaxZ: number,\n ): void {\n const np = this.nodePool;\n const ap = this.aabbPool;\n\n // 1. Update the AABB data.\n ap.set(objectIndex, newMinX, newMinY, newMinZ, newMaxX, newMaxY, newMaxZ);\n\n // 2. Find current node.\n const currentNode = this.objectNodeMap.get(objectIndex);\n if (currentNode === undefined) return;\n\n // 3. If the new bounds still fit in the current node, nothing to move.\n if (this.fitsInNode(objectIndex, currentNode)) return;\n\n // 4. Remove from current node.\n np.removeObject(currentNode, objectIndex);\n this.objectNodeMap.delete(objectIndex);\n\n // 5. Walk up to find the lowest ancestor that fully contains the new bounds.\n let ancestorNode = np.getParent(currentNode);\n while (ancestorNode !== -1 && !this.fitsInNode(objectIndex, ancestorNode)) {\n ancestorNode = np.getParent(ancestorNode);\n }\n // If no ancestor fits (shouldn't happen for a well-formed tree), use root.\n if (ancestorNode === -1) ancestorNode = this.root;\n\n // 6. Re-insert downward from the ancestor.\n this.insertIntoNode(ancestorNode, objectIndex);\n }\n\n /**\n * Remove the object at `objectIndex` from the tree.\n * Safe to call even if the object was never inserted (no-op).\n */\n remove(objectIndex: number): void {\n const nodeIdx = this.objectNodeMap.get(objectIndex);\n if (nodeIdx === undefined) return;\n this.nodePool.removeObject(nodeIdx, objectIndex);\n this.objectNodeMap.delete(objectIndex);\n }\n\n /**\n * Cast a ray through the octree and return the closest intersecting object.\n *\n * Uses an iterative stack traversal (pre-allocated, no recursion) to avoid\n * GC pressure. Only descends into child nodes whose AABBs are intersected by\n * the ray, providing efficient pruning of non-intersecting subtrees.\n *\n * @param rayBuf Float32Array containing the ray data [ox,oy,oz,dx,dy,dz].\n * @param rayOffset Element offset (in floats) within `rayBuf` to the ray's origin.\n * @returns The closest `{ objectIndex, t }` hit, or `null` if nothing is hit.\n */\n raycast(\n rayBuf: Float32Array,\n rayOffset: number,\n ): { objectIndex: number; t: number } | null {\n const np = this.nodePool;\n // Access the underlying flat buffers directly for zero-GC intersection tests.\n const npBuf = (np as unknown as { buffer: Float32Array }).buffer;\n const apBuf = (this.aabbPool as unknown as { buffer: Float32Array }).buffer;\n\n // Early-out: test the root AABB before touching any object data.\n if (rayIntersectsAABB(rayBuf, rayOffset, npBuf, this.root * NODE_STRIDE + NODE_AABB_OFFSET) < 0) {\n return null;\n }\n\n // Iterative DFS using the pre-allocated stack.\n this._stack.length = 0;\n this._stack.push(this.root);\n\n let closestT = Infinity;\n let closestIndex = -1;\n\n while (this._stack.length > 0) {\n const nodeIdx = this._stack.pop()!;\n\n // Test every object stored at this node level.\n const objCount = np.getObjectCount(nodeIdx);\n for (let i = 0; i < objCount; i++) {\n const objIdx = np.getObject(nodeIdx, i);\n const t = rayIntersectsAABB(rayBuf, rayOffset, apBuf, objIdx * AABB_STRIDE);\n if (t >= 0 && t < closestT) {\n closestT = t;\n closestIndex = objIdx;\n }\n }\n\n // Push only children whose AABB the ray actually intersects.\n const firstChild = np.getFirstChild(nodeIdx);\n if (firstChild !== -1) {\n for (let i = 0; i < 8; i++) {\n const childIdx = firstChild + i;\n if (rayIntersectsAABB(rayBuf, rayOffset, npBuf, childIdx * NODE_STRIDE + NODE_AABB_OFFSET) >= 0) {\n this._stack.push(childIdx);\n }\n }\n }\n }\n\n if (closestIndex === -1) return null;\n return { objectIndex: closestIndex, t: closestT };\n }\n\n /**\n * Query the octree for all objects whose AABB overlaps the given axis-aligned\n * box region and return their indices (in the AABBPool).\n *\n * Uses the same pre-allocated iterative stack as `raycast` to avoid GC\n * pressure. Descends only into child nodes whose AABBs overlap the query box,\n * pruning non-intersecting subtrees.\n *\n * @param minX Minimum X of the query box.\n * @param minY Minimum Y of the query box.\n * @param minZ Minimum Z of the query box.\n * @param maxX Maximum X of the query box.\n * @param maxY Maximum Y of the query box.\n * @param maxZ Maximum Z of the query box.\n * @returns Array of AABBPool indices for every object that overlaps the box.\n */\n queryBox(\n minX: number,\n minY: number,\n minZ: number,\n maxX: number,\n maxY: number,\n maxZ: number,\n ): number[] {\n const np = this.nodePool;\n const npBuf = (np as unknown as { buffer: Float32Array }).buffer;\n const apBuf = (this.aabbPool as unknown as { buffer: Float32Array }).buffer;\n\n const results: number[] = [];\n\n // Early-out: if the query box doesn't overlap the root, nothing to do.\n if (!aabbOverlapsBox(npBuf, this.root * NODE_STRIDE + NODE_AABB_OFFSET, minX, minY, minZ, maxX, maxY, maxZ)) {\n return results;\n }\n\n // Iterative DFS using the pre-allocated stack.\n this._stack.length = 0;\n this._stack.push(this.root);\n\n while (this._stack.length > 0) {\n const nodeIdx = this._stack.pop()!;\n\n // Test every object stored at this node level.\n const objCount = np.getObjectCount(nodeIdx);\n for (let i = 0; i < objCount; i++) {\n const objIdx = np.getObject(nodeIdx, i);\n if (aabbOverlapsBox(apBuf, objIdx * AABB_STRIDE, minX, minY, minZ, maxX, maxY, maxZ)) {\n results.push(objIdx);\n }\n }\n\n // Push only children whose AABB overlaps the query box.\n const firstChild = np.getFirstChild(nodeIdx);\n if (firstChild !== -1) {\n for (let i = 0; i < 8; i++) {\n const childIdx = firstChild + i;\n if (aabbOverlapsBox(npBuf, childIdx * NODE_STRIDE + NODE_AABB_OFFSET, minX, minY, minZ, maxX, maxY, maxZ)) {\n this._stack.push(childIdx);\n }\n }\n }\n }\n\n return results;\n }\n\n // ── Private helpers ──────────────────────────────────────────────────────\n\n private insertIntoNode(nodeIdx: number, objectIndex: number): void {\n const np = this.nodePool;\n const firstChild = np.getFirstChild(nodeIdx);\n\n // Internal node: try to push the object into a fitting child octant.\n if (firstChild !== -1) {\n for (let i = 0; i < 8; i++) {\n if (this.fitsInNode(objectIndex, firstChild + i)) {\n this.insertIntoNode(firstChild + i, objectIndex);\n return;\n }\n }\n // Object straddles one or more boundaries – keep it at this level.\n // We manually add it here even if it exceeds MAX_OBJECTS_PER_NODE,\n // because internal nodes only hold straddling objects.\n np.addObject(nodeIdx, objectIndex);\n this.objectNodeMap.set(objectIndex, nodeIdx);\n return;\n }\n\n // Leaf node.\n const count = np.getObjectCount(nodeIdx);\n if (count < MAX_OBJECTS_PER_NODE) {\n np.addObject(nodeIdx, objectIndex);\n this.objectNodeMap.set(objectIndex, nodeIdx);\n } else {\n // Prevent infinite subdivision if perfectly overlapping items exceed capacity.\n // If the node we are trying to subdivide has an incredibly small physical bounds\n // or if it fails to separate objects during subdivide, it would loop forever. \n // Instead, we just forcefully add it here and allow the slot to overflow if needed,\n // but because our Float32Array nodes have strict memory limits for MAX_OBJECTS,\n // we must just drop it or accept that `MAX_OBJECTS_PER_NODE` is a hard physical limit.\n // To properly handle this without breaking the pool sizing, in a DOD engine we simply\n // stop adding when perfectly overlapping identical items exceed memory at the leaf bounds.\n try {\n this.subdivide(nodeIdx);\n this.insertIntoNode(nodeIdx, objectIndex);\n } catch (e) {\n if (e instanceof RangeError) {\n // Hard capacity limit hit on identical objects that refuse to separate. \n // Stop recursion. We cannot safely insert it into this perfectly overlapping bucket.\n } else {\n throw e; // Bubble up other errors.\n }\n }\n }\n }\n\n /** Split a leaf node into 8 children and redistribute its objects. */\n private subdivide(nodeIdx: number): void {\n const np = this.nodePool;\n\n const minX = np.getAABB(nodeIdx, 0);\n const minY = np.getAABB(nodeIdx, 1);\n const minZ = np.getAABB(nodeIdx, 2);\n const maxX = np.getAABB(nodeIdx, 3);\n const maxY = np.getAABB(nodeIdx, 4);\n const maxZ = np.getAABB(nodeIdx, 5);\n\n const midX = (minX + maxX) / 2;\n const midY = (minY + maxY) / 2;\n const midZ = (minZ + maxZ) / 2;\n\n // Allocate 8 consecutive child nodes (guarantees contiguous indices).\n const firstChild = np.allocate();\n for (let i = 1; i < 8; i++) {\n np.allocate();\n }\n np.setFirstChild(nodeIdx, firstChild);\n\n // Assign each child its octant AABB.\n // Bit decomposition: bit-0 → X half, bit-1 → Y half, bit-2 → Z half.\n // i & 1 === 0 means left half (minX to midX), i & 1 !== 0 means right half (midX to maxX)\n for (let i = 0; i < 8; i++) {\n const childIdx = firstChild + i;\n const cMinX = (i & 1) === 0 ? minX : midX;\n const cMaxX = (i & 1) === 0 ? midX : maxX;\n const cMinY = (i & 2) === 0 ? minY : midY;\n const cMaxY = (i & 2) === 0 ? midY : maxY;\n const cMinZ = (i & 4) === 0 ? minZ : midZ;\n const cMaxZ = (i & 4) === 0 ? midZ : maxZ;\n np.setAABB(childIdx, cMinX, cMinY, cMinZ, cMaxX, cMaxY, cMaxZ);\n np.setParent(childIdx, nodeIdx);\n }\n\n // Redistribute the existing objects.\n const count = np.getObjectCount(nodeIdx);\n const saved: number[] = [];\n for (let i = 0; i < count; i++) {\n saved.push(np.getObject(nodeIdx, i));\n }\n np.clearObjects(nodeIdx);\n\n for (const obj of saved) {\n // Use insertIntoNode so objectNodeMap stays consistent.\n this.objectNodeMap.delete(obj);\n this.insertIntoNode(nodeIdx, obj);\n }\n }\n\n /** Returns true when the AABB at `objectIndex` is fully contained by `nodeIdx`. */\n private fitsInNode(objectIndex: number, nodeIdx: number): boolean {\n const np = this.nodePool;\n const ap = this.aabbPool;\n return (\n ap.get(objectIndex, 0) >= np.getAABB(nodeIdx, 0) &&\n ap.get(objectIndex, 1) >= np.getAABB(nodeIdx, 1) &&\n ap.get(objectIndex, 2) >= np.getAABB(nodeIdx, 2) &&\n ap.get(objectIndex, 3) <= np.getAABB(nodeIdx, 3) &&\n ap.get(objectIndex, 4) <= np.getAABB(nodeIdx, 4) &&\n ap.get(objectIndex, 5) <= np.getAABB(nodeIdx, 5)\n );\n }\n}\n","/**\n * LiDAR Web Worker\n *\n * Handles heavy raycasting (LiDAR sweep logic) off the main thread.\n *\n * Protocol\n * --------\n * 1. Main thread sends one `LidarInitMessage` to set up shared buffers and\n * world bounds. The worker replies with `{ type: 'ready' }`.\n *\n * 2. For each sweep the main thread:\n * a. Writes up-to-date object AABB data into `aabbsSab`\n * (6 floats per object: minX, minY, minZ, maxX, maxY, maxZ).\n * b. Sends a `LidarSweepMessage` with the current `objectCount`.\n *\n * 3. The worker reads the transforms, inserts/updates objects in its local\n * Octree, casts every ray from `raysSab`, and writes results to\n * `resultsSab` (2 floats per ray: objectIndex, t; –1/–1 for a miss).\n * It then replies with `{ type: 'done', rayCount }`.\n *\n * SharedArrayBuffer layout\n * ------------------------\n * aabbsSab : Float32Array – objectCapacity × AABB_STRIDE (6) floats\n * nodesSab : Float32Array – nodeCapacity × NODE_STRIDE floats\n * raysSab : Float32Array – rayCount × RAY_STRIDE (6) floats\n * resultsSab: Float32Array – rayCount × 2 floats\n * [objectIndex (float), t (float)] per ray\n */\n\nimport { OctreeNodePool } from './octree-node.js';\nimport { AABBPool } from './aabb.js';\nimport { RAY_STRIDE } from './ray.js';\nimport { Octree } from './octree.js';\n\n// ── Public message types ──────────────────────────────────────────────────────\n\n/** Sent once from the main thread to initialise the shared buffers. */\nexport interface LidarInitMessage {\n type: 'init';\n /** SharedArrayBuffer for AABB data (objectCapacity × 6 floats). Written by the main thread. */\n aabbsSab: SharedArrayBuffer;\n /** SharedArrayBuffer for Octree node data (nodeCapacity × NODE_STRIDE floats). Managed by the worker. */\n nodesSab: SharedArrayBuffer;\n /** SharedArrayBuffer for ray data (rayCount × 6 floats). Written by the main thread (or set once). */\n raysSab: SharedArrayBuffer;\n /** SharedArrayBuffer for raycast results (rayCount × 2 floats). Written by the worker. */\n resultsSab: SharedArrayBuffer;\n /** Maximum number of objects the AABBPool can hold. */\n objectCapacity: number;\n /** Maximum number of Octree nodes the NodePool can hold. */\n nodeCapacity: number;\n /** Number of rays in the LiDAR sweep. */\n rayCount: number;\n /** World-space bounds for the root octree node. */\n worldMinX: number;\n worldMinY: number;\n worldMinZ: number;\n worldMaxX: number;\n worldMaxY: number;\n worldMaxZ: number;\n}\n\n/** Sent each frame to trigger a LiDAR sweep. */\nexport interface LidarSweepMessage {\n type: 'sweep';\n /** Number of active objects whose AABB data has been written to `aabbsSab`. */\n objectCount: number;\n}\n\nexport type LidarWorkerInMessage = LidarInitMessage | LidarSweepMessage;\n\n/** Posted by the worker after initialisation succeeds. */\nexport interface LidarReadyMessage {\n type: 'ready';\n}\n\n/** Posted by the worker after a sweep finishes. */\nexport interface LidarDoneMessage {\n type: 'done';\n /** Number of rays that were cast (equals the `rayCount` given in `init`). */\n rayCount: number;\n}\n\nexport type LidarWorkerOutMessage = LidarReadyMessage | LidarDoneMessage;\n\n// ── Processor (pure logic, no worker-global bindings) ────────────────────────\n\n/**\n * Stateful LiDAR sweep processor.\n *\n * Exported for direct use in tests and advanced integrations. The bottom of\n * this module wires it to the Web Worker global scope automatically when the\n * script runs inside a worker.\n */\nexport function createLidarProcessor(): {\n init(msg: LidarInitMessage): LidarReadyMessage;\n sweep(msg: LidarSweepMessage): LidarDoneMessage;\n} {\n let octree: Octree | null = null;\n let aabbPool: AABBPool | null = null;\n let raysBuf: Float32Array | null = null;\n let resultsBuf: Float32Array | null = null;\n let totalRayCount = 0;\n /** Tracks how many objects have been inserted into the local octree. */\n let insertedObjectCount = 0;\n\n return {\n /** Initialise shared buffers and create the Octree. */\n init(msg: LidarInitMessage): LidarReadyMessage {\n const nodePool = new OctreeNodePool(msg.nodeCapacity, msg.nodesSab);\n aabbPool = new AABBPool(msg.objectCapacity, msg.aabbsSab);\n raysBuf = new Float32Array(msg.raysSab);\n resultsBuf = new Float32Array(msg.resultsSab);\n totalRayCount = msg.rayCount;\n insertedObjectCount = 0;\n\n octree = new Octree(nodePool, aabbPool);\n octree.setBounds(\n msg.worldMinX, msg.worldMinY, msg.worldMinZ,\n msg.worldMaxX, msg.worldMaxY, msg.worldMaxZ,\n );\n\n return { type: 'ready' };\n },\n\n /**\n * Read current object AABB data from the shared buffer, update the Octree,\n * cast every sweep ray, and write results to the results buffer.\n */\n sweep(msg: LidarSweepMessage): LidarDoneMessage {\n if (octree === null || aabbPool === null || raysBuf === null || resultsBuf === null) {\n throw new Error('LidarProcessor: sweep called before init');\n }\n\n const objectCount = msg.objectCount;\n\n // Ensure the pool's allocation count reflects all active objects so that\n // the pool's `size` getter stays accurate (the buffer memory already exists\n // in the SAB – allocate() only increments the internal counter).\n while (aabbPool.size < objectCount) {\n aabbPool.allocate();\n }\n\n // Insert new objects or reposition existing ones in the octree.\n for (let i = 0; i < objectCount; i++) {\n const minX = aabbPool.get(i, 0);\n const minY = aabbPool.get(i, 1);\n const minZ = aabbPool.get(i, 2);\n const maxX = aabbPool.get(i, 3);\n const maxY = aabbPool.get(i, 4);\n const maxZ = aabbPool.get(i, 5);\n\n if (i < insertedObjectCount) {\n // Already in the tree – reposition without full rebuild.\n octree.update(i, minX, minY, minZ, maxX, maxY, maxZ);\n } else {\n // First appearance: insert using the bounds already in the SAB.\n octree.insert(i);\n insertedObjectCount++;\n }\n }\n\n // LiDAR sweep: cast each ray and write the closest hit (or miss) to\n // the results buffer. Layout: [objectIndex, t] per ray (–1/–1 = miss).\n for (let r = 0; r < totalRayCount; r++) {\n const hit = octree.raycast(raysBuf, r * RAY_STRIDE);\n if (hit !== null) {\n resultsBuf[r * 2] = hit.objectIndex;\n resultsBuf[r * 2 + 1] = hit.t;\n } else {\n resultsBuf[r * 2] = -1;\n resultsBuf[r * 2 + 1] = -1;\n }\n }\n\n return { type: 'done', rayCount: totalRayCount };\n },\n };\n}\n\n// ── Web Worker binding ────────────────────────────────────────────────────────\n// Automatically wire the processor to the worker's global message handler when\n// this script is loaded inside a Web Worker context (browser or Node.js worker).\n\ntype WorkerGlobalSelf = {\n onmessage: ((event: { data: LidarWorkerInMessage }) => void) | null;\n postMessage(data: LidarWorkerOutMessage): void;\n};\n\n// `postMessage` is available on the worker global but not in a regular Node.js\n// module context, so check for it before binding.\nconst globalScope = globalThis as Record<string, unknown>;\nif (typeof globalScope['postMessage'] === 'function' && typeof globalScope['onmessage'] !== 'undefined') {\n const workerSelf = globalThis as unknown as WorkerGlobalSelf;\n const processor = createLidarProcessor();\n\n workerSelf.onmessage = (event) => {\n const msg = event.data;\n if (msg.type === 'init') {\n workerSelf.postMessage(processor.init(msg));\n } else if (msg.type === 'sweep') {\n workerSelf.postMessage(processor.sweep(msg));\n }\n };\n}\n"],"mappings":"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","names":[]}
package/dist/index.cjs CHANGED
@@ -269,7 +269,7 @@ var NODE_FIRST_CHILD_OFFSET = 6;
269
269
  var NODE_PARENT_OFFSET = 7;
270
270
  var NODE_OBJECT_COUNT_OFFSET = 8;
271
271
  var NODE_OBJECTS_OFFSET = 9;
272
- var MAX_OBJECTS_PER_NODE = 8;
272
+ var MAX_OBJECTS_PER_NODE = 64;
273
273
  var NODE_STRIDE = NODE_OBJECTS_OFFSET + MAX_OBJECTS_PER_NODE;
274
274
  var OctreeNodePool = class _OctreeNodePool {
275
275
  buffer;
@@ -435,6 +435,16 @@ var Octree = class {
435
435
  if (ancestorNode === -1) ancestorNode = this.root;
436
436
  this.insertIntoNode(ancestorNode, objectIndex);
437
437
  }
438
+ /**
439
+ * Remove the object at `objectIndex` from the tree.
440
+ * Safe to call even if the object was never inserted (no-op).
441
+ */
442
+ remove(objectIndex) {
443
+ const nodeIdx = this.objectNodeMap.get(objectIndex);
444
+ if (nodeIdx === void 0) return;
445
+ this.nodePool.removeObject(nodeIdx, objectIndex);
446
+ this.objectNodeMap.delete(objectIndex);
447
+ }
438
448
  /**
439
449
  * Cast a ray through the octree and return the closest intersecting object.
440
450
  *
@@ -548,8 +558,15 @@ var Octree = class {
548
558
  np.addObject(nodeIdx, objectIndex);
549
559
  this.objectNodeMap.set(objectIndex, nodeIdx);
550
560
  } else {
551
- this.subdivide(nodeIdx);
552
- this.insertIntoNode(nodeIdx, objectIndex);
561
+ try {
562
+ this.subdivide(nodeIdx);
563
+ this.insertIntoNode(nodeIdx, objectIndex);
564
+ } catch (e) {
565
+ if (e instanceof RangeError) {
566
+ } else {
567
+ throw e;
568
+ }
569
+ }
553
570
  }
554
571
  }
555
572
  /** Split a leaf node into 8 children and redistribute its objects. */
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/aabb.ts","../src/ray.ts","../src/object-pool.ts","../src/flat-math.ts","../src/octree-node.ts","../src/octree.ts","../src/lidar-worker.ts"],"sourcesContent":["export { AABBPool, aabbIntersects, aabbExpand, aabbMerge, AABB_STRIDE } from './aabb.js';\nexport { RayPool, rayIntersectsAABB, RAY_STRIDE } from './ray.js';\nexport { ObjectPool } from './object-pool.js';\nexport { vec3Dot, vec3DistanceSq, vec3Distance, vec3Cross } from './flat-math.js';\nexport {\n OctreeNodePool,\n NODE_STRIDE,\n NODE_AABB_OFFSET,\n NODE_FIRST_CHILD_OFFSET,\n NODE_PARENT_OFFSET,\n NODE_OBJECT_COUNT_OFFSET,\n NODE_OBJECTS_OFFSET,\n MAX_OBJECTS_PER_NODE,\n} from './octree-node.js';\nexport { Octree } from './octree.js';\nexport {\n createLidarProcessor,\n} from './lidar-worker.js';\nexport type {\n LidarInitMessage,\n LidarSweepMessage,\n LidarWorkerInMessage,\n LidarReadyMessage,\n LidarDoneMessage,\n LidarWorkerOutMessage,\n} from './lidar-worker.js';\n","/**\n * AABB (Axis-Aligned Bounding Box) stored as a flat Float32Array slice.\n * Layout: [minX, minY, minZ, maxX, maxY, maxZ]\n */\nexport const AABB_STRIDE = 6;\n\n/**\n * A pool-backed, zero-GC AABB store.\n * Each AABB occupies AABB_STRIDE floats in the underlying buffer.\n *\n * When a `SharedArrayBuffer` is provided, the pool's data is accessible from\n * multiple threads (e.g. a Web Worker) with no copying.\n */\nexport class AABBPool {\n private readonly buffer: Float32Array;\n private count: number = 0;\n\n constructor(capacity: number, sharedBuffer?: SharedArrayBuffer) {\n this.buffer = sharedBuffer\n ? new Float32Array(sharedBuffer, 0, capacity * AABB_STRIDE)\n : new Float32Array(capacity * AABB_STRIDE);\n }\n\n /**\n * Create an AABBPool backed by a new `SharedArrayBuffer`.\n * Both the pool and the underlying `SharedArrayBuffer` are returned so the\n * caller can transfer the buffer to a `Worker` via `postMessage`.\n */\n static createShared(capacity: number): { pool: AABBPool; sab: SharedArrayBuffer } {\n const sab = new SharedArrayBuffer(capacity * AABB_STRIDE * Float32Array.BYTES_PER_ELEMENT);\n return { pool: new AABBPool(capacity, sab), sab };\n }\n\n /** Allocate a new AABB slot and return its index. */\n allocate(): number {\n const index = this.count;\n this.count += 1;\n return index;\n }\n\n /** Set the bounds of an AABB at the given index. */\n set(\n index: number,\n minX: number,\n minY: number,\n minZ: number,\n maxX: number,\n maxY: number,\n maxZ: number,\n ): void {\n const offset = index * AABB_STRIDE;\n this.buffer[offset] = minX;\n this.buffer[offset + 1] = minY;\n this.buffer[offset + 2] = minZ;\n this.buffer[offset + 3] = maxX;\n this.buffer[offset + 4] = maxY;\n this.buffer[offset + 5] = maxZ;\n }\n\n /** Read a single component value from the buffer. */\n get(index: number, component: number): number {\n return this.buffer[index * AABB_STRIDE + component] ?? 0;\n }\n\n /** Returns the number of allocated AABBs. */\n get size(): number {\n return this.count;\n }\n\n /** Reset the pool (all allocations freed, no GC). */\n reset(): void {\n this.count = 0;\n }\n}\n\n/**\n * Expand AABB `a` in-place so it also fully contains AABB `b`.\n */\nexport function aabbExpand(pool: AABBPool, a: number, b: number): void {\n const buf = (pool as unknown as { buffer: Float32Array }).buffer;\n const aOff = a * AABB_STRIDE;\n const bOff = b * AABB_STRIDE;\n buf[aOff] = Math.min(buf[aOff] ?? 0, buf[bOff] ?? 0);\n buf[aOff + 1] = Math.min(buf[aOff + 1] ?? 0, buf[bOff + 1] ?? 0);\n buf[aOff + 2] = Math.min(buf[aOff + 2] ?? 0, buf[bOff + 2] ?? 0);\n buf[aOff + 3] = Math.max(buf[aOff + 3] ?? 0, buf[bOff + 3] ?? 0);\n buf[aOff + 4] = Math.max(buf[aOff + 4] ?? 0, buf[bOff + 4] ?? 0);\n buf[aOff + 5] = Math.max(buf[aOff + 5] ?? 0, buf[bOff + 5] ?? 0);\n}\n\n/**\n * Merge AABBs `a` and `b` into `dest` (a pre-allocated slot), storing their union.\n */\nexport function aabbMerge(pool: AABBPool, dest: number, a: number, b: number): void {\n const buf = (pool as unknown as { buffer: Float32Array }).buffer;\n const aOff = a * AABB_STRIDE;\n const bOff = b * AABB_STRIDE;\n const dOff = dest * AABB_STRIDE;\n buf[dOff] = Math.min(buf[aOff] ?? 0, buf[bOff] ?? 0);\n buf[dOff + 1] = Math.min(buf[aOff + 1] ?? 0, buf[bOff + 1] ?? 0);\n buf[dOff + 2] = Math.min(buf[aOff + 2] ?? 0, buf[bOff + 2] ?? 0);\n buf[dOff + 3] = Math.max(buf[aOff + 3] ?? 0, buf[bOff + 3] ?? 0);\n buf[dOff + 4] = Math.max(buf[aOff + 4] ?? 0, buf[bOff + 4] ?? 0);\n buf[dOff + 5] = Math.max(buf[aOff + 5] ?? 0, buf[bOff + 5] ?? 0);\n}\n\n/**\n * Test whether two AABBs (by index in the same pool) intersect.\n */\nexport function aabbIntersects(pool: AABBPool, a: number, b: number): boolean {\n const aOff = a * AABB_STRIDE;\n const bOff = b * AABB_STRIDE;\n // Access via get() to stay safe with bounds, but direct buffer access is\n // equally valid when performance is critical.\n const buf = (pool as unknown as { buffer: Float32Array }).buffer;\n return (\n (buf[aOff] ?? 0) <= (buf[bOff + 3] ?? 0) &&\n (buf[aOff + 3] ?? 0) >= (buf[bOff] ?? 0) &&\n (buf[aOff + 1] ?? 0) <= (buf[bOff + 4] ?? 0) &&\n (buf[aOff + 4] ?? 0) >= (buf[bOff + 1] ?? 0) &&\n (buf[aOff + 2] ?? 0) <= (buf[bOff + 5] ?? 0) &&\n (buf[aOff + 5] ?? 0) >= (buf[bOff + 2] ?? 0)\n );\n}\n","/**\n * Ray stored as origin + direction (unit vector).\n * Layout: [ox, oy, oz, dx, dy, dz]\n */\nexport const RAY_STRIDE = 6;\n\n/** A pool-backed, zero-GC Ray store.\n *\n * When a `SharedArrayBuffer` is provided, the pool's data is accessible from\n * multiple threads (e.g. a Web Worker) with no copying.\n */\nexport class RayPool {\n private readonly buffer: Float32Array;\n private count: number = 0;\n\n constructor(capacity: number, sharedBuffer?: SharedArrayBuffer) {\n this.buffer = sharedBuffer\n ? new Float32Array(sharedBuffer, 0, capacity * RAY_STRIDE)\n : new Float32Array(capacity * RAY_STRIDE);\n }\n\n /**\n * Create a RayPool backed by a new `SharedArrayBuffer`.\n * Both the pool and the underlying `SharedArrayBuffer` are returned so the\n * caller can transfer the buffer to a `Worker` via `postMessage`.\n */\n static createShared(capacity: number): { pool: RayPool; sab: SharedArrayBuffer } {\n const sab = new SharedArrayBuffer(capacity * RAY_STRIDE * Float32Array.BYTES_PER_ELEMENT);\n return { pool: new RayPool(capacity, sab), sab };\n }\n\n /** Allocate a new Ray slot and return its index. */\n allocate(): number {\n const index = this.count;\n this.count += 1;\n return index;\n }\n\n /** Set origin and direction of a ray at the given index. */\n set(\n index: number,\n ox: number,\n oy: number,\n oz: number,\n dx: number,\n dy: number,\n dz: number,\n ): void {\n const offset = index * RAY_STRIDE;\n this.buffer[offset] = ox;\n this.buffer[offset + 1] = oy;\n this.buffer[offset + 2] = oz;\n this.buffer[offset + 3] = dx;\n this.buffer[offset + 4] = dy;\n this.buffer[offset + 5] = dz;\n }\n\n /** Read a single component value from the buffer. */\n get(index: number, component: number): number {\n return this.buffer[index * RAY_STRIDE + component] ?? 0;\n }\n\n /** Returns the number of allocated Rays. */\n get size(): number {\n return this.count;\n }\n\n /** Reset the pool (all allocations freed, no GC). */\n reset(): void {\n this.count = 0;\n }\n}\n\n/**\n * Optimized branchless slab-method ray–AABB intersection test.\n *\n * Operates purely on flat Float32Arrays with no per-axis direction branches.\n * When a direction component is zero, IEEE 754 produces ±Infinity for the\n * reciprocal, which propagates correctly through the min/max slab computation:\n * - origin inside the slab → [−∞, +∞] interval (no constraint)\n * - origin outside the slab → either [+∞, +∞] or [−∞, −∞] → forces a miss\n *\n * Returns the parametric hit distance `t` (>= 0) if the ray intersects the\n * AABB, or -1 if there is no intersection (miss or box entirely behind origin).\n */\nexport function rayIntersectsAABB(\n rayBuf: Float32Array,\n rayOffset: number,\n aabbBuf: Float32Array,\n aabbOffset: number,\n): number {\n const ox = rayBuf[rayOffset] ?? 0;\n const oy = rayBuf[rayOffset + 1] ?? 0;\n const oz = rayBuf[rayOffset + 2] ?? 0;\n const idx = 1 / (rayBuf[rayOffset + 3] ?? 0);\n const idy = 1 / (rayBuf[rayOffset + 4] ?? 0);\n const idz = 1 / (rayBuf[rayOffset + 5] ?? 0);\n\n const minX = aabbBuf[aabbOffset] ?? 0;\n const minY = aabbBuf[aabbOffset + 1] ?? 0;\n const minZ = aabbBuf[aabbOffset + 2] ?? 0;\n const maxX = aabbBuf[aabbOffset + 3] ?? 0;\n const maxY = aabbBuf[aabbOffset + 4] ?? 0;\n const maxZ = aabbBuf[aabbOffset + 5] ?? 0;\n\n // Branchless slab method: per-axis near/far t values.\n const t1x = (minX - ox) * idx;\n const t2x = (maxX - ox) * idx;\n const t1y = (minY - oy) * idy;\n const t2y = (maxY - oy) * idy;\n const t1z = (minZ - oz) * idz;\n const t2z = (maxZ - oz) * idz;\n\n const tmin = Math.max(Math.min(t1x, t2x), Math.min(t1y, t2y), Math.min(t1z, t2z));\n const tmax = Math.min(Math.max(t1x, t2x), Math.max(t1y, t2y), Math.max(t1z, t2z));\n\n if (tmax < 0 || !(tmin <= tmax)) return -1;\n return tmin >= 0 ? tmin : tmax;\n}\n","/**\n * A generic Object Pool that manages a fixed set of integer indices.\n *\n * Pre-allocates a free-list on construction so that acquire() and release()\n * operate without any heap allocation.\n */\nexport class ObjectPool {\n private readonly freeList: Int32Array;\n private top: number;\n\n /** Total number of slots in the pool. */\n readonly capacity: number;\n\n constructor(capacity: number) {\n this.capacity = capacity;\n this.freeList = new Int32Array(capacity);\n // Fill free-list in reverse so that the first acquire() returns index 0.\n for (let i = 0; i < capacity; i++) {\n this.freeList[i] = capacity - 1 - i;\n }\n this.top = capacity;\n }\n\n /** Number of indices currently available for acquisition. */\n get available(): number {\n return this.top;\n }\n\n /**\n * Acquire a free index from the pool.\n * Returns the index, or `null` when the pool is exhausted.\n */\n acquire(): number | null {\n if (this.top === 0) {\n return null;\n }\n this.top -= 1;\n return this.freeList[this.top] as number;\n }\n\n /**\n * Release a previously acquired index back to the pool.\n * Throws if the index is out of range or the pool is already full.\n */\n release(index: number): void {\n if (index < 0 || index >= this.capacity) {\n throw new RangeError(`ObjectPool.release: index ${index} is out of range [0, ${this.capacity})`);\n }\n if (this.top >= this.capacity) {\n throw new RangeError('ObjectPool.release: pool is already at full capacity');\n }\n this.freeList[this.top] = index;\n this.top += 1;\n }\n}\n","/**\n * Flat Array Math Utilities\n *\n * Zero-allocation Vec3 math that reads directly from a Float32Array\n * given a start index, without creating any Vector3 objects.\n *\n * All index parameters (`i`, `j`, `outOffset`) are raw flat-array offsets.\n */\n\n/**\n * Compute the dot product of two Vec3s stored in `buf` at offsets `i` and `j`.\n */\nexport function vec3Dot(buf: Float32Array, i: number, j: number): number {\n return (buf[i] ?? 0) * (buf[j] ?? 0) +\n (buf[i + 1] ?? 0) * (buf[j + 1] ?? 0) +\n (buf[i + 2] ?? 0) * (buf[j + 2] ?? 0);\n}\n\n/**\n * Compute the squared distance between two Vec3s stored in `buf` at offsets `i` and `j`.\n */\nexport function vec3DistanceSq(buf: Float32Array, i: number, j: number): number {\n const dx = (buf[i] ?? 0) - (buf[j] ?? 0);\n const dy = (buf[i + 1] ?? 0) - (buf[j + 1] ?? 0);\n const dz = (buf[i + 2] ?? 0) - (buf[j + 2] ?? 0);\n return dx * dx + dy * dy + dz * dz;\n}\n\n/**\n * Compute the Euclidean distance between two Vec3s stored in `buf` at offsets `i` and `j`.\n */\nexport function vec3Distance(buf: Float32Array, i: number, j: number): number {\n return Math.sqrt(vec3DistanceSq(buf, i, j));\n}\n\n/**\n * Compute the cross product of two Vec3s in `buf` at offsets `i` and `j`,\n * and write the result into `out` starting at `outOffset`.\n */\nexport function vec3Cross(\n out: Float32Array,\n outOffset: number,\n buf: Float32Array,\n i: number,\n j: number,\n): void {\n const ax = buf[i] ?? 0;\n const ay = buf[i + 1] ?? 0;\n const az = buf[i + 2] ?? 0;\n const bx = buf[j] ?? 0;\n const by = buf[j + 1] ?? 0;\n const bz = buf[j + 2] ?? 0;\n out[outOffset] = ay * bz - az * by;\n out[outOffset + 1] = az * bx - ax * bz;\n out[outOffset + 2] = ax * by - ay * bx;\n}\n","/**\n * Octree node stored as a flat Float32Array slice.\n *\n * Layout per node (NODE_STRIDE floats):\n * [0..5] – AABB (minX, minY, minZ, maxX, maxY, maxZ)\n * [6] – firstChild index (-1 = leaf / no children)\n * [7] – parent index (-1 = root)\n * [8] – object count\n * [9..] – object indices (MAX_OBJECTS_PER_NODE slots)\n */\n\nexport const NODE_AABB_OFFSET = 0;\nexport const NODE_FIRST_CHILD_OFFSET = 6;\nexport const NODE_PARENT_OFFSET = 7;\nexport const NODE_OBJECT_COUNT_OFFSET = 8;\nexport const NODE_OBJECTS_OFFSET = 9;\nexport const MAX_OBJECTS_PER_NODE = 8;\nexport const NODE_STRIDE = NODE_OBJECTS_OFFSET + MAX_OBJECTS_PER_NODE;\n\n/**\n * A pool-backed, zero-GC Octree node store.\n * Each node occupies NODE_STRIDE floats in the underlying Float32Array buffer.\n *\n * When a `SharedArrayBuffer` is provided, the pool's data is accessible from\n * multiple threads (e.g. a Web Worker) with no copying.\n */\nexport class OctreeNodePool {\n private readonly buffer: Float32Array;\n private count: number = 0;\n\n constructor(capacity: number, sharedBuffer?: SharedArrayBuffer) {\n this.buffer = sharedBuffer\n ? new Float32Array(sharedBuffer, 0, capacity * NODE_STRIDE)\n : new Float32Array(capacity * NODE_STRIDE);\n }\n\n /**\n * Create an OctreeNodePool backed by a new `SharedArrayBuffer`.\n * Both the pool and the underlying `SharedArrayBuffer` are returned so the\n * caller can transfer the buffer to a `Worker` via `postMessage`.\n */\n static createShared(capacity: number): { pool: OctreeNodePool; sab: SharedArrayBuffer } {\n const sab = new SharedArrayBuffer(capacity * NODE_STRIDE * Float32Array.BYTES_PER_ELEMENT);\n return { pool: new OctreeNodePool(capacity, sab), sab };\n }\n\n /** Allocate a new node slot, initialise sentinel values, and return its index. */\n allocate(): number {\n const index = this.count;\n const offset = index * NODE_STRIDE;\n // Initialise sentinel values for child/parent links and object count.\n this.buffer[offset + NODE_FIRST_CHILD_OFFSET] = -1;\n this.buffer[offset + NODE_PARENT_OFFSET] = -1;\n this.buffer[offset + NODE_OBJECT_COUNT_OFFSET] = 0;\n this.count += 1;\n return index;\n }\n\n /** Returns the number of allocated nodes. */\n get size(): number {\n return this.count;\n }\n\n /** Reset the pool (all allocations freed, no GC). */\n reset(): void {\n this.count = 0;\n }\n\n // ── AABB ──────────────────────────────────────────────────────────────────\n\n /** Set the AABB bounds for the node at the given index. */\n setAABB(\n index: number,\n minX: number,\n minY: number,\n minZ: number,\n maxX: number,\n maxY: number,\n maxZ: number,\n ): void {\n const off = index * NODE_STRIDE + NODE_AABB_OFFSET;\n this.buffer[off] = minX;\n this.buffer[off + 1] = minY;\n this.buffer[off + 2] = minZ;\n this.buffer[off + 3] = maxX;\n this.buffer[off + 4] = maxY;\n this.buffer[off + 5] = maxZ;\n }\n\n /** Read a single AABB component (0–5) from the node at the given index. */\n getAABB(index: number, component: number): number {\n return this.buffer[index * NODE_STRIDE + NODE_AABB_OFFSET + component] ?? 0;\n }\n\n // ── First-child link ──────────────────────────────────────────────────────\n\n /** Set the first-child index for the node (-1 = leaf). */\n setFirstChild(index: number, childIndex: number): void {\n this.buffer[index * NODE_STRIDE + NODE_FIRST_CHILD_OFFSET] = childIndex;\n }\n\n /** Get the first-child index for the node (-1 = leaf). */\n getFirstChild(index: number): number {\n return this.buffer[index * NODE_STRIDE + NODE_FIRST_CHILD_OFFSET] ?? -1;\n }\n\n // ── Parent link ───────────────────────────────────────────────────────────\n\n /** Set the parent index for the node (-1 = root). */\n setParent(index: number, parentIndex: number): void {\n this.buffer[index * NODE_STRIDE + NODE_PARENT_OFFSET] = parentIndex;\n }\n\n /** Get the parent index for the node (-1 = root). */\n getParent(index: number): number {\n return this.buffer[index * NODE_STRIDE + NODE_PARENT_OFFSET] ?? -1;\n }\n\n // ── Object pointers ───────────────────────────────────────────────────────\n\n /** Return the number of objects stored in the node at the given index. */\n getObjectCount(index: number): number {\n return this.buffer[index * NODE_STRIDE + NODE_OBJECT_COUNT_OFFSET] ?? 0;\n }\n\n /**\n * Append an object index to the node's object list.\n * Throws a RangeError when MAX_OBJECTS_PER_NODE is exceeded.\n */\n addObject(index: number, objectIndex: number): void {\n const countOff = index * NODE_STRIDE + NODE_OBJECT_COUNT_OFFSET;\n const count = this.buffer[countOff] ?? 0;\n if (count >= MAX_OBJECTS_PER_NODE) {\n throw new RangeError(\n `OctreeNodePool.addObject: node ${index} already contains MAX_OBJECTS_PER_NODE (${MAX_OBJECTS_PER_NODE}) objects`,\n );\n }\n this.buffer[index * NODE_STRIDE + NODE_OBJECTS_OFFSET + count] = objectIndex;\n this.buffer[countOff] = count + 1;\n }\n\n /** Return the object index stored at slot `slot` in the given node. */\n getObject(index: number, slot: number): number {\n return this.buffer[index * NODE_STRIDE + NODE_OBJECTS_OFFSET + slot] ?? 0;\n }\n\n /** Reset the object count of the given node to zero (does not zero the slots). */\n clearObjects(index: number): void {\n this.buffer[index * NODE_STRIDE + NODE_OBJECT_COUNT_OFFSET] = 0;\n }\n\n /**\n * Remove a single object index from the node's object list.\n * Uses swap-with-last to avoid shifting. Returns true if found and removed.\n */\n removeObject(index: number, objectIndex: number): boolean {\n const countOff = index * NODE_STRIDE + NODE_OBJECT_COUNT_OFFSET;\n const count = this.buffer[countOff] ?? 0;\n const baseOff = index * NODE_STRIDE + NODE_OBJECTS_OFFSET;\n for (let i = 0; i < count; i++) {\n if (this.buffer[baseOff + i] === objectIndex) {\n // Swap with the last element, then decrement count.\n this.buffer[baseOff + i] = this.buffer[baseOff + count - 1] ?? 0;\n this.buffer[countOff] = count - 1;\n return true;\n }\n }\n return false;\n }\n}\n","import { OctreeNodePool, MAX_OBJECTS_PER_NODE, NODE_STRIDE, NODE_AABB_OFFSET } from './octree-node.js';\nimport { AABBPool, AABB_STRIDE } from './aabb.js';\nimport { rayIntersectsAABB } from './ray.js';\n\n/**\n * Returns true when the AABB stored at `bufOffset` in `buf` overlaps the\n * axis-aligned box defined by the six query scalars.\n */\nfunction aabbOverlapsBox(\n buf: Float32Array,\n bufOffset: number,\n qMinX: number,\n qMinY: number,\n qMinZ: number,\n qMaxX: number,\n qMaxY: number,\n qMaxZ: number,\n): boolean {\n return (\n buf[bufOffset]! <= qMaxX &&\n buf[bufOffset + 3]! >= qMinX &&\n buf[bufOffset + 1]! <= qMaxY &&\n buf[bufOffset + 4]! >= qMinY &&\n buf[bufOffset + 2]! <= qMaxZ &&\n buf[bufOffset + 5]! >= qMinZ\n );\n}\n\n/**\n * Octree with insertion and automatic subdivision.\n *\n * - When a leaf node's object count reaches MAX_OBJECTS_PER_NODE it is\n * subdivided into 8 axis-aligned child octants.\n * - An inserted AABB that fits entirely within a child octant is pushed down.\n * - An AABB that straddles a boundary is kept in the nearest ancestor that\n * fully contains it.\n */\nexport class Octree {\n private readonly nodePool: OctreeNodePool;\n private readonly aabbPool: AABBPool;\n private readonly root: number;\n /** Tracks which node each object (by AABBPool index) is currently stored in. */\n private readonly objectNodeMap: Map<number, number> = new Map();\n /** Pre-allocated traversal stack reused across raycast calls to avoid GC pressure. */\n private readonly _stack: number[] = [];\n\n constructor(nodePool: OctreeNodePool, aabbPool: AABBPool) {\n this.nodePool = nodePool;\n this.aabbPool = aabbPool;\n this.root = nodePool.allocate();\n }\n\n /** The index of the root node in the underlying OctreeNodePool. */\n get rootIndex(): number {\n return this.root;\n }\n\n /** Set the world-space AABB that the root node covers. */\n setBounds(\n minX: number,\n minY: number,\n minZ: number,\n maxX: number,\n maxY: number,\n maxZ: number,\n ): void {\n this.nodePool.setAABB(this.root, minX, minY, minZ, maxX, maxY, maxZ);\n }\n\n /** Insert the AABB at `objectIndex` (in the AABBPool) into the tree. */\n insert(objectIndex: number): void {\n this.insertIntoNode(this.root, objectIndex);\n }\n\n /**\n * Update the AABB at `objectIndex` with new bounds and reposition it in the\n * tree without rebuilding. If the new bounds still fit in the current node\n * the object stays there; otherwise it is removed and re-inserted from the\n * lowest ancestor whose bounds fully contain the new AABB.\n */\n update(\n objectIndex: number,\n newMinX: number,\n newMinY: number,\n newMinZ: number,\n newMaxX: number,\n newMaxY: number,\n newMaxZ: number,\n ): void {\n const np = this.nodePool;\n const ap = this.aabbPool;\n\n // 1. Update the AABB data.\n ap.set(objectIndex, newMinX, newMinY, newMinZ, newMaxX, newMaxY, newMaxZ);\n\n // 2. Find current node.\n const currentNode = this.objectNodeMap.get(objectIndex);\n if (currentNode === undefined) return;\n\n // 3. If the new bounds still fit in the current node, nothing to move.\n if (this.fitsInNode(objectIndex, currentNode)) return;\n\n // 4. Remove from current node.\n np.removeObject(currentNode, objectIndex);\n this.objectNodeMap.delete(objectIndex);\n\n // 5. Walk up to find the lowest ancestor that fully contains the new bounds.\n let ancestorNode = np.getParent(currentNode);\n while (ancestorNode !== -1 && !this.fitsInNode(objectIndex, ancestorNode)) {\n ancestorNode = np.getParent(ancestorNode);\n }\n // If no ancestor fits (shouldn't happen for a well-formed tree), use root.\n if (ancestorNode === -1) ancestorNode = this.root;\n\n // 6. Re-insert downward from the ancestor.\n this.insertIntoNode(ancestorNode, objectIndex);\n }\n\n /**\n * Cast a ray through the octree and return the closest intersecting object.\n *\n * Uses an iterative stack traversal (pre-allocated, no recursion) to avoid\n * GC pressure. Only descends into child nodes whose AABBs are intersected by\n * the ray, providing efficient pruning of non-intersecting subtrees.\n *\n * @param rayBuf Float32Array containing the ray data [ox,oy,oz,dx,dy,dz].\n * @param rayOffset Element offset (in floats) within `rayBuf` to the ray's origin.\n * @returns The closest `{ objectIndex, t }` hit, or `null` if nothing is hit.\n */\n raycast(\n rayBuf: Float32Array,\n rayOffset: number,\n ): { objectIndex: number; t: number } | null {\n const np = this.nodePool;\n // Access the underlying flat buffers directly for zero-GC intersection tests.\n const npBuf = (np as unknown as { buffer: Float32Array }).buffer;\n const apBuf = (this.aabbPool as unknown as { buffer: Float32Array }).buffer;\n\n // Early-out: test the root AABB before touching any object data.\n if (rayIntersectsAABB(rayBuf, rayOffset, npBuf, this.root * NODE_STRIDE + NODE_AABB_OFFSET) < 0) {\n return null;\n }\n\n // Iterative DFS using the pre-allocated stack.\n this._stack.length = 0;\n this._stack.push(this.root);\n\n let closestT = Infinity;\n let closestIndex = -1;\n\n while (this._stack.length > 0) {\n const nodeIdx = this._stack.pop()!;\n\n // Test every object stored at this node level.\n const objCount = np.getObjectCount(nodeIdx);\n for (let i = 0; i < objCount; i++) {\n const objIdx = np.getObject(nodeIdx, i);\n const t = rayIntersectsAABB(rayBuf, rayOffset, apBuf, objIdx * AABB_STRIDE);\n if (t >= 0 && t < closestT) {\n closestT = t;\n closestIndex = objIdx;\n }\n }\n\n // Push only children whose AABB the ray actually intersects.\n const firstChild = np.getFirstChild(nodeIdx);\n if (firstChild !== -1) {\n for (let i = 0; i < 8; i++) {\n const childIdx = firstChild + i;\n if (rayIntersectsAABB(rayBuf, rayOffset, npBuf, childIdx * NODE_STRIDE + NODE_AABB_OFFSET) >= 0) {\n this._stack.push(childIdx);\n }\n }\n }\n }\n\n if (closestIndex === -1) return null;\n return { objectIndex: closestIndex, t: closestT };\n }\n\n /**\n * Query the octree for all objects whose AABB overlaps the given axis-aligned\n * box region and return their indices (in the AABBPool).\n *\n * Uses the same pre-allocated iterative stack as `raycast` to avoid GC\n * pressure. Descends only into child nodes whose AABBs overlap the query box,\n * pruning non-intersecting subtrees.\n *\n * @param minX Minimum X of the query box.\n * @param minY Minimum Y of the query box.\n * @param minZ Minimum Z of the query box.\n * @param maxX Maximum X of the query box.\n * @param maxY Maximum Y of the query box.\n * @param maxZ Maximum Z of the query box.\n * @returns Array of AABBPool indices for every object that overlaps the box.\n */\n queryBox(\n minX: number,\n minY: number,\n minZ: number,\n maxX: number,\n maxY: number,\n maxZ: number,\n ): number[] {\n const np = this.nodePool;\n const npBuf = (np as unknown as { buffer: Float32Array }).buffer;\n const apBuf = (this.aabbPool as unknown as { buffer: Float32Array }).buffer;\n\n const results: number[] = [];\n\n // Early-out: if the query box doesn't overlap the root, nothing to do.\n if (!aabbOverlapsBox(npBuf, this.root * NODE_STRIDE + NODE_AABB_OFFSET, minX, minY, minZ, maxX, maxY, maxZ)) {\n return results;\n }\n\n // Iterative DFS using the pre-allocated stack.\n this._stack.length = 0;\n this._stack.push(this.root);\n\n while (this._stack.length > 0) {\n const nodeIdx = this._stack.pop()!;\n\n // Test every object stored at this node level.\n const objCount = np.getObjectCount(nodeIdx);\n for (let i = 0; i < objCount; i++) {\n const objIdx = np.getObject(nodeIdx, i);\n if (aabbOverlapsBox(apBuf, objIdx * AABB_STRIDE, minX, minY, minZ, maxX, maxY, maxZ)) {\n results.push(objIdx);\n }\n }\n\n // Push only children whose AABB overlaps the query box.\n const firstChild = np.getFirstChild(nodeIdx);\n if (firstChild !== -1) {\n for (let i = 0; i < 8; i++) {\n const childIdx = firstChild + i;\n if (aabbOverlapsBox(npBuf, childIdx * NODE_STRIDE + NODE_AABB_OFFSET, minX, minY, minZ, maxX, maxY, maxZ)) {\n this._stack.push(childIdx);\n }\n }\n }\n }\n\n return results;\n }\n\n // ── Private helpers ──────────────────────────────────────────────────────\n\n private insertIntoNode(nodeIdx: number, objectIndex: number): void {\n const np = this.nodePool;\n const firstChild = np.getFirstChild(nodeIdx);\n\n if (firstChild !== -1) {\n // Internal node: try to push the object into a fitting child octant.\n for (let i = 0; i < 8; i++) {\n if (this.fitsInNode(objectIndex, firstChild + i)) {\n this.insertIntoNode(firstChild + i, objectIndex);\n return;\n }\n }\n // Object straddles one or more boundaries – keep it at this level.\n np.addObject(nodeIdx, objectIndex);\n this.objectNodeMap.set(objectIndex, nodeIdx);\n return;\n }\n\n // Leaf node.\n const count = np.getObjectCount(nodeIdx);\n if (count < MAX_OBJECTS_PER_NODE) {\n np.addObject(nodeIdx, objectIndex);\n this.objectNodeMap.set(objectIndex, nodeIdx);\n } else {\n // Capacity reached: subdivide, then retry the insertion.\n this.subdivide(nodeIdx);\n this.insertIntoNode(nodeIdx, objectIndex);\n }\n }\n\n /** Split a leaf node into 8 children and redistribute its objects. */\n private subdivide(nodeIdx: number): void {\n const np = this.nodePool;\n\n const minX = np.getAABB(nodeIdx, 0);\n const minY = np.getAABB(nodeIdx, 1);\n const minZ = np.getAABB(nodeIdx, 2);\n const maxX = np.getAABB(nodeIdx, 3);\n const maxY = np.getAABB(nodeIdx, 4);\n const maxZ = np.getAABB(nodeIdx, 5);\n\n const midX = (minX + maxX) / 2;\n const midY = (minY + maxY) / 2;\n const midZ = (minZ + maxZ) / 2;\n\n // Allocate 8 consecutive child nodes (guarantees contiguous indices).\n const firstChild = np.allocate();\n for (let i = 1; i < 8; i++) {\n np.allocate();\n }\n np.setFirstChild(nodeIdx, firstChild);\n\n // Assign each child its octant AABB.\n // Bit decomposition: bit-0 → X half, bit-1 → Y half, bit-2 → Z half.\n for (let i = 0; i < 8; i++) {\n const childIdx = firstChild + i;\n const cMinX = (i & 1) === 0 ? minX : midX;\n const cMaxX = (i & 1) === 0 ? midX : maxX;\n const cMinY = (i & 2) === 0 ? minY : midY;\n const cMaxY = (i & 2) === 0 ? midY : maxY;\n const cMinZ = (i & 4) === 0 ? minZ : midZ;\n const cMaxZ = (i & 4) === 0 ? midZ : maxZ;\n np.setAABB(childIdx, cMinX, cMinY, cMinZ, cMaxX, cMaxY, cMaxZ);\n np.setParent(childIdx, nodeIdx);\n }\n\n // Redistribute the existing objects.\n const count = np.getObjectCount(nodeIdx);\n const saved: number[] = [];\n for (let i = 0; i < count; i++) {\n saved.push(np.getObject(nodeIdx, i));\n }\n np.clearObjects(nodeIdx);\n\n for (const obj of saved) {\n // Use insertIntoNode so objectNodeMap stays consistent.\n this.objectNodeMap.delete(obj);\n this.insertIntoNode(nodeIdx, obj);\n }\n }\n\n /** Returns true when the AABB at `objectIndex` is fully contained by `nodeIdx`. */\n private fitsInNode(objectIndex: number, nodeIdx: number): boolean {\n const np = this.nodePool;\n const ap = this.aabbPool;\n return (\n ap.get(objectIndex, 0) >= np.getAABB(nodeIdx, 0) &&\n ap.get(objectIndex, 1) >= np.getAABB(nodeIdx, 1) &&\n ap.get(objectIndex, 2) >= np.getAABB(nodeIdx, 2) &&\n ap.get(objectIndex, 3) <= np.getAABB(nodeIdx, 3) &&\n ap.get(objectIndex, 4) <= np.getAABB(nodeIdx, 4) &&\n ap.get(objectIndex, 5) <= np.getAABB(nodeIdx, 5)\n );\n }\n}\n","/**\n * LiDAR Web Worker\n *\n * Handles heavy raycasting (LiDAR sweep logic) off the main thread.\n *\n * Protocol\n * --------\n * 1. Main thread sends one `LidarInitMessage` to set up shared buffers and\n * world bounds. The worker replies with `{ type: 'ready' }`.\n *\n * 2. For each sweep the main thread:\n * a. Writes up-to-date object AABB data into `aabbsSab`\n * (6 floats per object: minX, minY, minZ, maxX, maxY, maxZ).\n * b. Sends a `LidarSweepMessage` with the current `objectCount`.\n *\n * 3. The worker reads the transforms, inserts/updates objects in its local\n * Octree, casts every ray from `raysSab`, and writes results to\n * `resultsSab` (2 floats per ray: objectIndex, t; –1/–1 for a miss).\n * It then replies with `{ type: 'done', rayCount }`.\n *\n * SharedArrayBuffer layout\n * ------------------------\n * aabbsSab : Float32Array – objectCapacity × AABB_STRIDE (6) floats\n * nodesSab : Float32Array – nodeCapacity × NODE_STRIDE floats\n * raysSab : Float32Array – rayCount × RAY_STRIDE (6) floats\n * resultsSab: Float32Array – rayCount × 2 floats\n * [objectIndex (float), t (float)] per ray\n */\n\nimport { OctreeNodePool } from './octree-node.js';\nimport { AABBPool } from './aabb.js';\nimport { RAY_STRIDE } from './ray.js';\nimport { Octree } from './octree.js';\n\n// ── Public message types ──────────────────────────────────────────────────────\n\n/** Sent once from the main thread to initialise the shared buffers. */\nexport interface LidarInitMessage {\n type: 'init';\n /** SharedArrayBuffer for AABB data (objectCapacity × 6 floats). Written by the main thread. */\n aabbsSab: SharedArrayBuffer;\n /** SharedArrayBuffer for Octree node data (nodeCapacity × NODE_STRIDE floats). Managed by the worker. */\n nodesSab: SharedArrayBuffer;\n /** SharedArrayBuffer for ray data (rayCount × 6 floats). Written by the main thread (or set once). */\n raysSab: SharedArrayBuffer;\n /** SharedArrayBuffer for raycast results (rayCount × 2 floats). Written by the worker. */\n resultsSab: SharedArrayBuffer;\n /** Maximum number of objects the AABBPool can hold. */\n objectCapacity: number;\n /** Maximum number of Octree nodes the NodePool can hold. */\n nodeCapacity: number;\n /** Number of rays in the LiDAR sweep. */\n rayCount: number;\n /** World-space bounds for the root octree node. */\n worldMinX: number;\n worldMinY: number;\n worldMinZ: number;\n worldMaxX: number;\n worldMaxY: number;\n worldMaxZ: number;\n}\n\n/** Sent each frame to trigger a LiDAR sweep. */\nexport interface LidarSweepMessage {\n type: 'sweep';\n /** Number of active objects whose AABB data has been written to `aabbsSab`. */\n objectCount: number;\n}\n\nexport type LidarWorkerInMessage = LidarInitMessage | LidarSweepMessage;\n\n/** Posted by the worker after initialisation succeeds. */\nexport interface LidarReadyMessage {\n type: 'ready';\n}\n\n/** Posted by the worker after a sweep finishes. */\nexport interface LidarDoneMessage {\n type: 'done';\n /** Number of rays that were cast (equals the `rayCount` given in `init`). */\n rayCount: number;\n}\n\nexport type LidarWorkerOutMessage = LidarReadyMessage | LidarDoneMessage;\n\n// ── Processor (pure logic, no worker-global bindings) ────────────────────────\n\n/**\n * Stateful LiDAR sweep processor.\n *\n * Exported for direct use in tests and advanced integrations. The bottom of\n * this module wires it to the Web Worker global scope automatically when the\n * script runs inside a worker.\n */\nexport function createLidarProcessor(): {\n init(msg: LidarInitMessage): LidarReadyMessage;\n sweep(msg: LidarSweepMessage): LidarDoneMessage;\n} {\n let octree: Octree | null = null;\n let aabbPool: AABBPool | null = null;\n let raysBuf: Float32Array | null = null;\n let resultsBuf: Float32Array | null = null;\n let totalRayCount = 0;\n /** Tracks how many objects have been inserted into the local octree. */\n let insertedObjectCount = 0;\n\n return {\n /** Initialise shared buffers and create the Octree. */\n init(msg: LidarInitMessage): LidarReadyMessage {\n const nodePool = new OctreeNodePool(msg.nodeCapacity, msg.nodesSab);\n aabbPool = new AABBPool(msg.objectCapacity, msg.aabbsSab);\n raysBuf = new Float32Array(msg.raysSab);\n resultsBuf = new Float32Array(msg.resultsSab);\n totalRayCount = msg.rayCount;\n insertedObjectCount = 0;\n\n octree = new Octree(nodePool, aabbPool);\n octree.setBounds(\n msg.worldMinX, msg.worldMinY, msg.worldMinZ,\n msg.worldMaxX, msg.worldMaxY, msg.worldMaxZ,\n );\n\n return { type: 'ready' };\n },\n\n /**\n * Read current object AABB data from the shared buffer, update the Octree,\n * cast every sweep ray, and write results to the results buffer.\n */\n sweep(msg: LidarSweepMessage): LidarDoneMessage {\n if (octree === null || aabbPool === null || raysBuf === null || resultsBuf === null) {\n throw new Error('LidarProcessor: sweep called before init');\n }\n\n const objectCount = msg.objectCount;\n\n // Ensure the pool's allocation count reflects all active objects so that\n // the pool's `size` getter stays accurate (the buffer memory already exists\n // in the SAB – allocate() only increments the internal counter).\n while (aabbPool.size < objectCount) {\n aabbPool.allocate();\n }\n\n // Insert new objects or reposition existing ones in the octree.\n for (let i = 0; i < objectCount; i++) {\n const minX = aabbPool.get(i, 0);\n const minY = aabbPool.get(i, 1);\n const minZ = aabbPool.get(i, 2);\n const maxX = aabbPool.get(i, 3);\n const maxY = aabbPool.get(i, 4);\n const maxZ = aabbPool.get(i, 5);\n\n if (i < insertedObjectCount) {\n // Already in the tree – reposition without full rebuild.\n octree.update(i, minX, minY, minZ, maxX, maxY, maxZ);\n } else {\n // First appearance: insert using the bounds already in the SAB.\n octree.insert(i);\n insertedObjectCount++;\n }\n }\n\n // LiDAR sweep: cast each ray and write the closest hit (or miss) to\n // the results buffer. Layout: [objectIndex, t] per ray (–1/–1 = miss).\n for (let r = 0; r < totalRayCount; r++) {\n const hit = octree.raycast(raysBuf, r * RAY_STRIDE);\n if (hit !== null) {\n resultsBuf[r * 2] = hit.objectIndex;\n resultsBuf[r * 2 + 1] = hit.t;\n } else {\n resultsBuf[r * 2] = -1;\n resultsBuf[r * 2 + 1] = -1;\n }\n }\n\n return { type: 'done', rayCount: totalRayCount };\n },\n };\n}\n\n// ── Web Worker binding ────────────────────────────────────────────────────────\n// Automatically wire the processor to the worker's global message handler when\n// this script is loaded inside a Web Worker context (browser or Node.js worker).\n\ntype WorkerGlobalSelf = {\n onmessage: ((event: { data: LidarWorkerInMessage }) => void) | null;\n postMessage(data: LidarWorkerOutMessage): void;\n};\n\n// `postMessage` is available on the worker global but not in a regular Node.js\n// module context, so check for it before binding.\nconst globalScope = globalThis as Record<string, unknown>;\nif (typeof globalScope['postMessage'] === 'function' && typeof globalScope['onmessage'] !== 'undefined') {\n const workerSelf = globalThis as unknown as WorkerGlobalSelf;\n const processor = createLidarProcessor();\n\n workerSelf.onmessage = (event) => {\n const msg = event.data;\n if (msg.type === 'init') {\n workerSelf.postMessage(processor.init(msg));\n } else if (msg.type === 'sweep') {\n workerSelf.postMessage(processor.sweep(msg));\n }\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/aabb.ts","../src/ray.ts","../src/object-pool.ts","../src/flat-math.ts","../src/octree-node.ts","../src/octree.ts","../src/lidar-worker.ts"],"sourcesContent":["export { AABBPool, aabbIntersects, aabbExpand, aabbMerge, AABB_STRIDE } from './aabb.js';\nexport { RayPool, rayIntersectsAABB, RAY_STRIDE } from './ray.js';\nexport { ObjectPool } from './object-pool.js';\nexport { vec3Dot, vec3DistanceSq, vec3Distance, vec3Cross } from './flat-math.js';\nexport {\n OctreeNodePool,\n NODE_STRIDE,\n NODE_AABB_OFFSET,\n NODE_FIRST_CHILD_OFFSET,\n NODE_PARENT_OFFSET,\n NODE_OBJECT_COUNT_OFFSET,\n NODE_OBJECTS_OFFSET,\n MAX_OBJECTS_PER_NODE,\n} from './octree-node.js';\nexport { Octree } from './octree.js';\nexport {\n createLidarProcessor,\n} from './lidar-worker.js';\nexport type {\n LidarInitMessage,\n LidarSweepMessage,\n LidarWorkerInMessage,\n LidarReadyMessage,\n LidarDoneMessage,\n LidarWorkerOutMessage,\n} from './lidar-worker.js';\n","/**\n * AABB (Axis-Aligned Bounding Box) stored as a flat Float32Array slice.\n * Layout: [minX, minY, minZ, maxX, maxY, maxZ]\n */\nexport const AABB_STRIDE = 6;\n\n/**\n * A pool-backed, zero-GC AABB store.\n * Each AABB occupies AABB_STRIDE floats in the underlying buffer.\n *\n * When a `SharedArrayBuffer` is provided, the pool's data is accessible from\n * multiple threads (e.g. a Web Worker) with no copying.\n */\nexport class AABBPool {\n private readonly buffer: Float32Array;\n private count: number = 0;\n\n constructor(capacity: number, sharedBuffer?: SharedArrayBuffer) {\n this.buffer = sharedBuffer\n ? new Float32Array(sharedBuffer, 0, capacity * AABB_STRIDE)\n : new Float32Array(capacity * AABB_STRIDE);\n }\n\n /**\n * Create an AABBPool backed by a new `SharedArrayBuffer`.\n * Both the pool and the underlying `SharedArrayBuffer` are returned so the\n * caller can transfer the buffer to a `Worker` via `postMessage`.\n */\n static createShared(capacity: number): { pool: AABBPool; sab: SharedArrayBuffer } {\n const sab = new SharedArrayBuffer(capacity * AABB_STRIDE * Float32Array.BYTES_PER_ELEMENT);\n return { pool: new AABBPool(capacity, sab), sab };\n }\n\n /** Allocate a new AABB slot and return its index. */\n allocate(): number {\n const index = this.count;\n this.count += 1;\n return index;\n }\n\n /** Set the bounds of an AABB at the given index. */\n set(\n index: number,\n minX: number,\n minY: number,\n minZ: number,\n maxX: number,\n maxY: number,\n maxZ: number,\n ): void {\n const offset = index * AABB_STRIDE;\n this.buffer[offset] = minX;\n this.buffer[offset + 1] = minY;\n this.buffer[offset + 2] = minZ;\n this.buffer[offset + 3] = maxX;\n this.buffer[offset + 4] = maxY;\n this.buffer[offset + 5] = maxZ;\n }\n\n /** Read a single component value from the buffer. */\n get(index: number, component: number): number {\n return this.buffer[index * AABB_STRIDE + component] ?? 0;\n }\n\n /** Returns the number of allocated AABBs. */\n get size(): number {\n return this.count;\n }\n\n /** Reset the pool (all allocations freed, no GC). */\n reset(): void {\n this.count = 0;\n }\n}\n\n/**\n * Expand AABB `a` in-place so it also fully contains AABB `b`.\n */\nexport function aabbExpand(pool: AABBPool, a: number, b: number): void {\n const buf = (pool as unknown as { buffer: Float32Array }).buffer;\n const aOff = a * AABB_STRIDE;\n const bOff = b * AABB_STRIDE;\n buf[aOff] = Math.min(buf[aOff] ?? 0, buf[bOff] ?? 0);\n buf[aOff + 1] = Math.min(buf[aOff + 1] ?? 0, buf[bOff + 1] ?? 0);\n buf[aOff + 2] = Math.min(buf[aOff + 2] ?? 0, buf[bOff + 2] ?? 0);\n buf[aOff + 3] = Math.max(buf[aOff + 3] ?? 0, buf[bOff + 3] ?? 0);\n buf[aOff + 4] = Math.max(buf[aOff + 4] ?? 0, buf[bOff + 4] ?? 0);\n buf[aOff + 5] = Math.max(buf[aOff + 5] ?? 0, buf[bOff + 5] ?? 0);\n}\n\n/**\n * Merge AABBs `a` and `b` into `dest` (a pre-allocated slot), storing their union.\n */\nexport function aabbMerge(pool: AABBPool, dest: number, a: number, b: number): void {\n const buf = (pool as unknown as { buffer: Float32Array }).buffer;\n const aOff = a * AABB_STRIDE;\n const bOff = b * AABB_STRIDE;\n const dOff = dest * AABB_STRIDE;\n buf[dOff] = Math.min(buf[aOff] ?? 0, buf[bOff] ?? 0);\n buf[dOff + 1] = Math.min(buf[aOff + 1] ?? 0, buf[bOff + 1] ?? 0);\n buf[dOff + 2] = Math.min(buf[aOff + 2] ?? 0, buf[bOff + 2] ?? 0);\n buf[dOff + 3] = Math.max(buf[aOff + 3] ?? 0, buf[bOff + 3] ?? 0);\n buf[dOff + 4] = Math.max(buf[aOff + 4] ?? 0, buf[bOff + 4] ?? 0);\n buf[dOff + 5] = Math.max(buf[aOff + 5] ?? 0, buf[bOff + 5] ?? 0);\n}\n\n/**\n * Test whether two AABBs (by index in the same pool) intersect.\n */\nexport function aabbIntersects(pool: AABBPool, a: number, b: number): boolean {\n const aOff = a * AABB_STRIDE;\n const bOff = b * AABB_STRIDE;\n // Access via get() to stay safe with bounds, but direct buffer access is\n // equally valid when performance is critical.\n const buf = (pool as unknown as { buffer: Float32Array }).buffer;\n return (\n (buf[aOff] ?? 0) <= (buf[bOff + 3] ?? 0) &&\n (buf[aOff + 3] ?? 0) >= (buf[bOff] ?? 0) &&\n (buf[aOff + 1] ?? 0) <= (buf[bOff + 4] ?? 0) &&\n (buf[aOff + 4] ?? 0) >= (buf[bOff + 1] ?? 0) &&\n (buf[aOff + 2] ?? 0) <= (buf[bOff + 5] ?? 0) &&\n (buf[aOff + 5] ?? 0) >= (buf[bOff + 2] ?? 0)\n );\n}\n","/**\n * Ray stored as origin + direction (unit vector).\n * Layout: [ox, oy, oz, dx, dy, dz]\n */\nexport const RAY_STRIDE = 6;\n\n/** A pool-backed, zero-GC Ray store.\n *\n * When a `SharedArrayBuffer` is provided, the pool's data is accessible from\n * multiple threads (e.g. a Web Worker) with no copying.\n */\nexport class RayPool {\n private readonly buffer: Float32Array;\n private count: number = 0;\n\n constructor(capacity: number, sharedBuffer?: SharedArrayBuffer) {\n this.buffer = sharedBuffer\n ? new Float32Array(sharedBuffer, 0, capacity * RAY_STRIDE)\n : new Float32Array(capacity * RAY_STRIDE);\n }\n\n /**\n * Create a RayPool backed by a new `SharedArrayBuffer`.\n * Both the pool and the underlying `SharedArrayBuffer` are returned so the\n * caller can transfer the buffer to a `Worker` via `postMessage`.\n */\n static createShared(capacity: number): { pool: RayPool; sab: SharedArrayBuffer } {\n const sab = new SharedArrayBuffer(capacity * RAY_STRIDE * Float32Array.BYTES_PER_ELEMENT);\n return { pool: new RayPool(capacity, sab), sab };\n }\n\n /** Allocate a new Ray slot and return its index. */\n allocate(): number {\n const index = this.count;\n this.count += 1;\n return index;\n }\n\n /** Set origin and direction of a ray at the given index. */\n set(\n index: number,\n ox: number,\n oy: number,\n oz: number,\n dx: number,\n dy: number,\n dz: number,\n ): void {\n const offset = index * RAY_STRIDE;\n this.buffer[offset] = ox;\n this.buffer[offset + 1] = oy;\n this.buffer[offset + 2] = oz;\n this.buffer[offset + 3] = dx;\n this.buffer[offset + 4] = dy;\n this.buffer[offset + 5] = dz;\n }\n\n /** Read a single component value from the buffer. */\n get(index: number, component: number): number {\n return this.buffer[index * RAY_STRIDE + component] ?? 0;\n }\n\n /** Returns the number of allocated Rays. */\n get size(): number {\n return this.count;\n }\n\n /** Reset the pool (all allocations freed, no GC). */\n reset(): void {\n this.count = 0;\n }\n}\n\n/**\n * Optimized branchless slab-method ray–AABB intersection test.\n *\n * Operates purely on flat Float32Arrays with no per-axis direction branches.\n * When a direction component is zero, IEEE 754 produces ±Infinity for the\n * reciprocal, which propagates correctly through the min/max slab computation:\n * - origin inside the slab → [−∞, +∞] interval (no constraint)\n * - origin outside the slab → either [+∞, +∞] or [−∞, −∞] → forces a miss\n *\n * Returns the parametric hit distance `t` (>= 0) if the ray intersects the\n * AABB, or -1 if there is no intersection (miss or box entirely behind origin).\n */\nexport function rayIntersectsAABB(\n rayBuf: Float32Array,\n rayOffset: number,\n aabbBuf: Float32Array,\n aabbOffset: number,\n): number {\n const ox = rayBuf[rayOffset] ?? 0;\n const oy = rayBuf[rayOffset + 1] ?? 0;\n const oz = rayBuf[rayOffset + 2] ?? 0;\n const idx = 1 / (rayBuf[rayOffset + 3] ?? 0);\n const idy = 1 / (rayBuf[rayOffset + 4] ?? 0);\n const idz = 1 / (rayBuf[rayOffset + 5] ?? 0);\n\n const minX = aabbBuf[aabbOffset] ?? 0;\n const minY = aabbBuf[aabbOffset + 1] ?? 0;\n const minZ = aabbBuf[aabbOffset + 2] ?? 0;\n const maxX = aabbBuf[aabbOffset + 3] ?? 0;\n const maxY = aabbBuf[aabbOffset + 4] ?? 0;\n const maxZ = aabbBuf[aabbOffset + 5] ?? 0;\n\n // Branchless slab method: per-axis near/far t values.\n const t1x = (minX - ox) * idx;\n const t2x = (maxX - ox) * idx;\n const t1y = (minY - oy) * idy;\n const t2y = (maxY - oy) * idy;\n const t1z = (minZ - oz) * idz;\n const t2z = (maxZ - oz) * idz;\n\n const tmin = Math.max(Math.min(t1x, t2x), Math.min(t1y, t2y), Math.min(t1z, t2z));\n const tmax = Math.min(Math.max(t1x, t2x), Math.max(t1y, t2y), Math.max(t1z, t2z));\n\n if (tmax < 0 || !(tmin <= tmax)) return -1;\n return tmin >= 0 ? tmin : tmax;\n}\n","/**\n * A generic Object Pool that manages a fixed set of integer indices.\n *\n * Pre-allocates a free-list on construction so that acquire() and release()\n * operate without any heap allocation.\n */\nexport class ObjectPool {\n private readonly freeList: Int32Array;\n private top: number;\n\n /** Total number of slots in the pool. */\n readonly capacity: number;\n\n constructor(capacity: number) {\n this.capacity = capacity;\n this.freeList = new Int32Array(capacity);\n // Fill free-list in reverse so that the first acquire() returns index 0.\n for (let i = 0; i < capacity; i++) {\n this.freeList[i] = capacity - 1 - i;\n }\n this.top = capacity;\n }\n\n /** Number of indices currently available for acquisition. */\n get available(): number {\n return this.top;\n }\n\n /**\n * Acquire a free index from the pool.\n * Returns the index, or `null` when the pool is exhausted.\n */\n acquire(): number | null {\n if (this.top === 0) {\n return null;\n }\n this.top -= 1;\n return this.freeList[this.top] as number;\n }\n\n /**\n * Release a previously acquired index back to the pool.\n * Throws if the index is out of range or the pool is already full.\n */\n release(index: number): void {\n if (index < 0 || index >= this.capacity) {\n throw new RangeError(`ObjectPool.release: index ${index} is out of range [0, ${this.capacity})`);\n }\n if (this.top >= this.capacity) {\n throw new RangeError('ObjectPool.release: pool is already at full capacity');\n }\n this.freeList[this.top] = index;\n this.top += 1;\n }\n}\n","/**\n * Flat Array Math Utilities\n *\n * Zero-allocation Vec3 math that reads directly from a Float32Array\n * given a start index, without creating any Vector3 objects.\n *\n * All index parameters (`i`, `j`, `outOffset`) are raw flat-array offsets.\n */\n\n/**\n * Compute the dot product of two Vec3s stored in `buf` at offsets `i` and `j`.\n */\nexport function vec3Dot(buf: Float32Array, i: number, j: number): number {\n return (buf[i] ?? 0) * (buf[j] ?? 0) +\n (buf[i + 1] ?? 0) * (buf[j + 1] ?? 0) +\n (buf[i + 2] ?? 0) * (buf[j + 2] ?? 0);\n}\n\n/**\n * Compute the squared distance between two Vec3s stored in `buf` at offsets `i` and `j`.\n */\nexport function vec3DistanceSq(buf: Float32Array, i: number, j: number): number {\n const dx = (buf[i] ?? 0) - (buf[j] ?? 0);\n const dy = (buf[i + 1] ?? 0) - (buf[j + 1] ?? 0);\n const dz = (buf[i + 2] ?? 0) - (buf[j + 2] ?? 0);\n return dx * dx + dy * dy + dz * dz;\n}\n\n/**\n * Compute the Euclidean distance between two Vec3s stored in `buf` at offsets `i` and `j`.\n */\nexport function vec3Distance(buf: Float32Array, i: number, j: number): number {\n return Math.sqrt(vec3DistanceSq(buf, i, j));\n}\n\n/**\n * Compute the cross product of two Vec3s in `buf` at offsets `i` and `j`,\n * and write the result into `out` starting at `outOffset`.\n */\nexport function vec3Cross(\n out: Float32Array,\n outOffset: number,\n buf: Float32Array,\n i: number,\n j: number,\n): void {\n const ax = buf[i] ?? 0;\n const ay = buf[i + 1] ?? 0;\n const az = buf[i + 2] ?? 0;\n const bx = buf[j] ?? 0;\n const by = buf[j + 1] ?? 0;\n const bz = buf[j + 2] ?? 0;\n out[outOffset] = ay * bz - az * by;\n out[outOffset + 1] = az * bx - ax * bz;\n out[outOffset + 2] = ax * by - ay * bx;\n}\n","/**\n * Octree node stored as a flat Float32Array slice.\n *\n * Layout per node (NODE_STRIDE floats):\n * [0..5] – AABB (minX, minY, minZ, maxX, maxY, maxZ)\n * [6] – firstChild index (-1 = leaf / no children)\n * [7] – parent index (-1 = root)\n * [8] – object count\n * [9..] – object indices (MAX_OBJECTS_PER_NODE slots)\n */\n\nexport const NODE_AABB_OFFSET = 0;\nexport const NODE_FIRST_CHILD_OFFSET = 6;\nexport const NODE_PARENT_OFFSET = 7;\nexport const NODE_OBJECT_COUNT_OFFSET = 8;\nexport const NODE_OBJECTS_OFFSET = 9;\nexport const MAX_OBJECTS_PER_NODE = 64;\nexport const NODE_STRIDE = NODE_OBJECTS_OFFSET + MAX_OBJECTS_PER_NODE;\n\n/**\n * A pool-backed, zero-GC Octree node store.\n * Each node occupies NODE_STRIDE floats in the underlying Float32Array buffer.\n *\n * When a `SharedArrayBuffer` is provided, the pool's data is accessible from\n * multiple threads (e.g. a Web Worker) with no copying.\n */\nexport class OctreeNodePool {\n private readonly buffer: Float32Array;\n private count: number = 0;\n\n constructor(capacity: number, sharedBuffer?: SharedArrayBuffer) {\n this.buffer = sharedBuffer\n ? new Float32Array(sharedBuffer, 0, capacity * NODE_STRIDE)\n : new Float32Array(capacity * NODE_STRIDE);\n }\n\n /**\n * Create an OctreeNodePool backed by a new `SharedArrayBuffer`.\n * Both the pool and the underlying `SharedArrayBuffer` are returned so the\n * caller can transfer the buffer to a `Worker` via `postMessage`.\n */\n static createShared(capacity: number): { pool: OctreeNodePool; sab: SharedArrayBuffer } {\n const sab = new SharedArrayBuffer(capacity * NODE_STRIDE * Float32Array.BYTES_PER_ELEMENT);\n return { pool: new OctreeNodePool(capacity, sab), sab };\n }\n\n /** Allocate a new node slot, initialise sentinel values, and return its index. */\n allocate(): number {\n const index = this.count;\n const offset = index * NODE_STRIDE;\n // Initialise sentinel values for child/parent links and object count.\n this.buffer[offset + NODE_FIRST_CHILD_OFFSET] = -1;\n this.buffer[offset + NODE_PARENT_OFFSET] = -1;\n this.buffer[offset + NODE_OBJECT_COUNT_OFFSET] = 0;\n this.count += 1;\n return index;\n }\n\n /** Returns the number of allocated nodes. */\n get size(): number {\n return this.count;\n }\n\n /** Reset the pool (all allocations freed, no GC). */\n reset(): void {\n this.count = 0;\n }\n\n // ── AABB ──────────────────────────────────────────────────────────────────\n\n /** Set the AABB bounds for the node at the given index. */\n setAABB(\n index: number,\n minX: number,\n minY: number,\n minZ: number,\n maxX: number,\n maxY: number,\n maxZ: number,\n ): void {\n const off = index * NODE_STRIDE + NODE_AABB_OFFSET;\n this.buffer[off] = minX;\n this.buffer[off + 1] = minY;\n this.buffer[off + 2] = minZ;\n this.buffer[off + 3] = maxX;\n this.buffer[off + 4] = maxY;\n this.buffer[off + 5] = maxZ;\n }\n\n /** Read a single AABB component (0–5) from the node at the given index. */\n getAABB(index: number, component: number): number {\n return this.buffer[index * NODE_STRIDE + NODE_AABB_OFFSET + component] ?? 0;\n }\n\n // ── First-child link ──────────────────────────────────────────────────────\n\n /** Set the first-child index for the node (-1 = leaf). */\n setFirstChild(index: number, childIndex: number): void {\n this.buffer[index * NODE_STRIDE + NODE_FIRST_CHILD_OFFSET] = childIndex;\n }\n\n /** Get the first-child index for the node (-1 = leaf). */\n getFirstChild(index: number): number {\n return this.buffer[index * NODE_STRIDE + NODE_FIRST_CHILD_OFFSET] ?? -1;\n }\n\n // ── Parent link ───────────────────────────────────────────────────────────\n\n /** Set the parent index for the node (-1 = root). */\n setParent(index: number, parentIndex: number): void {\n this.buffer[index * NODE_STRIDE + NODE_PARENT_OFFSET] = parentIndex;\n }\n\n /** Get the parent index for the node (-1 = root). */\n getParent(index: number): number {\n return this.buffer[index * NODE_STRIDE + NODE_PARENT_OFFSET] ?? -1;\n }\n\n // ── Object pointers ───────────────────────────────────────────────────────\n\n /** Return the number of objects stored in the node at the given index. */\n getObjectCount(index: number): number {\n return this.buffer[index * NODE_STRIDE + NODE_OBJECT_COUNT_OFFSET] ?? 0;\n }\n\n /**\n * Append an object index to the node's object list.\n * Throws a RangeError when MAX_OBJECTS_PER_NODE is exceeded.\n */\n addObject(index: number, objectIndex: number): void {\n const countOff = index * NODE_STRIDE + NODE_OBJECT_COUNT_OFFSET;\n const count = this.buffer[countOff] ?? 0;\n if (count >= MAX_OBJECTS_PER_NODE) {\n throw new RangeError(\n `OctreeNodePool.addObject: node ${index} already contains MAX_OBJECTS_PER_NODE (${MAX_OBJECTS_PER_NODE}) objects`,\n );\n }\n this.buffer[index * NODE_STRIDE + NODE_OBJECTS_OFFSET + count] = objectIndex;\n this.buffer[countOff] = count + 1;\n }\n\n /** Return the object index stored at slot `slot` in the given node. */\n getObject(index: number, slot: number): number {\n return this.buffer[index * NODE_STRIDE + NODE_OBJECTS_OFFSET + slot] ?? 0;\n }\n\n /** Reset the object count of the given node to zero (does not zero the slots). */\n clearObjects(index: number): void {\n this.buffer[index * NODE_STRIDE + NODE_OBJECT_COUNT_OFFSET] = 0;\n }\n\n /**\n * Remove a single object index from the node's object list.\n * Uses swap-with-last to avoid shifting. Returns true if found and removed.\n */\n removeObject(index: number, objectIndex: number): boolean {\n const countOff = index * NODE_STRIDE + NODE_OBJECT_COUNT_OFFSET;\n const count = this.buffer[countOff] ?? 0;\n const baseOff = index * NODE_STRIDE + NODE_OBJECTS_OFFSET;\n for (let i = 0; i < count; i++) {\n if (this.buffer[baseOff + i] === objectIndex) {\n // Swap with the last element, then decrement count.\n this.buffer[baseOff + i] = this.buffer[baseOff + count - 1] ?? 0;\n this.buffer[countOff] = count - 1;\n return true;\n }\n }\n return false;\n }\n}\n","import { OctreeNodePool, MAX_OBJECTS_PER_NODE, NODE_STRIDE, NODE_AABB_OFFSET } from './octree-node.js';\nimport { AABBPool, AABB_STRIDE } from './aabb.js';\nimport { rayIntersectsAABB } from './ray.js';\n\n/**\n * Returns true when the AABB stored at `bufOffset` in `buf` overlaps the\n * axis-aligned box defined by the six query scalars.\n */\nfunction aabbOverlapsBox(\n buf: Float32Array,\n bufOffset: number,\n qMinX: number,\n qMinY: number,\n qMinZ: number,\n qMaxX: number,\n qMaxY: number,\n qMaxZ: number,\n): boolean {\n return (\n buf[bufOffset]! <= qMaxX &&\n buf[bufOffset + 3]! >= qMinX &&\n buf[bufOffset + 1]! <= qMaxY &&\n buf[bufOffset + 4]! >= qMinY &&\n buf[bufOffset + 2]! <= qMaxZ &&\n buf[bufOffset + 5]! >= qMinZ\n );\n}\n\n/**\n * Octree with insertion and automatic subdivision.\n *\n * - When a leaf node's object count reaches MAX_OBJECTS_PER_NODE it is\n * subdivided into 8 axis-aligned child octants.\n * - An inserted AABB that fits entirely within a child octant is pushed down.\n * - An AABB that straddles a boundary is kept in the nearest ancestor that\n * fully contains it.\n */\nexport class Octree {\n private readonly nodePool: OctreeNodePool;\n private readonly aabbPool: AABBPool;\n private readonly root: number;\n /** Tracks which node each object (by AABBPool index) is currently stored in. */\n private readonly objectNodeMap: Map<number, number> = new Map();\n /** Pre-allocated traversal stack reused across raycast calls to avoid GC pressure. */\n private readonly _stack: number[] = [];\n\n constructor(nodePool: OctreeNodePool, aabbPool: AABBPool) {\n this.nodePool = nodePool;\n this.aabbPool = aabbPool;\n this.root = nodePool.allocate();\n }\n\n /** The index of the root node in the underlying OctreeNodePool. */\n get rootIndex(): number {\n return this.root;\n }\n\n /** Set the world-space AABB that the root node covers. */\n setBounds(\n minX: number,\n minY: number,\n minZ: number,\n maxX: number,\n maxY: number,\n maxZ: number,\n ): void {\n this.nodePool.setAABB(this.root, minX, minY, minZ, maxX, maxY, maxZ);\n }\n\n /** Insert the AABB at `objectIndex` (in the AABBPool) into the tree. */\n insert(objectIndex: number): void {\n this.insertIntoNode(this.root, objectIndex);\n }\n\n /**\n * Update the AABB at `objectIndex` with new bounds and reposition it in the\n * tree without rebuilding. If the new bounds still fit in the current node\n * the object stays there; otherwise it is removed and re-inserted from the\n * lowest ancestor whose bounds fully contain the new AABB.\n */\n update(\n objectIndex: number,\n newMinX: number,\n newMinY: number,\n newMinZ: number,\n newMaxX: number,\n newMaxY: number,\n newMaxZ: number,\n ): void {\n const np = this.nodePool;\n const ap = this.aabbPool;\n\n // 1. Update the AABB data.\n ap.set(objectIndex, newMinX, newMinY, newMinZ, newMaxX, newMaxY, newMaxZ);\n\n // 2. Find current node.\n const currentNode = this.objectNodeMap.get(objectIndex);\n if (currentNode === undefined) return;\n\n // 3. If the new bounds still fit in the current node, nothing to move.\n if (this.fitsInNode(objectIndex, currentNode)) return;\n\n // 4. Remove from current node.\n np.removeObject(currentNode, objectIndex);\n this.objectNodeMap.delete(objectIndex);\n\n // 5. Walk up to find the lowest ancestor that fully contains the new bounds.\n let ancestorNode = np.getParent(currentNode);\n while (ancestorNode !== -1 && !this.fitsInNode(objectIndex, ancestorNode)) {\n ancestorNode = np.getParent(ancestorNode);\n }\n // If no ancestor fits (shouldn't happen for a well-formed tree), use root.\n if (ancestorNode === -1) ancestorNode = this.root;\n\n // 6. Re-insert downward from the ancestor.\n this.insertIntoNode(ancestorNode, objectIndex);\n }\n\n /**\n * Remove the object at `objectIndex` from the tree.\n * Safe to call even if the object was never inserted (no-op).\n */\n remove(objectIndex: number): void {\n const nodeIdx = this.objectNodeMap.get(objectIndex);\n if (nodeIdx === undefined) return;\n this.nodePool.removeObject(nodeIdx, objectIndex);\n this.objectNodeMap.delete(objectIndex);\n }\n\n /**\n * Cast a ray through the octree and return the closest intersecting object.\n *\n * Uses an iterative stack traversal (pre-allocated, no recursion) to avoid\n * GC pressure. Only descends into child nodes whose AABBs are intersected by\n * the ray, providing efficient pruning of non-intersecting subtrees.\n *\n * @param rayBuf Float32Array containing the ray data [ox,oy,oz,dx,dy,dz].\n * @param rayOffset Element offset (in floats) within `rayBuf` to the ray's origin.\n * @returns The closest `{ objectIndex, t }` hit, or `null` if nothing is hit.\n */\n raycast(\n rayBuf: Float32Array,\n rayOffset: number,\n ): { objectIndex: number; t: number } | null {\n const np = this.nodePool;\n // Access the underlying flat buffers directly for zero-GC intersection tests.\n const npBuf = (np as unknown as { buffer: Float32Array }).buffer;\n const apBuf = (this.aabbPool as unknown as { buffer: Float32Array }).buffer;\n\n // Early-out: test the root AABB before touching any object data.\n if (rayIntersectsAABB(rayBuf, rayOffset, npBuf, this.root * NODE_STRIDE + NODE_AABB_OFFSET) < 0) {\n return null;\n }\n\n // Iterative DFS using the pre-allocated stack.\n this._stack.length = 0;\n this._stack.push(this.root);\n\n let closestT = Infinity;\n let closestIndex = -1;\n\n while (this._stack.length > 0) {\n const nodeIdx = this._stack.pop()!;\n\n // Test every object stored at this node level.\n const objCount = np.getObjectCount(nodeIdx);\n for (let i = 0; i < objCount; i++) {\n const objIdx = np.getObject(nodeIdx, i);\n const t = rayIntersectsAABB(rayBuf, rayOffset, apBuf, objIdx * AABB_STRIDE);\n if (t >= 0 && t < closestT) {\n closestT = t;\n closestIndex = objIdx;\n }\n }\n\n // Push only children whose AABB the ray actually intersects.\n const firstChild = np.getFirstChild(nodeIdx);\n if (firstChild !== -1) {\n for (let i = 0; i < 8; i++) {\n const childIdx = firstChild + i;\n if (rayIntersectsAABB(rayBuf, rayOffset, npBuf, childIdx * NODE_STRIDE + NODE_AABB_OFFSET) >= 0) {\n this._stack.push(childIdx);\n }\n }\n }\n }\n\n if (closestIndex === -1) return null;\n return { objectIndex: closestIndex, t: closestT };\n }\n\n /**\n * Query the octree for all objects whose AABB overlaps the given axis-aligned\n * box region and return their indices (in the AABBPool).\n *\n * Uses the same pre-allocated iterative stack as `raycast` to avoid GC\n * pressure. Descends only into child nodes whose AABBs overlap the query box,\n * pruning non-intersecting subtrees.\n *\n * @param minX Minimum X of the query box.\n * @param minY Minimum Y of the query box.\n * @param minZ Minimum Z of the query box.\n * @param maxX Maximum X of the query box.\n * @param maxY Maximum Y of the query box.\n * @param maxZ Maximum Z of the query box.\n * @returns Array of AABBPool indices for every object that overlaps the box.\n */\n queryBox(\n minX: number,\n minY: number,\n minZ: number,\n maxX: number,\n maxY: number,\n maxZ: number,\n ): number[] {\n const np = this.nodePool;\n const npBuf = (np as unknown as { buffer: Float32Array }).buffer;\n const apBuf = (this.aabbPool as unknown as { buffer: Float32Array }).buffer;\n\n const results: number[] = [];\n\n // Early-out: if the query box doesn't overlap the root, nothing to do.\n if (!aabbOverlapsBox(npBuf, this.root * NODE_STRIDE + NODE_AABB_OFFSET, minX, minY, minZ, maxX, maxY, maxZ)) {\n return results;\n }\n\n // Iterative DFS using the pre-allocated stack.\n this._stack.length = 0;\n this._stack.push(this.root);\n\n while (this._stack.length > 0) {\n const nodeIdx = this._stack.pop()!;\n\n // Test every object stored at this node level.\n const objCount = np.getObjectCount(nodeIdx);\n for (let i = 0; i < objCount; i++) {\n const objIdx = np.getObject(nodeIdx, i);\n if (aabbOverlapsBox(apBuf, objIdx * AABB_STRIDE, minX, minY, minZ, maxX, maxY, maxZ)) {\n results.push(objIdx);\n }\n }\n\n // Push only children whose AABB overlaps the query box.\n const firstChild = np.getFirstChild(nodeIdx);\n if (firstChild !== -1) {\n for (let i = 0; i < 8; i++) {\n const childIdx = firstChild + i;\n if (aabbOverlapsBox(npBuf, childIdx * NODE_STRIDE + NODE_AABB_OFFSET, minX, minY, minZ, maxX, maxY, maxZ)) {\n this._stack.push(childIdx);\n }\n }\n }\n }\n\n return results;\n }\n\n // ── Private helpers ──────────────────────────────────────────────────────\n\n private insertIntoNode(nodeIdx: number, objectIndex: number): void {\n const np = this.nodePool;\n const firstChild = np.getFirstChild(nodeIdx);\n\n // Internal node: try to push the object into a fitting child octant.\n if (firstChild !== -1) {\n for (let i = 0; i < 8; i++) {\n if (this.fitsInNode(objectIndex, firstChild + i)) {\n this.insertIntoNode(firstChild + i, objectIndex);\n return;\n }\n }\n // Object straddles one or more boundaries – keep it at this level.\n // We manually add it here even if it exceeds MAX_OBJECTS_PER_NODE,\n // because internal nodes only hold straddling objects.\n np.addObject(nodeIdx, objectIndex);\n this.objectNodeMap.set(objectIndex, nodeIdx);\n return;\n }\n\n // Leaf node.\n const count = np.getObjectCount(nodeIdx);\n if (count < MAX_OBJECTS_PER_NODE) {\n np.addObject(nodeIdx, objectIndex);\n this.objectNodeMap.set(objectIndex, nodeIdx);\n } else {\n // Prevent infinite subdivision if perfectly overlapping items exceed capacity.\n // If the node we are trying to subdivide has an incredibly small physical bounds\n // or if it fails to separate objects during subdivide, it would loop forever. \n // Instead, we just forcefully add it here and allow the slot to overflow if needed,\n // but because our Float32Array nodes have strict memory limits for MAX_OBJECTS,\n // we must just drop it or accept that `MAX_OBJECTS_PER_NODE` is a hard physical limit.\n // To properly handle this without breaking the pool sizing, in a DOD engine we simply\n // stop adding when perfectly overlapping identical items exceed memory at the leaf bounds.\n try {\n this.subdivide(nodeIdx);\n this.insertIntoNode(nodeIdx, objectIndex);\n } catch (e) {\n if (e instanceof RangeError) {\n // Hard capacity limit hit on identical objects that refuse to separate. \n // Stop recursion. We cannot safely insert it into this perfectly overlapping bucket.\n } else {\n throw e; // Bubble up other errors.\n }\n }\n }\n }\n\n /** Split a leaf node into 8 children and redistribute its objects. */\n private subdivide(nodeIdx: number): void {\n const np = this.nodePool;\n\n const minX = np.getAABB(nodeIdx, 0);\n const minY = np.getAABB(nodeIdx, 1);\n const minZ = np.getAABB(nodeIdx, 2);\n const maxX = np.getAABB(nodeIdx, 3);\n const maxY = np.getAABB(nodeIdx, 4);\n const maxZ = np.getAABB(nodeIdx, 5);\n\n const midX = (minX + maxX) / 2;\n const midY = (minY + maxY) / 2;\n const midZ = (minZ + maxZ) / 2;\n\n // Allocate 8 consecutive child nodes (guarantees contiguous indices).\n const firstChild = np.allocate();\n for (let i = 1; i < 8; i++) {\n np.allocate();\n }\n np.setFirstChild(nodeIdx, firstChild);\n\n // Assign each child its octant AABB.\n // Bit decomposition: bit-0 → X half, bit-1 → Y half, bit-2 → Z half.\n // i & 1 === 0 means left half (minX to midX), i & 1 !== 0 means right half (midX to maxX)\n for (let i = 0; i < 8; i++) {\n const childIdx = firstChild + i;\n const cMinX = (i & 1) === 0 ? minX : midX;\n const cMaxX = (i & 1) === 0 ? midX : maxX;\n const cMinY = (i & 2) === 0 ? minY : midY;\n const cMaxY = (i & 2) === 0 ? midY : maxY;\n const cMinZ = (i & 4) === 0 ? minZ : midZ;\n const cMaxZ = (i & 4) === 0 ? midZ : maxZ;\n np.setAABB(childIdx, cMinX, cMinY, cMinZ, cMaxX, cMaxY, cMaxZ);\n np.setParent(childIdx, nodeIdx);\n }\n\n // Redistribute the existing objects.\n const count = np.getObjectCount(nodeIdx);\n const saved: number[] = [];\n for (let i = 0; i < count; i++) {\n saved.push(np.getObject(nodeIdx, i));\n }\n np.clearObjects(nodeIdx);\n\n for (const obj of saved) {\n // Use insertIntoNode so objectNodeMap stays consistent.\n this.objectNodeMap.delete(obj);\n this.insertIntoNode(nodeIdx, obj);\n }\n }\n\n /** Returns true when the AABB at `objectIndex` is fully contained by `nodeIdx`. */\n private fitsInNode(objectIndex: number, nodeIdx: number): boolean {\n const np = this.nodePool;\n const ap = this.aabbPool;\n return (\n ap.get(objectIndex, 0) >= np.getAABB(nodeIdx, 0) &&\n ap.get(objectIndex, 1) >= np.getAABB(nodeIdx, 1) &&\n ap.get(objectIndex, 2) >= np.getAABB(nodeIdx, 2) &&\n ap.get(objectIndex, 3) <= np.getAABB(nodeIdx, 3) &&\n ap.get(objectIndex, 4) <= np.getAABB(nodeIdx, 4) &&\n ap.get(objectIndex, 5) <= np.getAABB(nodeIdx, 5)\n );\n }\n}\n","/**\n * LiDAR Web Worker\n *\n * Handles heavy raycasting (LiDAR sweep logic) off the main thread.\n *\n * Protocol\n * --------\n * 1. Main thread sends one `LidarInitMessage` to set up shared buffers and\n * world bounds. The worker replies with `{ type: 'ready' }`.\n *\n * 2. For each sweep the main thread:\n * a. Writes up-to-date object AABB data into `aabbsSab`\n * (6 floats per object: minX, minY, minZ, maxX, maxY, maxZ).\n * b. Sends a `LidarSweepMessage` with the current `objectCount`.\n *\n * 3. The worker reads the transforms, inserts/updates objects in its local\n * Octree, casts every ray from `raysSab`, and writes results to\n * `resultsSab` (2 floats per ray: objectIndex, t; –1/–1 for a miss).\n * It then replies with `{ type: 'done', rayCount }`.\n *\n * SharedArrayBuffer layout\n * ------------------------\n * aabbsSab : Float32Array – objectCapacity × AABB_STRIDE (6) floats\n * nodesSab : Float32Array – nodeCapacity × NODE_STRIDE floats\n * raysSab : Float32Array – rayCount × RAY_STRIDE (6) floats\n * resultsSab: Float32Array – rayCount × 2 floats\n * [objectIndex (float), t (float)] per ray\n */\n\nimport { OctreeNodePool } from './octree-node.js';\nimport { AABBPool } from './aabb.js';\nimport { RAY_STRIDE } from './ray.js';\nimport { Octree } from './octree.js';\n\n// ── Public message types ──────────────────────────────────────────────────────\n\n/** Sent once from the main thread to initialise the shared buffers. */\nexport interface LidarInitMessage {\n type: 'init';\n /** SharedArrayBuffer for AABB data (objectCapacity × 6 floats). Written by the main thread. */\n aabbsSab: SharedArrayBuffer;\n /** SharedArrayBuffer for Octree node data (nodeCapacity × NODE_STRIDE floats). Managed by the worker. */\n nodesSab: SharedArrayBuffer;\n /** SharedArrayBuffer for ray data (rayCount × 6 floats). Written by the main thread (or set once). */\n raysSab: SharedArrayBuffer;\n /** SharedArrayBuffer for raycast results (rayCount × 2 floats). Written by the worker. */\n resultsSab: SharedArrayBuffer;\n /** Maximum number of objects the AABBPool can hold. */\n objectCapacity: number;\n /** Maximum number of Octree nodes the NodePool can hold. */\n nodeCapacity: number;\n /** Number of rays in the LiDAR sweep. */\n rayCount: number;\n /** World-space bounds for the root octree node. */\n worldMinX: number;\n worldMinY: number;\n worldMinZ: number;\n worldMaxX: number;\n worldMaxY: number;\n worldMaxZ: number;\n}\n\n/** Sent each frame to trigger a LiDAR sweep. */\nexport interface LidarSweepMessage {\n type: 'sweep';\n /** Number of active objects whose AABB data has been written to `aabbsSab`. */\n objectCount: number;\n}\n\nexport type LidarWorkerInMessage = LidarInitMessage | LidarSweepMessage;\n\n/** Posted by the worker after initialisation succeeds. */\nexport interface LidarReadyMessage {\n type: 'ready';\n}\n\n/** Posted by the worker after a sweep finishes. */\nexport interface LidarDoneMessage {\n type: 'done';\n /** Number of rays that were cast (equals the `rayCount` given in `init`). */\n rayCount: number;\n}\n\nexport type LidarWorkerOutMessage = LidarReadyMessage | LidarDoneMessage;\n\n// ── Processor (pure logic, no worker-global bindings) ────────────────────────\n\n/**\n * Stateful LiDAR sweep processor.\n *\n * Exported for direct use in tests and advanced integrations. The bottom of\n * this module wires it to the Web Worker global scope automatically when the\n * script runs inside a worker.\n */\nexport function createLidarProcessor(): {\n init(msg: LidarInitMessage): LidarReadyMessage;\n sweep(msg: LidarSweepMessage): LidarDoneMessage;\n} {\n let octree: Octree | null = null;\n let aabbPool: AABBPool | null = null;\n let raysBuf: Float32Array | null = null;\n let resultsBuf: Float32Array | null = null;\n let totalRayCount = 0;\n /** Tracks how many objects have been inserted into the local octree. */\n let insertedObjectCount = 0;\n\n return {\n /** Initialise shared buffers and create the Octree. */\n init(msg: LidarInitMessage): LidarReadyMessage {\n const nodePool = new OctreeNodePool(msg.nodeCapacity, msg.nodesSab);\n aabbPool = new AABBPool(msg.objectCapacity, msg.aabbsSab);\n raysBuf = new Float32Array(msg.raysSab);\n resultsBuf = new Float32Array(msg.resultsSab);\n totalRayCount = msg.rayCount;\n insertedObjectCount = 0;\n\n octree = new Octree(nodePool, aabbPool);\n octree.setBounds(\n msg.worldMinX, msg.worldMinY, msg.worldMinZ,\n msg.worldMaxX, msg.worldMaxY, msg.worldMaxZ,\n );\n\n return { type: 'ready' };\n },\n\n /**\n * Read current object AABB data from the shared buffer, update the Octree,\n * cast every sweep ray, and write results to the results buffer.\n */\n sweep(msg: LidarSweepMessage): LidarDoneMessage {\n if (octree === null || aabbPool === null || raysBuf === null || resultsBuf === null) {\n throw new Error('LidarProcessor: sweep called before init');\n }\n\n const objectCount = msg.objectCount;\n\n // Ensure the pool's allocation count reflects all active objects so that\n // the pool's `size` getter stays accurate (the buffer memory already exists\n // in the SAB – allocate() only increments the internal counter).\n while (aabbPool.size < objectCount) {\n aabbPool.allocate();\n }\n\n // Insert new objects or reposition existing ones in the octree.\n for (let i = 0; i < objectCount; i++) {\n const minX = aabbPool.get(i, 0);\n const minY = aabbPool.get(i, 1);\n const minZ = aabbPool.get(i, 2);\n const maxX = aabbPool.get(i, 3);\n const maxY = aabbPool.get(i, 4);\n const maxZ = aabbPool.get(i, 5);\n\n if (i < insertedObjectCount) {\n // Already in the tree – reposition without full rebuild.\n octree.update(i, minX, minY, minZ, maxX, maxY, maxZ);\n } else {\n // First appearance: insert using the bounds already in the SAB.\n octree.insert(i);\n insertedObjectCount++;\n }\n }\n\n // LiDAR sweep: cast each ray and write the closest hit (or miss) to\n // the results buffer. Layout: [objectIndex, t] per ray (–1/–1 = miss).\n for (let r = 0; r < totalRayCount; r++) {\n const hit = octree.raycast(raysBuf, r * RAY_STRIDE);\n if (hit !== null) {\n resultsBuf[r * 2] = hit.objectIndex;\n resultsBuf[r * 2 + 1] = hit.t;\n } else {\n resultsBuf[r * 2] = -1;\n resultsBuf[r * 2 + 1] = -1;\n }\n }\n\n return { type: 'done', rayCount: totalRayCount };\n },\n };\n}\n\n// ── Web Worker binding ────────────────────────────────────────────────────────\n// Automatically wire the processor to the worker's global message handler when\n// this script is loaded inside a Web Worker context (browser or Node.js worker).\n\ntype WorkerGlobalSelf = {\n onmessage: ((event: { data: LidarWorkerInMessage }) => void) | null;\n postMessage(data: LidarWorkerOutMessage): void;\n};\n\n// `postMessage` is available on the worker global but not in a regular Node.js\n// module context, so check for it before binding.\nconst globalScope = globalThis as Record<string, unknown>;\nif (typeof globalScope['postMessage'] === 'function' && typeof globalScope['onmessage'] !== 'undefined') {\n const workerSelf = globalThis as unknown as WorkerGlobalSelf;\n const processor = createLidarProcessor();\n\n workerSelf.onmessage = (event) => {\n const msg = event.data;\n if (msg.type === 'init') {\n workerSelf.postMessage(processor.init(msg));\n } else if (msg.type === 'sweep') {\n workerSelf.postMessage(processor.sweep(msg));\n }\n };\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -164,7 +164,7 @@ declare const NODE_FIRST_CHILD_OFFSET = 6;
164
164
  declare const NODE_PARENT_OFFSET = 7;
165
165
  declare const NODE_OBJECT_COUNT_OFFSET = 8;
166
166
  declare const NODE_OBJECTS_OFFSET = 9;
167
- declare const MAX_OBJECTS_PER_NODE = 8;
167
+ declare const MAX_OBJECTS_PER_NODE = 64;
168
168
  declare const NODE_STRIDE: number;
169
169
  /**
170
170
  * A pool-backed, zero-GC Octree node store.
@@ -253,6 +253,11 @@ declare class Octree {
253
253
  * lowest ancestor whose bounds fully contain the new AABB.
254
254
  */
255
255
  update(objectIndex: number, newMinX: number, newMinY: number, newMinZ: number, newMaxX: number, newMaxY: number, newMaxZ: number): void;
256
+ /**
257
+ * Remove the object at `objectIndex` from the tree.
258
+ * Safe to call even if the object was never inserted (no-op).
259
+ */
260
+ remove(objectIndex: number): void;
256
261
  /**
257
262
  * Cast a ray through the octree and return the closest intersecting object.
258
263
  *
package/dist/index.d.ts CHANGED
@@ -164,7 +164,7 @@ declare const NODE_FIRST_CHILD_OFFSET = 6;
164
164
  declare const NODE_PARENT_OFFSET = 7;
165
165
  declare const NODE_OBJECT_COUNT_OFFSET = 8;
166
166
  declare const NODE_OBJECTS_OFFSET = 9;
167
- declare const MAX_OBJECTS_PER_NODE = 8;
167
+ declare const MAX_OBJECTS_PER_NODE = 64;
168
168
  declare const NODE_STRIDE: number;
169
169
  /**
170
170
  * A pool-backed, zero-GC Octree node store.
@@ -253,6 +253,11 @@ declare class Octree {
253
253
  * lowest ancestor whose bounds fully contain the new AABB.
254
254
  */
255
255
  update(objectIndex: number, newMinX: number, newMinY: number, newMinZ: number, newMaxX: number, newMaxY: number, newMaxZ: number): void;
256
+ /**
257
+ * Remove the object at `objectIndex` from the tree.
258
+ * Safe to call even if the object was never inserted (no-op).
259
+ */
260
+ remove(objectIndex: number): void;
256
261
  /**
257
262
  * Cast a ray through the octree and return the closest intersecting object.
258
263
  *
package/dist/index.js CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  aabbMerge,
18
18
  createLidarProcessor,
19
19
  rayIntersectsAABB
20
- } from "./chunk-JF4RVZK3.js";
20
+ } from "./chunk-6B5AUBFL.js";
21
21
 
22
22
  // src/object-pool.ts
23
23
  var ObjectPool = class {
@@ -30,7 +30,7 @@ var NODE_FIRST_CHILD_OFFSET = 6;
30
30
  var NODE_PARENT_OFFSET = 7;
31
31
  var NODE_OBJECT_COUNT_OFFSET = 8;
32
32
  var NODE_OBJECTS_OFFSET = 9;
33
- var MAX_OBJECTS_PER_NODE = 8;
33
+ var MAX_OBJECTS_PER_NODE = 64;
34
34
  var NODE_STRIDE = NODE_OBJECTS_OFFSET + MAX_OBJECTS_PER_NODE;
35
35
  var OctreeNodePool = class _OctreeNodePool {
36
36
  buffer;
@@ -270,6 +270,16 @@ var Octree = class {
270
270
  if (ancestorNode === -1) ancestorNode = this.root;
271
271
  this.insertIntoNode(ancestorNode, objectIndex);
272
272
  }
273
+ /**
274
+ * Remove the object at `objectIndex` from the tree.
275
+ * Safe to call even if the object was never inserted (no-op).
276
+ */
277
+ remove(objectIndex) {
278
+ const nodeIdx = this.objectNodeMap.get(objectIndex);
279
+ if (nodeIdx === void 0) return;
280
+ this.nodePool.removeObject(nodeIdx, objectIndex);
281
+ this.objectNodeMap.delete(objectIndex);
282
+ }
273
283
  /**
274
284
  * Cast a ray through the octree and return the closest intersecting object.
275
285
  *
@@ -383,8 +393,15 @@ var Octree = class {
383
393
  np.addObject(nodeIdx, objectIndex);
384
394
  this.objectNodeMap.set(objectIndex, nodeIdx);
385
395
  } else {
386
- this.subdivide(nodeIdx);
387
- this.insertIntoNode(nodeIdx, objectIndex);
396
+ try {
397
+ this.subdivide(nodeIdx);
398
+ this.insertIntoNode(nodeIdx, objectIndex);
399
+ } catch (e) {
400
+ if (e instanceof RangeError) {
401
+ } else {
402
+ throw e;
403
+ }
404
+ }
388
405
  }
389
406
  }
390
407
  /** Split a leaf node into 8 children and redistribute its objects. */
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lidar-worker.ts","../src/octree-node.ts","../src/aabb.ts","../src/ray.ts","../src/octree.ts"],"sourcesContent":["/**\n * LiDAR Web Worker\n *\n * Handles heavy raycasting (LiDAR sweep logic) off the main thread.\n *\n * Protocol\n * --------\n * 1. Main thread sends one `LidarInitMessage` to set up shared buffers and\n * world bounds. The worker replies with `{ type: 'ready' }`.\n *\n * 2. For each sweep the main thread:\n * a. Writes up-to-date object AABB data into `aabbsSab`\n * (6 floats per object: minX, minY, minZ, maxX, maxY, maxZ).\n * b. Sends a `LidarSweepMessage` with the current `objectCount`.\n *\n * 3. The worker reads the transforms, inserts/updates objects in its local\n * Octree, casts every ray from `raysSab`, and writes results to\n * `resultsSab` (2 floats per ray: objectIndex, t; –1/–1 for a miss).\n * It then replies with `{ type: 'done', rayCount }`.\n *\n * SharedArrayBuffer layout\n * ------------------------\n * aabbsSab : Float32Array – objectCapacity × AABB_STRIDE (6) floats\n * nodesSab : Float32Array – nodeCapacity × NODE_STRIDE floats\n * raysSab : Float32Array – rayCount × RAY_STRIDE (6) floats\n * resultsSab: Float32Array – rayCount × 2 floats\n * [objectIndex (float), t (float)] per ray\n */\n\nimport { OctreeNodePool } from './octree-node.js';\nimport { AABBPool } from './aabb.js';\nimport { RAY_STRIDE } from './ray.js';\nimport { Octree } from './octree.js';\n\n// ── Public message types ──────────────────────────────────────────────────────\n\n/** Sent once from the main thread to initialise the shared buffers. */\nexport interface LidarInitMessage {\n type: 'init';\n /** SharedArrayBuffer for AABB data (objectCapacity × 6 floats). Written by the main thread. */\n aabbsSab: SharedArrayBuffer;\n /** SharedArrayBuffer for Octree node data (nodeCapacity × NODE_STRIDE floats). Managed by the worker. */\n nodesSab: SharedArrayBuffer;\n /** SharedArrayBuffer for ray data (rayCount × 6 floats). Written by the main thread (or set once). */\n raysSab: SharedArrayBuffer;\n /** SharedArrayBuffer for raycast results (rayCount × 2 floats). Written by the worker. */\n resultsSab: SharedArrayBuffer;\n /** Maximum number of objects the AABBPool can hold. */\n objectCapacity: number;\n /** Maximum number of Octree nodes the NodePool can hold. */\n nodeCapacity: number;\n /** Number of rays in the LiDAR sweep. */\n rayCount: number;\n /** World-space bounds for the root octree node. */\n worldMinX: number;\n worldMinY: number;\n worldMinZ: number;\n worldMaxX: number;\n worldMaxY: number;\n worldMaxZ: number;\n}\n\n/** Sent each frame to trigger a LiDAR sweep. */\nexport interface LidarSweepMessage {\n type: 'sweep';\n /** Number of active objects whose AABB data has been written to `aabbsSab`. */\n objectCount: number;\n}\n\nexport type LidarWorkerInMessage = LidarInitMessage | LidarSweepMessage;\n\n/** Posted by the worker after initialisation succeeds. */\nexport interface LidarReadyMessage {\n type: 'ready';\n}\n\n/** Posted by the worker after a sweep finishes. */\nexport interface LidarDoneMessage {\n type: 'done';\n /** Number of rays that were cast (equals the `rayCount` given in `init`). */\n rayCount: number;\n}\n\nexport type LidarWorkerOutMessage = LidarReadyMessage | LidarDoneMessage;\n\n// ── Processor (pure logic, no worker-global bindings) ────────────────────────\n\n/**\n * Stateful LiDAR sweep processor.\n *\n * Exported for direct use in tests and advanced integrations. The bottom of\n * this module wires it to the Web Worker global scope automatically when the\n * script runs inside a worker.\n */\nexport function createLidarProcessor(): {\n init(msg: LidarInitMessage): LidarReadyMessage;\n sweep(msg: LidarSweepMessage): LidarDoneMessage;\n} {\n let octree: Octree | null = null;\n let aabbPool: AABBPool | null = null;\n let raysBuf: Float32Array | null = null;\n let resultsBuf: Float32Array | null = null;\n let totalRayCount = 0;\n /** Tracks how many objects have been inserted into the local octree. */\n let insertedObjectCount = 0;\n\n return {\n /** Initialise shared buffers and create the Octree. */\n init(msg: LidarInitMessage): LidarReadyMessage {\n const nodePool = new OctreeNodePool(msg.nodeCapacity, msg.nodesSab);\n aabbPool = new AABBPool(msg.objectCapacity, msg.aabbsSab);\n raysBuf = new Float32Array(msg.raysSab);\n resultsBuf = new Float32Array(msg.resultsSab);\n totalRayCount = msg.rayCount;\n insertedObjectCount = 0;\n\n octree = new Octree(nodePool, aabbPool);\n octree.setBounds(\n msg.worldMinX, msg.worldMinY, msg.worldMinZ,\n msg.worldMaxX, msg.worldMaxY, msg.worldMaxZ,\n );\n\n return { type: 'ready' };\n },\n\n /**\n * Read current object AABB data from the shared buffer, update the Octree,\n * cast every sweep ray, and write results to the results buffer.\n */\n sweep(msg: LidarSweepMessage): LidarDoneMessage {\n if (octree === null || aabbPool === null || raysBuf === null || resultsBuf === null) {\n throw new Error('LidarProcessor: sweep called before init');\n }\n\n const objectCount = msg.objectCount;\n\n // Ensure the pool's allocation count reflects all active objects so that\n // the pool's `size` getter stays accurate (the buffer memory already exists\n // in the SAB – allocate() only increments the internal counter).\n while (aabbPool.size < objectCount) {\n aabbPool.allocate();\n }\n\n // Insert new objects or reposition existing ones in the octree.\n for (let i = 0; i < objectCount; i++) {\n const minX = aabbPool.get(i, 0);\n const minY = aabbPool.get(i, 1);\n const minZ = aabbPool.get(i, 2);\n const maxX = aabbPool.get(i, 3);\n const maxY = aabbPool.get(i, 4);\n const maxZ = aabbPool.get(i, 5);\n\n if (i < insertedObjectCount) {\n // Already in the tree – reposition without full rebuild.\n octree.update(i, minX, minY, minZ, maxX, maxY, maxZ);\n } else {\n // First appearance: insert using the bounds already in the SAB.\n octree.insert(i);\n insertedObjectCount++;\n }\n }\n\n // LiDAR sweep: cast each ray and write the closest hit (or miss) to\n // the results buffer. Layout: [objectIndex, t] per ray (–1/–1 = miss).\n for (let r = 0; r < totalRayCount; r++) {\n const hit = octree.raycast(raysBuf, r * RAY_STRIDE);\n if (hit !== null) {\n resultsBuf[r * 2] = hit.objectIndex;\n resultsBuf[r * 2 + 1] = hit.t;\n } else {\n resultsBuf[r * 2] = -1;\n resultsBuf[r * 2 + 1] = -1;\n }\n }\n\n return { type: 'done', rayCount: totalRayCount };\n },\n };\n}\n\n// ── Web Worker binding ────────────────────────────────────────────────────────\n// Automatically wire the processor to the worker's global message handler when\n// this script is loaded inside a Web Worker context (browser or Node.js worker).\n\ntype WorkerGlobalSelf = {\n onmessage: ((event: { data: LidarWorkerInMessage }) => void) | null;\n postMessage(data: LidarWorkerOutMessage): void;\n};\n\n// `postMessage` is available on the worker global but not in a regular Node.js\n// module context, so check for it before binding.\nconst globalScope = globalThis as Record<string, unknown>;\nif (typeof globalScope['postMessage'] === 'function' && typeof globalScope['onmessage'] !== 'undefined') {\n const workerSelf = globalThis as unknown as WorkerGlobalSelf;\n const processor = createLidarProcessor();\n\n workerSelf.onmessage = (event) => {\n const msg = event.data;\n if (msg.type === 'init') {\n workerSelf.postMessage(processor.init(msg));\n } else if (msg.type === 'sweep') {\n workerSelf.postMessage(processor.sweep(msg));\n }\n };\n}\n","/**\n * Octree node stored as a flat Float32Array slice.\n *\n * Layout per node (NODE_STRIDE floats):\n * [0..5] – AABB (minX, minY, minZ, maxX, maxY, maxZ)\n * [6] – firstChild index (-1 = leaf / no children)\n * [7] – parent index (-1 = root)\n * [8] – object count\n * [9..] – object indices (MAX_OBJECTS_PER_NODE slots)\n */\n\nexport const NODE_AABB_OFFSET = 0;\nexport const NODE_FIRST_CHILD_OFFSET = 6;\nexport const NODE_PARENT_OFFSET = 7;\nexport const NODE_OBJECT_COUNT_OFFSET = 8;\nexport const NODE_OBJECTS_OFFSET = 9;\nexport const MAX_OBJECTS_PER_NODE = 8;\nexport const NODE_STRIDE = NODE_OBJECTS_OFFSET + MAX_OBJECTS_PER_NODE;\n\n/**\n * A pool-backed, zero-GC Octree node store.\n * Each node occupies NODE_STRIDE floats in the underlying Float32Array buffer.\n *\n * When a `SharedArrayBuffer` is provided, the pool's data is accessible from\n * multiple threads (e.g. a Web Worker) with no copying.\n */\nexport class OctreeNodePool {\n private readonly buffer: Float32Array;\n private count: number = 0;\n\n constructor(capacity: number, sharedBuffer?: SharedArrayBuffer) {\n this.buffer = sharedBuffer\n ? new Float32Array(sharedBuffer, 0, capacity * NODE_STRIDE)\n : new Float32Array(capacity * NODE_STRIDE);\n }\n\n /**\n * Create an OctreeNodePool backed by a new `SharedArrayBuffer`.\n * Both the pool and the underlying `SharedArrayBuffer` are returned so the\n * caller can transfer the buffer to a `Worker` via `postMessage`.\n */\n static createShared(capacity: number): { pool: OctreeNodePool; sab: SharedArrayBuffer } {\n const sab = new SharedArrayBuffer(capacity * NODE_STRIDE * Float32Array.BYTES_PER_ELEMENT);\n return { pool: new OctreeNodePool(capacity, sab), sab };\n }\n\n /** Allocate a new node slot, initialise sentinel values, and return its index. */\n allocate(): number {\n const index = this.count;\n const offset = index * NODE_STRIDE;\n // Initialise sentinel values for child/parent links and object count.\n this.buffer[offset + NODE_FIRST_CHILD_OFFSET] = -1;\n this.buffer[offset + NODE_PARENT_OFFSET] = -1;\n this.buffer[offset + NODE_OBJECT_COUNT_OFFSET] = 0;\n this.count += 1;\n return index;\n }\n\n /** Returns the number of allocated nodes. */\n get size(): number {\n return this.count;\n }\n\n /** Reset the pool (all allocations freed, no GC). */\n reset(): void {\n this.count = 0;\n }\n\n // ── AABB ──────────────────────────────────────────────────────────────────\n\n /** Set the AABB bounds for the node at the given index. */\n setAABB(\n index: number,\n minX: number,\n minY: number,\n minZ: number,\n maxX: number,\n maxY: number,\n maxZ: number,\n ): void {\n const off = index * NODE_STRIDE + NODE_AABB_OFFSET;\n this.buffer[off] = minX;\n this.buffer[off + 1] = minY;\n this.buffer[off + 2] = minZ;\n this.buffer[off + 3] = maxX;\n this.buffer[off + 4] = maxY;\n this.buffer[off + 5] = maxZ;\n }\n\n /** Read a single AABB component (0–5) from the node at the given index. */\n getAABB(index: number, component: number): number {\n return this.buffer[index * NODE_STRIDE + NODE_AABB_OFFSET + component] ?? 0;\n }\n\n // ── First-child link ──────────────────────────────────────────────────────\n\n /** Set the first-child index for the node (-1 = leaf). */\n setFirstChild(index: number, childIndex: number): void {\n this.buffer[index * NODE_STRIDE + NODE_FIRST_CHILD_OFFSET] = childIndex;\n }\n\n /** Get the first-child index for the node (-1 = leaf). */\n getFirstChild(index: number): number {\n return this.buffer[index * NODE_STRIDE + NODE_FIRST_CHILD_OFFSET] ?? -1;\n }\n\n // ── Parent link ───────────────────────────────────────────────────────────\n\n /** Set the parent index for the node (-1 = root). */\n setParent(index: number, parentIndex: number): void {\n this.buffer[index * NODE_STRIDE + NODE_PARENT_OFFSET] = parentIndex;\n }\n\n /** Get the parent index for the node (-1 = root). */\n getParent(index: number): number {\n return this.buffer[index * NODE_STRIDE + NODE_PARENT_OFFSET] ?? -1;\n }\n\n // ── Object pointers ───────────────────────────────────────────────────────\n\n /** Return the number of objects stored in the node at the given index. */\n getObjectCount(index: number): number {\n return this.buffer[index * NODE_STRIDE + NODE_OBJECT_COUNT_OFFSET] ?? 0;\n }\n\n /**\n * Append an object index to the node's object list.\n * Throws a RangeError when MAX_OBJECTS_PER_NODE is exceeded.\n */\n addObject(index: number, objectIndex: number): void {\n const countOff = index * NODE_STRIDE + NODE_OBJECT_COUNT_OFFSET;\n const count = this.buffer[countOff] ?? 0;\n if (count >= MAX_OBJECTS_PER_NODE) {\n throw new RangeError(\n `OctreeNodePool.addObject: node ${index} already contains MAX_OBJECTS_PER_NODE (${MAX_OBJECTS_PER_NODE}) objects`,\n );\n }\n this.buffer[index * NODE_STRIDE + NODE_OBJECTS_OFFSET + count] = objectIndex;\n this.buffer[countOff] = count + 1;\n }\n\n /** Return the object index stored at slot `slot` in the given node. */\n getObject(index: number, slot: number): number {\n return this.buffer[index * NODE_STRIDE + NODE_OBJECTS_OFFSET + slot] ?? 0;\n }\n\n /** Reset the object count of the given node to zero (does not zero the slots). */\n clearObjects(index: number): void {\n this.buffer[index * NODE_STRIDE + NODE_OBJECT_COUNT_OFFSET] = 0;\n }\n\n /**\n * Remove a single object index from the node's object list.\n * Uses swap-with-last to avoid shifting. Returns true if found and removed.\n */\n removeObject(index: number, objectIndex: number): boolean {\n const countOff = index * NODE_STRIDE + NODE_OBJECT_COUNT_OFFSET;\n const count = this.buffer[countOff] ?? 0;\n const baseOff = index * NODE_STRIDE + NODE_OBJECTS_OFFSET;\n for (let i = 0; i < count; i++) {\n if (this.buffer[baseOff + i] === objectIndex) {\n // Swap with the last element, then decrement count.\n this.buffer[baseOff + i] = this.buffer[baseOff + count - 1] ?? 0;\n this.buffer[countOff] = count - 1;\n return true;\n }\n }\n return false;\n }\n}\n","/**\n * AABB (Axis-Aligned Bounding Box) stored as a flat Float32Array slice.\n * Layout: [minX, minY, minZ, maxX, maxY, maxZ]\n */\nexport const AABB_STRIDE = 6;\n\n/**\n * A pool-backed, zero-GC AABB store.\n * Each AABB occupies AABB_STRIDE floats in the underlying buffer.\n *\n * When a `SharedArrayBuffer` is provided, the pool's data is accessible from\n * multiple threads (e.g. a Web Worker) with no copying.\n */\nexport class AABBPool {\n private readonly buffer: Float32Array;\n private count: number = 0;\n\n constructor(capacity: number, sharedBuffer?: SharedArrayBuffer) {\n this.buffer = sharedBuffer\n ? new Float32Array(sharedBuffer, 0, capacity * AABB_STRIDE)\n : new Float32Array(capacity * AABB_STRIDE);\n }\n\n /**\n * Create an AABBPool backed by a new `SharedArrayBuffer`.\n * Both the pool and the underlying `SharedArrayBuffer` are returned so the\n * caller can transfer the buffer to a `Worker` via `postMessage`.\n */\n static createShared(capacity: number): { pool: AABBPool; sab: SharedArrayBuffer } {\n const sab = new SharedArrayBuffer(capacity * AABB_STRIDE * Float32Array.BYTES_PER_ELEMENT);\n return { pool: new AABBPool(capacity, sab), sab };\n }\n\n /** Allocate a new AABB slot and return its index. */\n allocate(): number {\n const index = this.count;\n this.count += 1;\n return index;\n }\n\n /** Set the bounds of an AABB at the given index. */\n set(\n index: number,\n minX: number,\n minY: number,\n minZ: number,\n maxX: number,\n maxY: number,\n maxZ: number,\n ): void {\n const offset = index * AABB_STRIDE;\n this.buffer[offset] = minX;\n this.buffer[offset + 1] = minY;\n this.buffer[offset + 2] = minZ;\n this.buffer[offset + 3] = maxX;\n this.buffer[offset + 4] = maxY;\n this.buffer[offset + 5] = maxZ;\n }\n\n /** Read a single component value from the buffer. */\n get(index: number, component: number): number {\n return this.buffer[index * AABB_STRIDE + component] ?? 0;\n }\n\n /** Returns the number of allocated AABBs. */\n get size(): number {\n return this.count;\n }\n\n /** Reset the pool (all allocations freed, no GC). */\n reset(): void {\n this.count = 0;\n }\n}\n\n/**\n * Expand AABB `a` in-place so it also fully contains AABB `b`.\n */\nexport function aabbExpand(pool: AABBPool, a: number, b: number): void {\n const buf = (pool as unknown as { buffer: Float32Array }).buffer;\n const aOff = a * AABB_STRIDE;\n const bOff = b * AABB_STRIDE;\n buf[aOff] = Math.min(buf[aOff] ?? 0, buf[bOff] ?? 0);\n buf[aOff + 1] = Math.min(buf[aOff + 1] ?? 0, buf[bOff + 1] ?? 0);\n buf[aOff + 2] = Math.min(buf[aOff + 2] ?? 0, buf[bOff + 2] ?? 0);\n buf[aOff + 3] = Math.max(buf[aOff + 3] ?? 0, buf[bOff + 3] ?? 0);\n buf[aOff + 4] = Math.max(buf[aOff + 4] ?? 0, buf[bOff + 4] ?? 0);\n buf[aOff + 5] = Math.max(buf[aOff + 5] ?? 0, buf[bOff + 5] ?? 0);\n}\n\n/**\n * Merge AABBs `a` and `b` into `dest` (a pre-allocated slot), storing their union.\n */\nexport function aabbMerge(pool: AABBPool, dest: number, a: number, b: number): void {\n const buf = (pool as unknown as { buffer: Float32Array }).buffer;\n const aOff = a * AABB_STRIDE;\n const bOff = b * AABB_STRIDE;\n const dOff = dest * AABB_STRIDE;\n buf[dOff] = Math.min(buf[aOff] ?? 0, buf[bOff] ?? 0);\n buf[dOff + 1] = Math.min(buf[aOff + 1] ?? 0, buf[bOff + 1] ?? 0);\n buf[dOff + 2] = Math.min(buf[aOff + 2] ?? 0, buf[bOff + 2] ?? 0);\n buf[dOff + 3] = Math.max(buf[aOff + 3] ?? 0, buf[bOff + 3] ?? 0);\n buf[dOff + 4] = Math.max(buf[aOff + 4] ?? 0, buf[bOff + 4] ?? 0);\n buf[dOff + 5] = Math.max(buf[aOff + 5] ?? 0, buf[bOff + 5] ?? 0);\n}\n\n/**\n * Test whether two AABBs (by index in the same pool) intersect.\n */\nexport function aabbIntersects(pool: AABBPool, a: number, b: number): boolean {\n const aOff = a * AABB_STRIDE;\n const bOff = b * AABB_STRIDE;\n // Access via get() to stay safe with bounds, but direct buffer access is\n // equally valid when performance is critical.\n const buf = (pool as unknown as { buffer: Float32Array }).buffer;\n return (\n (buf[aOff] ?? 0) <= (buf[bOff + 3] ?? 0) &&\n (buf[aOff + 3] ?? 0) >= (buf[bOff] ?? 0) &&\n (buf[aOff + 1] ?? 0) <= (buf[bOff + 4] ?? 0) &&\n (buf[aOff + 4] ?? 0) >= (buf[bOff + 1] ?? 0) &&\n (buf[aOff + 2] ?? 0) <= (buf[bOff + 5] ?? 0) &&\n (buf[aOff + 5] ?? 0) >= (buf[bOff + 2] ?? 0)\n );\n}\n","/**\n * Ray stored as origin + direction (unit vector).\n * Layout: [ox, oy, oz, dx, dy, dz]\n */\nexport const RAY_STRIDE = 6;\n\n/** A pool-backed, zero-GC Ray store.\n *\n * When a `SharedArrayBuffer` is provided, the pool's data is accessible from\n * multiple threads (e.g. a Web Worker) with no copying.\n */\nexport class RayPool {\n private readonly buffer: Float32Array;\n private count: number = 0;\n\n constructor(capacity: number, sharedBuffer?: SharedArrayBuffer) {\n this.buffer = sharedBuffer\n ? new Float32Array(sharedBuffer, 0, capacity * RAY_STRIDE)\n : new Float32Array(capacity * RAY_STRIDE);\n }\n\n /**\n * Create a RayPool backed by a new `SharedArrayBuffer`.\n * Both the pool and the underlying `SharedArrayBuffer` are returned so the\n * caller can transfer the buffer to a `Worker` via `postMessage`.\n */\n static createShared(capacity: number): { pool: RayPool; sab: SharedArrayBuffer } {\n const sab = new SharedArrayBuffer(capacity * RAY_STRIDE * Float32Array.BYTES_PER_ELEMENT);\n return { pool: new RayPool(capacity, sab), sab };\n }\n\n /** Allocate a new Ray slot and return its index. */\n allocate(): number {\n const index = this.count;\n this.count += 1;\n return index;\n }\n\n /** Set origin and direction of a ray at the given index. */\n set(\n index: number,\n ox: number,\n oy: number,\n oz: number,\n dx: number,\n dy: number,\n dz: number,\n ): void {\n const offset = index * RAY_STRIDE;\n this.buffer[offset] = ox;\n this.buffer[offset + 1] = oy;\n this.buffer[offset + 2] = oz;\n this.buffer[offset + 3] = dx;\n this.buffer[offset + 4] = dy;\n this.buffer[offset + 5] = dz;\n }\n\n /** Read a single component value from the buffer. */\n get(index: number, component: number): number {\n return this.buffer[index * RAY_STRIDE + component] ?? 0;\n }\n\n /** Returns the number of allocated Rays. */\n get size(): number {\n return this.count;\n }\n\n /** Reset the pool (all allocations freed, no GC). */\n reset(): void {\n this.count = 0;\n }\n}\n\n/**\n * Optimized branchless slab-method ray–AABB intersection test.\n *\n * Operates purely on flat Float32Arrays with no per-axis direction branches.\n * When a direction component is zero, IEEE 754 produces ±Infinity for the\n * reciprocal, which propagates correctly through the min/max slab computation:\n * - origin inside the slab → [−∞, +∞] interval (no constraint)\n * - origin outside the slab → either [+∞, +∞] or [−∞, −∞] → forces a miss\n *\n * Returns the parametric hit distance `t` (>= 0) if the ray intersects the\n * AABB, or -1 if there is no intersection (miss or box entirely behind origin).\n */\nexport function rayIntersectsAABB(\n rayBuf: Float32Array,\n rayOffset: number,\n aabbBuf: Float32Array,\n aabbOffset: number,\n): number {\n const ox = rayBuf[rayOffset] ?? 0;\n const oy = rayBuf[rayOffset + 1] ?? 0;\n const oz = rayBuf[rayOffset + 2] ?? 0;\n const idx = 1 / (rayBuf[rayOffset + 3] ?? 0);\n const idy = 1 / (rayBuf[rayOffset + 4] ?? 0);\n const idz = 1 / (rayBuf[rayOffset + 5] ?? 0);\n\n const minX = aabbBuf[aabbOffset] ?? 0;\n const minY = aabbBuf[aabbOffset + 1] ?? 0;\n const minZ = aabbBuf[aabbOffset + 2] ?? 0;\n const maxX = aabbBuf[aabbOffset + 3] ?? 0;\n const maxY = aabbBuf[aabbOffset + 4] ?? 0;\n const maxZ = aabbBuf[aabbOffset + 5] ?? 0;\n\n // Branchless slab method: per-axis near/far t values.\n const t1x = (minX - ox) * idx;\n const t2x = (maxX - ox) * idx;\n const t1y = (minY - oy) * idy;\n const t2y = (maxY - oy) * idy;\n const t1z = (minZ - oz) * idz;\n const t2z = (maxZ - oz) * idz;\n\n const tmin = Math.max(Math.min(t1x, t2x), Math.min(t1y, t2y), Math.min(t1z, t2z));\n const tmax = Math.min(Math.max(t1x, t2x), Math.max(t1y, t2y), Math.max(t1z, t2z));\n\n if (tmax < 0 || !(tmin <= tmax)) return -1;\n return tmin >= 0 ? tmin : tmax;\n}\n","import { OctreeNodePool, MAX_OBJECTS_PER_NODE, NODE_STRIDE, NODE_AABB_OFFSET } from './octree-node.js';\nimport { AABBPool, AABB_STRIDE } from './aabb.js';\nimport { rayIntersectsAABB } from './ray.js';\n\n/**\n * Returns true when the AABB stored at `bufOffset` in `buf` overlaps the\n * axis-aligned box defined by the six query scalars.\n */\nfunction aabbOverlapsBox(\n buf: Float32Array,\n bufOffset: number,\n qMinX: number,\n qMinY: number,\n qMinZ: number,\n qMaxX: number,\n qMaxY: number,\n qMaxZ: number,\n): boolean {\n return (\n buf[bufOffset]! <= qMaxX &&\n buf[bufOffset + 3]! >= qMinX &&\n buf[bufOffset + 1]! <= qMaxY &&\n buf[bufOffset + 4]! >= qMinY &&\n buf[bufOffset + 2]! <= qMaxZ &&\n buf[bufOffset + 5]! >= qMinZ\n );\n}\n\n/**\n * Octree with insertion and automatic subdivision.\n *\n * - When a leaf node's object count reaches MAX_OBJECTS_PER_NODE it is\n * subdivided into 8 axis-aligned child octants.\n * - An inserted AABB that fits entirely within a child octant is pushed down.\n * - An AABB that straddles a boundary is kept in the nearest ancestor that\n * fully contains it.\n */\nexport class Octree {\n private readonly nodePool: OctreeNodePool;\n private readonly aabbPool: AABBPool;\n private readonly root: number;\n /** Tracks which node each object (by AABBPool index) is currently stored in. */\n private readonly objectNodeMap: Map<number, number> = new Map();\n /** Pre-allocated traversal stack reused across raycast calls to avoid GC pressure. */\n private readonly _stack: number[] = [];\n\n constructor(nodePool: OctreeNodePool, aabbPool: AABBPool) {\n this.nodePool = nodePool;\n this.aabbPool = aabbPool;\n this.root = nodePool.allocate();\n }\n\n /** The index of the root node in the underlying OctreeNodePool. */\n get rootIndex(): number {\n return this.root;\n }\n\n /** Set the world-space AABB that the root node covers. */\n setBounds(\n minX: number,\n minY: number,\n minZ: number,\n maxX: number,\n maxY: number,\n maxZ: number,\n ): void {\n this.nodePool.setAABB(this.root, minX, minY, minZ, maxX, maxY, maxZ);\n }\n\n /** Insert the AABB at `objectIndex` (in the AABBPool) into the tree. */\n insert(objectIndex: number): void {\n this.insertIntoNode(this.root, objectIndex);\n }\n\n /**\n * Update the AABB at `objectIndex` with new bounds and reposition it in the\n * tree without rebuilding. If the new bounds still fit in the current node\n * the object stays there; otherwise it is removed and re-inserted from the\n * lowest ancestor whose bounds fully contain the new AABB.\n */\n update(\n objectIndex: number,\n newMinX: number,\n newMinY: number,\n newMinZ: number,\n newMaxX: number,\n newMaxY: number,\n newMaxZ: number,\n ): void {\n const np = this.nodePool;\n const ap = this.aabbPool;\n\n // 1. Update the AABB data.\n ap.set(objectIndex, newMinX, newMinY, newMinZ, newMaxX, newMaxY, newMaxZ);\n\n // 2. Find current node.\n const currentNode = this.objectNodeMap.get(objectIndex);\n if (currentNode === undefined) return;\n\n // 3. If the new bounds still fit in the current node, nothing to move.\n if (this.fitsInNode(objectIndex, currentNode)) return;\n\n // 4. Remove from current node.\n np.removeObject(currentNode, objectIndex);\n this.objectNodeMap.delete(objectIndex);\n\n // 5. Walk up to find the lowest ancestor that fully contains the new bounds.\n let ancestorNode = np.getParent(currentNode);\n while (ancestorNode !== -1 && !this.fitsInNode(objectIndex, ancestorNode)) {\n ancestorNode = np.getParent(ancestorNode);\n }\n // If no ancestor fits (shouldn't happen for a well-formed tree), use root.\n if (ancestorNode === -1) ancestorNode = this.root;\n\n // 6. Re-insert downward from the ancestor.\n this.insertIntoNode(ancestorNode, objectIndex);\n }\n\n /**\n * Cast a ray through the octree and return the closest intersecting object.\n *\n * Uses an iterative stack traversal (pre-allocated, no recursion) to avoid\n * GC pressure. Only descends into child nodes whose AABBs are intersected by\n * the ray, providing efficient pruning of non-intersecting subtrees.\n *\n * @param rayBuf Float32Array containing the ray data [ox,oy,oz,dx,dy,dz].\n * @param rayOffset Element offset (in floats) within `rayBuf` to the ray's origin.\n * @returns The closest `{ objectIndex, t }` hit, or `null` if nothing is hit.\n */\n raycast(\n rayBuf: Float32Array,\n rayOffset: number,\n ): { objectIndex: number; t: number } | null {\n const np = this.nodePool;\n // Access the underlying flat buffers directly for zero-GC intersection tests.\n const npBuf = (np as unknown as { buffer: Float32Array }).buffer;\n const apBuf = (this.aabbPool as unknown as { buffer: Float32Array }).buffer;\n\n // Early-out: test the root AABB before touching any object data.\n if (rayIntersectsAABB(rayBuf, rayOffset, npBuf, this.root * NODE_STRIDE + NODE_AABB_OFFSET) < 0) {\n return null;\n }\n\n // Iterative DFS using the pre-allocated stack.\n this._stack.length = 0;\n this._stack.push(this.root);\n\n let closestT = Infinity;\n let closestIndex = -1;\n\n while (this._stack.length > 0) {\n const nodeIdx = this._stack.pop()!;\n\n // Test every object stored at this node level.\n const objCount = np.getObjectCount(nodeIdx);\n for (let i = 0; i < objCount; i++) {\n const objIdx = np.getObject(nodeIdx, i);\n const t = rayIntersectsAABB(rayBuf, rayOffset, apBuf, objIdx * AABB_STRIDE);\n if (t >= 0 && t < closestT) {\n closestT = t;\n closestIndex = objIdx;\n }\n }\n\n // Push only children whose AABB the ray actually intersects.\n const firstChild = np.getFirstChild(nodeIdx);\n if (firstChild !== -1) {\n for (let i = 0; i < 8; i++) {\n const childIdx = firstChild + i;\n if (rayIntersectsAABB(rayBuf, rayOffset, npBuf, childIdx * NODE_STRIDE + NODE_AABB_OFFSET) >= 0) {\n this._stack.push(childIdx);\n }\n }\n }\n }\n\n if (closestIndex === -1) return null;\n return { objectIndex: closestIndex, t: closestT };\n }\n\n /**\n * Query the octree for all objects whose AABB overlaps the given axis-aligned\n * box region and return their indices (in the AABBPool).\n *\n * Uses the same pre-allocated iterative stack as `raycast` to avoid GC\n * pressure. Descends only into child nodes whose AABBs overlap the query box,\n * pruning non-intersecting subtrees.\n *\n * @param minX Minimum X of the query box.\n * @param minY Minimum Y of the query box.\n * @param minZ Minimum Z of the query box.\n * @param maxX Maximum X of the query box.\n * @param maxY Maximum Y of the query box.\n * @param maxZ Maximum Z of the query box.\n * @returns Array of AABBPool indices for every object that overlaps the box.\n */\n queryBox(\n minX: number,\n minY: number,\n minZ: number,\n maxX: number,\n maxY: number,\n maxZ: number,\n ): number[] {\n const np = this.nodePool;\n const npBuf = (np as unknown as { buffer: Float32Array }).buffer;\n const apBuf = (this.aabbPool as unknown as { buffer: Float32Array }).buffer;\n\n const results: number[] = [];\n\n // Early-out: if the query box doesn't overlap the root, nothing to do.\n if (!aabbOverlapsBox(npBuf, this.root * NODE_STRIDE + NODE_AABB_OFFSET, minX, minY, minZ, maxX, maxY, maxZ)) {\n return results;\n }\n\n // Iterative DFS using the pre-allocated stack.\n this._stack.length = 0;\n this._stack.push(this.root);\n\n while (this._stack.length > 0) {\n const nodeIdx = this._stack.pop()!;\n\n // Test every object stored at this node level.\n const objCount = np.getObjectCount(nodeIdx);\n for (let i = 0; i < objCount; i++) {\n const objIdx = np.getObject(nodeIdx, i);\n if (aabbOverlapsBox(apBuf, objIdx * AABB_STRIDE, minX, minY, minZ, maxX, maxY, maxZ)) {\n results.push(objIdx);\n }\n }\n\n // Push only children whose AABB overlaps the query box.\n const firstChild = np.getFirstChild(nodeIdx);\n if (firstChild !== -1) {\n for (let i = 0; i < 8; i++) {\n const childIdx = firstChild + i;\n if (aabbOverlapsBox(npBuf, childIdx * NODE_STRIDE + NODE_AABB_OFFSET, minX, minY, minZ, maxX, maxY, maxZ)) {\n this._stack.push(childIdx);\n }\n }\n }\n }\n\n return results;\n }\n\n // ── Private helpers ──────────────────────────────────────────────────────\n\n private insertIntoNode(nodeIdx: number, objectIndex: number): void {\n const np = this.nodePool;\n const firstChild = np.getFirstChild(nodeIdx);\n\n if (firstChild !== -1) {\n // Internal node: try to push the object into a fitting child octant.\n for (let i = 0; i < 8; i++) {\n if (this.fitsInNode(objectIndex, firstChild + i)) {\n this.insertIntoNode(firstChild + i, objectIndex);\n return;\n }\n }\n // Object straddles one or more boundaries – keep it at this level.\n np.addObject(nodeIdx, objectIndex);\n this.objectNodeMap.set(objectIndex, nodeIdx);\n return;\n }\n\n // Leaf node.\n const count = np.getObjectCount(nodeIdx);\n if (count < MAX_OBJECTS_PER_NODE) {\n np.addObject(nodeIdx, objectIndex);\n this.objectNodeMap.set(objectIndex, nodeIdx);\n } else {\n // Capacity reached: subdivide, then retry the insertion.\n this.subdivide(nodeIdx);\n this.insertIntoNode(nodeIdx, objectIndex);\n }\n }\n\n /** Split a leaf node into 8 children and redistribute its objects. */\n private subdivide(nodeIdx: number): void {\n const np = this.nodePool;\n\n const minX = np.getAABB(nodeIdx, 0);\n const minY = np.getAABB(nodeIdx, 1);\n const minZ = np.getAABB(nodeIdx, 2);\n const maxX = np.getAABB(nodeIdx, 3);\n const maxY = np.getAABB(nodeIdx, 4);\n const maxZ = np.getAABB(nodeIdx, 5);\n\n const midX = (minX + maxX) / 2;\n const midY = (minY + maxY) / 2;\n const midZ = (minZ + maxZ) / 2;\n\n // Allocate 8 consecutive child nodes (guarantees contiguous indices).\n const firstChild = np.allocate();\n for (let i = 1; i < 8; i++) {\n np.allocate();\n }\n np.setFirstChild(nodeIdx, firstChild);\n\n // Assign each child its octant AABB.\n // Bit decomposition: bit-0 → X half, bit-1 → Y half, bit-2 → Z half.\n for (let i = 0; i < 8; i++) {\n const childIdx = firstChild + i;\n const cMinX = (i & 1) === 0 ? minX : midX;\n const cMaxX = (i & 1) === 0 ? midX : maxX;\n const cMinY = (i & 2) === 0 ? minY : midY;\n const cMaxY = (i & 2) === 0 ? midY : maxY;\n const cMinZ = (i & 4) === 0 ? minZ : midZ;\n const cMaxZ = (i & 4) === 0 ? midZ : maxZ;\n np.setAABB(childIdx, cMinX, cMinY, cMinZ, cMaxX, cMaxY, cMaxZ);\n np.setParent(childIdx, nodeIdx);\n }\n\n // Redistribute the existing objects.\n const count = np.getObjectCount(nodeIdx);\n const saved: number[] = [];\n for (let i = 0; i < count; i++) {\n saved.push(np.getObject(nodeIdx, i));\n }\n np.clearObjects(nodeIdx);\n\n for (const obj of saved) {\n // Use insertIntoNode so objectNodeMap stays consistent.\n this.objectNodeMap.delete(obj);\n this.insertIntoNode(nodeIdx, obj);\n }\n }\n\n /** Returns true when the AABB at `objectIndex` is fully contained by `nodeIdx`. */\n private fitsInNode(objectIndex: number, nodeIdx: number): boolean {\n const np = this.nodePool;\n const ap = this.aabbPool;\n return (\n ap.get(objectIndex, 0) >= np.getAABB(nodeIdx, 0) &&\n ap.get(objectIndex, 1) >= np.getAABB(nodeIdx, 1) &&\n ap.get(objectIndex, 2) >= np.getAABB(nodeIdx, 2) &&\n ap.get(objectIndex, 3) <= np.getAABB(nodeIdx, 3) &&\n ap.get(objectIndex, 4) <= np.getAABB(nodeIdx, 4) &&\n ap.get(objectIndex, 5) <= np.getAABB(nodeIdx, 5)\n );\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/lidar-worker.ts","../src/octree-node.ts","../src/aabb.ts","../src/ray.ts","../src/octree.ts"],"sourcesContent":["/**\n * LiDAR Web Worker\n *\n * Handles heavy raycasting (LiDAR sweep logic) off the main thread.\n *\n * Protocol\n * --------\n * 1. Main thread sends one `LidarInitMessage` to set up shared buffers and\n * world bounds. The worker replies with `{ type: 'ready' }`.\n *\n * 2. For each sweep the main thread:\n * a. Writes up-to-date object AABB data into `aabbsSab`\n * (6 floats per object: minX, minY, minZ, maxX, maxY, maxZ).\n * b. Sends a `LidarSweepMessage` with the current `objectCount`.\n *\n * 3. The worker reads the transforms, inserts/updates objects in its local\n * Octree, casts every ray from `raysSab`, and writes results to\n * `resultsSab` (2 floats per ray: objectIndex, t; –1/–1 for a miss).\n * It then replies with `{ type: 'done', rayCount }`.\n *\n * SharedArrayBuffer layout\n * ------------------------\n * aabbsSab : Float32Array – objectCapacity × AABB_STRIDE (6) floats\n * nodesSab : Float32Array – nodeCapacity × NODE_STRIDE floats\n * raysSab : Float32Array – rayCount × RAY_STRIDE (6) floats\n * resultsSab: Float32Array – rayCount × 2 floats\n * [objectIndex (float), t (float)] per ray\n */\n\nimport { OctreeNodePool } from './octree-node.js';\nimport { AABBPool } from './aabb.js';\nimport { RAY_STRIDE } from './ray.js';\nimport { Octree } from './octree.js';\n\n// ── Public message types ──────────────────────────────────────────────────────\n\n/** Sent once from the main thread to initialise the shared buffers. */\nexport interface LidarInitMessage {\n type: 'init';\n /** SharedArrayBuffer for AABB data (objectCapacity × 6 floats). Written by the main thread. */\n aabbsSab: SharedArrayBuffer;\n /** SharedArrayBuffer for Octree node data (nodeCapacity × NODE_STRIDE floats). Managed by the worker. */\n nodesSab: SharedArrayBuffer;\n /** SharedArrayBuffer for ray data (rayCount × 6 floats). Written by the main thread (or set once). */\n raysSab: SharedArrayBuffer;\n /** SharedArrayBuffer for raycast results (rayCount × 2 floats). Written by the worker. */\n resultsSab: SharedArrayBuffer;\n /** Maximum number of objects the AABBPool can hold. */\n objectCapacity: number;\n /** Maximum number of Octree nodes the NodePool can hold. */\n nodeCapacity: number;\n /** Number of rays in the LiDAR sweep. */\n rayCount: number;\n /** World-space bounds for the root octree node. */\n worldMinX: number;\n worldMinY: number;\n worldMinZ: number;\n worldMaxX: number;\n worldMaxY: number;\n worldMaxZ: number;\n}\n\n/** Sent each frame to trigger a LiDAR sweep. */\nexport interface LidarSweepMessage {\n type: 'sweep';\n /** Number of active objects whose AABB data has been written to `aabbsSab`. */\n objectCount: number;\n}\n\nexport type LidarWorkerInMessage = LidarInitMessage | LidarSweepMessage;\n\n/** Posted by the worker after initialisation succeeds. */\nexport interface LidarReadyMessage {\n type: 'ready';\n}\n\n/** Posted by the worker after a sweep finishes. */\nexport interface LidarDoneMessage {\n type: 'done';\n /** Number of rays that were cast (equals the `rayCount` given in `init`). */\n rayCount: number;\n}\n\nexport type LidarWorkerOutMessage = LidarReadyMessage | LidarDoneMessage;\n\n// ── Processor (pure logic, no worker-global bindings) ────────────────────────\n\n/**\n * Stateful LiDAR sweep processor.\n *\n * Exported for direct use in tests and advanced integrations. The bottom of\n * this module wires it to the Web Worker global scope automatically when the\n * script runs inside a worker.\n */\nexport function createLidarProcessor(): {\n init(msg: LidarInitMessage): LidarReadyMessage;\n sweep(msg: LidarSweepMessage): LidarDoneMessage;\n} {\n let octree: Octree | null = null;\n let aabbPool: AABBPool | null = null;\n let raysBuf: Float32Array | null = null;\n let resultsBuf: Float32Array | null = null;\n let totalRayCount = 0;\n /** Tracks how many objects have been inserted into the local octree. */\n let insertedObjectCount = 0;\n\n return {\n /** Initialise shared buffers and create the Octree. */\n init(msg: LidarInitMessage): LidarReadyMessage {\n const nodePool = new OctreeNodePool(msg.nodeCapacity, msg.nodesSab);\n aabbPool = new AABBPool(msg.objectCapacity, msg.aabbsSab);\n raysBuf = new Float32Array(msg.raysSab);\n resultsBuf = new Float32Array(msg.resultsSab);\n totalRayCount = msg.rayCount;\n insertedObjectCount = 0;\n\n octree = new Octree(nodePool, aabbPool);\n octree.setBounds(\n msg.worldMinX, msg.worldMinY, msg.worldMinZ,\n msg.worldMaxX, msg.worldMaxY, msg.worldMaxZ,\n );\n\n return { type: 'ready' };\n },\n\n /**\n * Read current object AABB data from the shared buffer, update the Octree,\n * cast every sweep ray, and write results to the results buffer.\n */\n sweep(msg: LidarSweepMessage): LidarDoneMessage {\n if (octree === null || aabbPool === null || raysBuf === null || resultsBuf === null) {\n throw new Error('LidarProcessor: sweep called before init');\n }\n\n const objectCount = msg.objectCount;\n\n // Ensure the pool's allocation count reflects all active objects so that\n // the pool's `size` getter stays accurate (the buffer memory already exists\n // in the SAB – allocate() only increments the internal counter).\n while (aabbPool.size < objectCount) {\n aabbPool.allocate();\n }\n\n // Insert new objects or reposition existing ones in the octree.\n for (let i = 0; i < objectCount; i++) {\n const minX = aabbPool.get(i, 0);\n const minY = aabbPool.get(i, 1);\n const minZ = aabbPool.get(i, 2);\n const maxX = aabbPool.get(i, 3);\n const maxY = aabbPool.get(i, 4);\n const maxZ = aabbPool.get(i, 5);\n\n if (i < insertedObjectCount) {\n // Already in the tree – reposition without full rebuild.\n octree.update(i, minX, minY, minZ, maxX, maxY, maxZ);\n } else {\n // First appearance: insert using the bounds already in the SAB.\n octree.insert(i);\n insertedObjectCount++;\n }\n }\n\n // LiDAR sweep: cast each ray and write the closest hit (or miss) to\n // the results buffer. Layout: [objectIndex, t] per ray (–1/–1 = miss).\n for (let r = 0; r < totalRayCount; r++) {\n const hit = octree.raycast(raysBuf, r * RAY_STRIDE);\n if (hit !== null) {\n resultsBuf[r * 2] = hit.objectIndex;\n resultsBuf[r * 2 + 1] = hit.t;\n } else {\n resultsBuf[r * 2] = -1;\n resultsBuf[r * 2 + 1] = -1;\n }\n }\n\n return { type: 'done', rayCount: totalRayCount };\n },\n };\n}\n\n// ── Web Worker binding ────────────────────────────────────────────────────────\n// Automatically wire the processor to the worker's global message handler when\n// this script is loaded inside a Web Worker context (browser or Node.js worker).\n\ntype WorkerGlobalSelf = {\n onmessage: ((event: { data: LidarWorkerInMessage }) => void) | null;\n postMessage(data: LidarWorkerOutMessage): void;\n};\n\n// `postMessage` is available on the worker global but not in a regular Node.js\n// module context, so check for it before binding.\nconst globalScope = globalThis as Record<string, unknown>;\nif (typeof globalScope['postMessage'] === 'function' && typeof globalScope['onmessage'] !== 'undefined') {\n const workerSelf = globalThis as unknown as WorkerGlobalSelf;\n const processor = createLidarProcessor();\n\n workerSelf.onmessage = (event) => {\n const msg = event.data;\n if (msg.type === 'init') {\n workerSelf.postMessage(processor.init(msg));\n } else if (msg.type === 'sweep') {\n workerSelf.postMessage(processor.sweep(msg));\n }\n };\n}\n","/**\n * Octree node stored as a flat Float32Array slice.\n *\n * Layout per node (NODE_STRIDE floats):\n * [0..5] – AABB (minX, minY, minZ, maxX, maxY, maxZ)\n * [6] – firstChild index (-1 = leaf / no children)\n * [7] – parent index (-1 = root)\n * [8] – object count\n * [9..] – object indices (MAX_OBJECTS_PER_NODE slots)\n */\n\nexport const NODE_AABB_OFFSET = 0;\nexport const NODE_FIRST_CHILD_OFFSET = 6;\nexport const NODE_PARENT_OFFSET = 7;\nexport const NODE_OBJECT_COUNT_OFFSET = 8;\nexport const NODE_OBJECTS_OFFSET = 9;\nexport const MAX_OBJECTS_PER_NODE = 64;\nexport const NODE_STRIDE = NODE_OBJECTS_OFFSET + MAX_OBJECTS_PER_NODE;\n\n/**\n * A pool-backed, zero-GC Octree node store.\n * Each node occupies NODE_STRIDE floats in the underlying Float32Array buffer.\n *\n * When a `SharedArrayBuffer` is provided, the pool's data is accessible from\n * multiple threads (e.g. a Web Worker) with no copying.\n */\nexport class OctreeNodePool {\n private readonly buffer: Float32Array;\n private count: number = 0;\n\n constructor(capacity: number, sharedBuffer?: SharedArrayBuffer) {\n this.buffer = sharedBuffer\n ? new Float32Array(sharedBuffer, 0, capacity * NODE_STRIDE)\n : new Float32Array(capacity * NODE_STRIDE);\n }\n\n /**\n * Create an OctreeNodePool backed by a new `SharedArrayBuffer`.\n * Both the pool and the underlying `SharedArrayBuffer` are returned so the\n * caller can transfer the buffer to a `Worker` via `postMessage`.\n */\n static createShared(capacity: number): { pool: OctreeNodePool; sab: SharedArrayBuffer } {\n const sab = new SharedArrayBuffer(capacity * NODE_STRIDE * Float32Array.BYTES_PER_ELEMENT);\n return { pool: new OctreeNodePool(capacity, sab), sab };\n }\n\n /** Allocate a new node slot, initialise sentinel values, and return its index. */\n allocate(): number {\n const index = this.count;\n const offset = index * NODE_STRIDE;\n // Initialise sentinel values for child/parent links and object count.\n this.buffer[offset + NODE_FIRST_CHILD_OFFSET] = -1;\n this.buffer[offset + NODE_PARENT_OFFSET] = -1;\n this.buffer[offset + NODE_OBJECT_COUNT_OFFSET] = 0;\n this.count += 1;\n return index;\n }\n\n /** Returns the number of allocated nodes. */\n get size(): number {\n return this.count;\n }\n\n /** Reset the pool (all allocations freed, no GC). */\n reset(): void {\n this.count = 0;\n }\n\n // ── AABB ──────────────────────────────────────────────────────────────────\n\n /** Set the AABB bounds for the node at the given index. */\n setAABB(\n index: number,\n minX: number,\n minY: number,\n minZ: number,\n maxX: number,\n maxY: number,\n maxZ: number,\n ): void {\n const off = index * NODE_STRIDE + NODE_AABB_OFFSET;\n this.buffer[off] = minX;\n this.buffer[off + 1] = minY;\n this.buffer[off + 2] = minZ;\n this.buffer[off + 3] = maxX;\n this.buffer[off + 4] = maxY;\n this.buffer[off + 5] = maxZ;\n }\n\n /** Read a single AABB component (0–5) from the node at the given index. */\n getAABB(index: number, component: number): number {\n return this.buffer[index * NODE_STRIDE + NODE_AABB_OFFSET + component] ?? 0;\n }\n\n // ── First-child link ──────────────────────────────────────────────────────\n\n /** Set the first-child index for the node (-1 = leaf). */\n setFirstChild(index: number, childIndex: number): void {\n this.buffer[index * NODE_STRIDE + NODE_FIRST_CHILD_OFFSET] = childIndex;\n }\n\n /** Get the first-child index for the node (-1 = leaf). */\n getFirstChild(index: number): number {\n return this.buffer[index * NODE_STRIDE + NODE_FIRST_CHILD_OFFSET] ?? -1;\n }\n\n // ── Parent link ───────────────────────────────────────────────────────────\n\n /** Set the parent index for the node (-1 = root). */\n setParent(index: number, parentIndex: number): void {\n this.buffer[index * NODE_STRIDE + NODE_PARENT_OFFSET] = parentIndex;\n }\n\n /** Get the parent index for the node (-1 = root). */\n getParent(index: number): number {\n return this.buffer[index * NODE_STRIDE + NODE_PARENT_OFFSET] ?? -1;\n }\n\n // ── Object pointers ───────────────────────────────────────────────────────\n\n /** Return the number of objects stored in the node at the given index. */\n getObjectCount(index: number): number {\n return this.buffer[index * NODE_STRIDE + NODE_OBJECT_COUNT_OFFSET] ?? 0;\n }\n\n /**\n * Append an object index to the node's object list.\n * Throws a RangeError when MAX_OBJECTS_PER_NODE is exceeded.\n */\n addObject(index: number, objectIndex: number): void {\n const countOff = index * NODE_STRIDE + NODE_OBJECT_COUNT_OFFSET;\n const count = this.buffer[countOff] ?? 0;\n if (count >= MAX_OBJECTS_PER_NODE) {\n throw new RangeError(\n `OctreeNodePool.addObject: node ${index} already contains MAX_OBJECTS_PER_NODE (${MAX_OBJECTS_PER_NODE}) objects`,\n );\n }\n this.buffer[index * NODE_STRIDE + NODE_OBJECTS_OFFSET + count] = objectIndex;\n this.buffer[countOff] = count + 1;\n }\n\n /** Return the object index stored at slot `slot` in the given node. */\n getObject(index: number, slot: number): number {\n return this.buffer[index * NODE_STRIDE + NODE_OBJECTS_OFFSET + slot] ?? 0;\n }\n\n /** Reset the object count of the given node to zero (does not zero the slots). */\n clearObjects(index: number): void {\n this.buffer[index * NODE_STRIDE + NODE_OBJECT_COUNT_OFFSET] = 0;\n }\n\n /**\n * Remove a single object index from the node's object list.\n * Uses swap-with-last to avoid shifting. Returns true if found and removed.\n */\n removeObject(index: number, objectIndex: number): boolean {\n const countOff = index * NODE_STRIDE + NODE_OBJECT_COUNT_OFFSET;\n const count = this.buffer[countOff] ?? 0;\n const baseOff = index * NODE_STRIDE + NODE_OBJECTS_OFFSET;\n for (let i = 0; i < count; i++) {\n if (this.buffer[baseOff + i] === objectIndex) {\n // Swap with the last element, then decrement count.\n this.buffer[baseOff + i] = this.buffer[baseOff + count - 1] ?? 0;\n this.buffer[countOff] = count - 1;\n return true;\n }\n }\n return false;\n }\n}\n","/**\n * AABB (Axis-Aligned Bounding Box) stored as a flat Float32Array slice.\n * Layout: [minX, minY, minZ, maxX, maxY, maxZ]\n */\nexport const AABB_STRIDE = 6;\n\n/**\n * A pool-backed, zero-GC AABB store.\n * Each AABB occupies AABB_STRIDE floats in the underlying buffer.\n *\n * When a `SharedArrayBuffer` is provided, the pool's data is accessible from\n * multiple threads (e.g. a Web Worker) with no copying.\n */\nexport class AABBPool {\n private readonly buffer: Float32Array;\n private count: number = 0;\n\n constructor(capacity: number, sharedBuffer?: SharedArrayBuffer) {\n this.buffer = sharedBuffer\n ? new Float32Array(sharedBuffer, 0, capacity * AABB_STRIDE)\n : new Float32Array(capacity * AABB_STRIDE);\n }\n\n /**\n * Create an AABBPool backed by a new `SharedArrayBuffer`.\n * Both the pool and the underlying `SharedArrayBuffer` are returned so the\n * caller can transfer the buffer to a `Worker` via `postMessage`.\n */\n static createShared(capacity: number): { pool: AABBPool; sab: SharedArrayBuffer } {\n const sab = new SharedArrayBuffer(capacity * AABB_STRIDE * Float32Array.BYTES_PER_ELEMENT);\n return { pool: new AABBPool(capacity, sab), sab };\n }\n\n /** Allocate a new AABB slot and return its index. */\n allocate(): number {\n const index = this.count;\n this.count += 1;\n return index;\n }\n\n /** Set the bounds of an AABB at the given index. */\n set(\n index: number,\n minX: number,\n minY: number,\n minZ: number,\n maxX: number,\n maxY: number,\n maxZ: number,\n ): void {\n const offset = index * AABB_STRIDE;\n this.buffer[offset] = minX;\n this.buffer[offset + 1] = minY;\n this.buffer[offset + 2] = minZ;\n this.buffer[offset + 3] = maxX;\n this.buffer[offset + 4] = maxY;\n this.buffer[offset + 5] = maxZ;\n }\n\n /** Read a single component value from the buffer. */\n get(index: number, component: number): number {\n return this.buffer[index * AABB_STRIDE + component] ?? 0;\n }\n\n /** Returns the number of allocated AABBs. */\n get size(): number {\n return this.count;\n }\n\n /** Reset the pool (all allocations freed, no GC). */\n reset(): void {\n this.count = 0;\n }\n}\n\n/**\n * Expand AABB `a` in-place so it also fully contains AABB `b`.\n */\nexport function aabbExpand(pool: AABBPool, a: number, b: number): void {\n const buf = (pool as unknown as { buffer: Float32Array }).buffer;\n const aOff = a * AABB_STRIDE;\n const bOff = b * AABB_STRIDE;\n buf[aOff] = Math.min(buf[aOff] ?? 0, buf[bOff] ?? 0);\n buf[aOff + 1] = Math.min(buf[aOff + 1] ?? 0, buf[bOff + 1] ?? 0);\n buf[aOff + 2] = Math.min(buf[aOff + 2] ?? 0, buf[bOff + 2] ?? 0);\n buf[aOff + 3] = Math.max(buf[aOff + 3] ?? 0, buf[bOff + 3] ?? 0);\n buf[aOff + 4] = Math.max(buf[aOff + 4] ?? 0, buf[bOff + 4] ?? 0);\n buf[aOff + 5] = Math.max(buf[aOff + 5] ?? 0, buf[bOff + 5] ?? 0);\n}\n\n/**\n * Merge AABBs `a` and `b` into `dest` (a pre-allocated slot), storing their union.\n */\nexport function aabbMerge(pool: AABBPool, dest: number, a: number, b: number): void {\n const buf = (pool as unknown as { buffer: Float32Array }).buffer;\n const aOff = a * AABB_STRIDE;\n const bOff = b * AABB_STRIDE;\n const dOff = dest * AABB_STRIDE;\n buf[dOff] = Math.min(buf[aOff] ?? 0, buf[bOff] ?? 0);\n buf[dOff + 1] = Math.min(buf[aOff + 1] ?? 0, buf[bOff + 1] ?? 0);\n buf[dOff + 2] = Math.min(buf[aOff + 2] ?? 0, buf[bOff + 2] ?? 0);\n buf[dOff + 3] = Math.max(buf[aOff + 3] ?? 0, buf[bOff + 3] ?? 0);\n buf[dOff + 4] = Math.max(buf[aOff + 4] ?? 0, buf[bOff + 4] ?? 0);\n buf[dOff + 5] = Math.max(buf[aOff + 5] ?? 0, buf[bOff + 5] ?? 0);\n}\n\n/**\n * Test whether two AABBs (by index in the same pool) intersect.\n */\nexport function aabbIntersects(pool: AABBPool, a: number, b: number): boolean {\n const aOff = a * AABB_STRIDE;\n const bOff = b * AABB_STRIDE;\n // Access via get() to stay safe with bounds, but direct buffer access is\n // equally valid when performance is critical.\n const buf = (pool as unknown as { buffer: Float32Array }).buffer;\n return (\n (buf[aOff] ?? 0) <= (buf[bOff + 3] ?? 0) &&\n (buf[aOff + 3] ?? 0) >= (buf[bOff] ?? 0) &&\n (buf[aOff + 1] ?? 0) <= (buf[bOff + 4] ?? 0) &&\n (buf[aOff + 4] ?? 0) >= (buf[bOff + 1] ?? 0) &&\n (buf[aOff + 2] ?? 0) <= (buf[bOff + 5] ?? 0) &&\n (buf[aOff + 5] ?? 0) >= (buf[bOff + 2] ?? 0)\n );\n}\n","/**\n * Ray stored as origin + direction (unit vector).\n * Layout: [ox, oy, oz, dx, dy, dz]\n */\nexport const RAY_STRIDE = 6;\n\n/** A pool-backed, zero-GC Ray store.\n *\n * When a `SharedArrayBuffer` is provided, the pool's data is accessible from\n * multiple threads (e.g. a Web Worker) with no copying.\n */\nexport class RayPool {\n private readonly buffer: Float32Array;\n private count: number = 0;\n\n constructor(capacity: number, sharedBuffer?: SharedArrayBuffer) {\n this.buffer = sharedBuffer\n ? new Float32Array(sharedBuffer, 0, capacity * RAY_STRIDE)\n : new Float32Array(capacity * RAY_STRIDE);\n }\n\n /**\n * Create a RayPool backed by a new `SharedArrayBuffer`.\n * Both the pool and the underlying `SharedArrayBuffer` are returned so the\n * caller can transfer the buffer to a `Worker` via `postMessage`.\n */\n static createShared(capacity: number): { pool: RayPool; sab: SharedArrayBuffer } {\n const sab = new SharedArrayBuffer(capacity * RAY_STRIDE * Float32Array.BYTES_PER_ELEMENT);\n return { pool: new RayPool(capacity, sab), sab };\n }\n\n /** Allocate a new Ray slot and return its index. */\n allocate(): number {\n const index = this.count;\n this.count += 1;\n return index;\n }\n\n /** Set origin and direction of a ray at the given index. */\n set(\n index: number,\n ox: number,\n oy: number,\n oz: number,\n dx: number,\n dy: number,\n dz: number,\n ): void {\n const offset = index * RAY_STRIDE;\n this.buffer[offset] = ox;\n this.buffer[offset + 1] = oy;\n this.buffer[offset + 2] = oz;\n this.buffer[offset + 3] = dx;\n this.buffer[offset + 4] = dy;\n this.buffer[offset + 5] = dz;\n }\n\n /** Read a single component value from the buffer. */\n get(index: number, component: number): number {\n return this.buffer[index * RAY_STRIDE + component] ?? 0;\n }\n\n /** Returns the number of allocated Rays. */\n get size(): number {\n return this.count;\n }\n\n /** Reset the pool (all allocations freed, no GC). */\n reset(): void {\n this.count = 0;\n }\n}\n\n/**\n * Optimized branchless slab-method ray–AABB intersection test.\n *\n * Operates purely on flat Float32Arrays with no per-axis direction branches.\n * When a direction component is zero, IEEE 754 produces ±Infinity for the\n * reciprocal, which propagates correctly through the min/max slab computation:\n * - origin inside the slab → [−∞, +∞] interval (no constraint)\n * - origin outside the slab → either [+∞, +∞] or [−∞, −∞] → forces a miss\n *\n * Returns the parametric hit distance `t` (>= 0) if the ray intersects the\n * AABB, or -1 if there is no intersection (miss or box entirely behind origin).\n */\nexport function rayIntersectsAABB(\n rayBuf: Float32Array,\n rayOffset: number,\n aabbBuf: Float32Array,\n aabbOffset: number,\n): number {\n const ox = rayBuf[rayOffset] ?? 0;\n const oy = rayBuf[rayOffset + 1] ?? 0;\n const oz = rayBuf[rayOffset + 2] ?? 0;\n const idx = 1 / (rayBuf[rayOffset + 3] ?? 0);\n const idy = 1 / (rayBuf[rayOffset + 4] ?? 0);\n const idz = 1 / (rayBuf[rayOffset + 5] ?? 0);\n\n const minX = aabbBuf[aabbOffset] ?? 0;\n const minY = aabbBuf[aabbOffset + 1] ?? 0;\n const minZ = aabbBuf[aabbOffset + 2] ?? 0;\n const maxX = aabbBuf[aabbOffset + 3] ?? 0;\n const maxY = aabbBuf[aabbOffset + 4] ?? 0;\n const maxZ = aabbBuf[aabbOffset + 5] ?? 0;\n\n // Branchless slab method: per-axis near/far t values.\n const t1x = (minX - ox) * idx;\n const t2x = (maxX - ox) * idx;\n const t1y = (minY - oy) * idy;\n const t2y = (maxY - oy) * idy;\n const t1z = (minZ - oz) * idz;\n const t2z = (maxZ - oz) * idz;\n\n const tmin = Math.max(Math.min(t1x, t2x), Math.min(t1y, t2y), Math.min(t1z, t2z));\n const tmax = Math.min(Math.max(t1x, t2x), Math.max(t1y, t2y), Math.max(t1z, t2z));\n\n if (tmax < 0 || !(tmin <= tmax)) return -1;\n return tmin >= 0 ? tmin : tmax;\n}\n","import { OctreeNodePool, MAX_OBJECTS_PER_NODE, NODE_STRIDE, NODE_AABB_OFFSET } from './octree-node.js';\nimport { AABBPool, AABB_STRIDE } from './aabb.js';\nimport { rayIntersectsAABB } from './ray.js';\n\n/**\n * Returns true when the AABB stored at `bufOffset` in `buf` overlaps the\n * axis-aligned box defined by the six query scalars.\n */\nfunction aabbOverlapsBox(\n buf: Float32Array,\n bufOffset: number,\n qMinX: number,\n qMinY: number,\n qMinZ: number,\n qMaxX: number,\n qMaxY: number,\n qMaxZ: number,\n): boolean {\n return (\n buf[bufOffset]! <= qMaxX &&\n buf[bufOffset + 3]! >= qMinX &&\n buf[bufOffset + 1]! <= qMaxY &&\n buf[bufOffset + 4]! >= qMinY &&\n buf[bufOffset + 2]! <= qMaxZ &&\n buf[bufOffset + 5]! >= qMinZ\n );\n}\n\n/**\n * Octree with insertion and automatic subdivision.\n *\n * - When a leaf node's object count reaches MAX_OBJECTS_PER_NODE it is\n * subdivided into 8 axis-aligned child octants.\n * - An inserted AABB that fits entirely within a child octant is pushed down.\n * - An AABB that straddles a boundary is kept in the nearest ancestor that\n * fully contains it.\n */\nexport class Octree {\n private readonly nodePool: OctreeNodePool;\n private readonly aabbPool: AABBPool;\n private readonly root: number;\n /** Tracks which node each object (by AABBPool index) is currently stored in. */\n private readonly objectNodeMap: Map<number, number> = new Map();\n /** Pre-allocated traversal stack reused across raycast calls to avoid GC pressure. */\n private readonly _stack: number[] = [];\n\n constructor(nodePool: OctreeNodePool, aabbPool: AABBPool) {\n this.nodePool = nodePool;\n this.aabbPool = aabbPool;\n this.root = nodePool.allocate();\n }\n\n /** The index of the root node in the underlying OctreeNodePool. */\n get rootIndex(): number {\n return this.root;\n }\n\n /** Set the world-space AABB that the root node covers. */\n setBounds(\n minX: number,\n minY: number,\n minZ: number,\n maxX: number,\n maxY: number,\n maxZ: number,\n ): void {\n this.nodePool.setAABB(this.root, minX, minY, minZ, maxX, maxY, maxZ);\n }\n\n /** Insert the AABB at `objectIndex` (in the AABBPool) into the tree. */\n insert(objectIndex: number): void {\n this.insertIntoNode(this.root, objectIndex);\n }\n\n /**\n * Update the AABB at `objectIndex` with new bounds and reposition it in the\n * tree without rebuilding. If the new bounds still fit in the current node\n * the object stays there; otherwise it is removed and re-inserted from the\n * lowest ancestor whose bounds fully contain the new AABB.\n */\n update(\n objectIndex: number,\n newMinX: number,\n newMinY: number,\n newMinZ: number,\n newMaxX: number,\n newMaxY: number,\n newMaxZ: number,\n ): void {\n const np = this.nodePool;\n const ap = this.aabbPool;\n\n // 1. Update the AABB data.\n ap.set(objectIndex, newMinX, newMinY, newMinZ, newMaxX, newMaxY, newMaxZ);\n\n // 2. Find current node.\n const currentNode = this.objectNodeMap.get(objectIndex);\n if (currentNode === undefined) return;\n\n // 3. If the new bounds still fit in the current node, nothing to move.\n if (this.fitsInNode(objectIndex, currentNode)) return;\n\n // 4. Remove from current node.\n np.removeObject(currentNode, objectIndex);\n this.objectNodeMap.delete(objectIndex);\n\n // 5. Walk up to find the lowest ancestor that fully contains the new bounds.\n let ancestorNode = np.getParent(currentNode);\n while (ancestorNode !== -1 && !this.fitsInNode(objectIndex, ancestorNode)) {\n ancestorNode = np.getParent(ancestorNode);\n }\n // If no ancestor fits (shouldn't happen for a well-formed tree), use root.\n if (ancestorNode === -1) ancestorNode = this.root;\n\n // 6. Re-insert downward from the ancestor.\n this.insertIntoNode(ancestorNode, objectIndex);\n }\n\n /**\n * Remove the object at `objectIndex` from the tree.\n * Safe to call even if the object was never inserted (no-op).\n */\n remove(objectIndex: number): void {\n const nodeIdx = this.objectNodeMap.get(objectIndex);\n if (nodeIdx === undefined) return;\n this.nodePool.removeObject(nodeIdx, objectIndex);\n this.objectNodeMap.delete(objectIndex);\n }\n\n /**\n * Cast a ray through the octree and return the closest intersecting object.\n *\n * Uses an iterative stack traversal (pre-allocated, no recursion) to avoid\n * GC pressure. Only descends into child nodes whose AABBs are intersected by\n * the ray, providing efficient pruning of non-intersecting subtrees.\n *\n * @param rayBuf Float32Array containing the ray data [ox,oy,oz,dx,dy,dz].\n * @param rayOffset Element offset (in floats) within `rayBuf` to the ray's origin.\n * @returns The closest `{ objectIndex, t }` hit, or `null` if nothing is hit.\n */\n raycast(\n rayBuf: Float32Array,\n rayOffset: number,\n ): { objectIndex: number; t: number } | null {\n const np = this.nodePool;\n // Access the underlying flat buffers directly for zero-GC intersection tests.\n const npBuf = (np as unknown as { buffer: Float32Array }).buffer;\n const apBuf = (this.aabbPool as unknown as { buffer: Float32Array }).buffer;\n\n // Early-out: test the root AABB before touching any object data.\n if (rayIntersectsAABB(rayBuf, rayOffset, npBuf, this.root * NODE_STRIDE + NODE_AABB_OFFSET) < 0) {\n return null;\n }\n\n // Iterative DFS using the pre-allocated stack.\n this._stack.length = 0;\n this._stack.push(this.root);\n\n let closestT = Infinity;\n let closestIndex = -1;\n\n while (this._stack.length > 0) {\n const nodeIdx = this._stack.pop()!;\n\n // Test every object stored at this node level.\n const objCount = np.getObjectCount(nodeIdx);\n for (let i = 0; i < objCount; i++) {\n const objIdx = np.getObject(nodeIdx, i);\n const t = rayIntersectsAABB(rayBuf, rayOffset, apBuf, objIdx * AABB_STRIDE);\n if (t >= 0 && t < closestT) {\n closestT = t;\n closestIndex = objIdx;\n }\n }\n\n // Push only children whose AABB the ray actually intersects.\n const firstChild = np.getFirstChild(nodeIdx);\n if (firstChild !== -1) {\n for (let i = 0; i < 8; i++) {\n const childIdx = firstChild + i;\n if (rayIntersectsAABB(rayBuf, rayOffset, npBuf, childIdx * NODE_STRIDE + NODE_AABB_OFFSET) >= 0) {\n this._stack.push(childIdx);\n }\n }\n }\n }\n\n if (closestIndex === -1) return null;\n return { objectIndex: closestIndex, t: closestT };\n }\n\n /**\n * Query the octree for all objects whose AABB overlaps the given axis-aligned\n * box region and return their indices (in the AABBPool).\n *\n * Uses the same pre-allocated iterative stack as `raycast` to avoid GC\n * pressure. Descends only into child nodes whose AABBs overlap the query box,\n * pruning non-intersecting subtrees.\n *\n * @param minX Minimum X of the query box.\n * @param minY Minimum Y of the query box.\n * @param minZ Minimum Z of the query box.\n * @param maxX Maximum X of the query box.\n * @param maxY Maximum Y of the query box.\n * @param maxZ Maximum Z of the query box.\n * @returns Array of AABBPool indices for every object that overlaps the box.\n */\n queryBox(\n minX: number,\n minY: number,\n minZ: number,\n maxX: number,\n maxY: number,\n maxZ: number,\n ): number[] {\n const np = this.nodePool;\n const npBuf = (np as unknown as { buffer: Float32Array }).buffer;\n const apBuf = (this.aabbPool as unknown as { buffer: Float32Array }).buffer;\n\n const results: number[] = [];\n\n // Early-out: if the query box doesn't overlap the root, nothing to do.\n if (!aabbOverlapsBox(npBuf, this.root * NODE_STRIDE + NODE_AABB_OFFSET, minX, minY, minZ, maxX, maxY, maxZ)) {\n return results;\n }\n\n // Iterative DFS using the pre-allocated stack.\n this._stack.length = 0;\n this._stack.push(this.root);\n\n while (this._stack.length > 0) {\n const nodeIdx = this._stack.pop()!;\n\n // Test every object stored at this node level.\n const objCount = np.getObjectCount(nodeIdx);\n for (let i = 0; i < objCount; i++) {\n const objIdx = np.getObject(nodeIdx, i);\n if (aabbOverlapsBox(apBuf, objIdx * AABB_STRIDE, minX, minY, minZ, maxX, maxY, maxZ)) {\n results.push(objIdx);\n }\n }\n\n // Push only children whose AABB overlaps the query box.\n const firstChild = np.getFirstChild(nodeIdx);\n if (firstChild !== -1) {\n for (let i = 0; i < 8; i++) {\n const childIdx = firstChild + i;\n if (aabbOverlapsBox(npBuf, childIdx * NODE_STRIDE + NODE_AABB_OFFSET, minX, minY, minZ, maxX, maxY, maxZ)) {\n this._stack.push(childIdx);\n }\n }\n }\n }\n\n return results;\n }\n\n // ── Private helpers ──────────────────────────────────────────────────────\n\n private insertIntoNode(nodeIdx: number, objectIndex: number): void {\n const np = this.nodePool;\n const firstChild = np.getFirstChild(nodeIdx);\n\n // Internal node: try to push the object into a fitting child octant.\n if (firstChild !== -1) {\n for (let i = 0; i < 8; i++) {\n if (this.fitsInNode(objectIndex, firstChild + i)) {\n this.insertIntoNode(firstChild + i, objectIndex);\n return;\n }\n }\n // Object straddles one or more boundaries – keep it at this level.\n // We manually add it here even if it exceeds MAX_OBJECTS_PER_NODE,\n // because internal nodes only hold straddling objects.\n np.addObject(nodeIdx, objectIndex);\n this.objectNodeMap.set(objectIndex, nodeIdx);\n return;\n }\n\n // Leaf node.\n const count = np.getObjectCount(nodeIdx);\n if (count < MAX_OBJECTS_PER_NODE) {\n np.addObject(nodeIdx, objectIndex);\n this.objectNodeMap.set(objectIndex, nodeIdx);\n } else {\n // Prevent infinite subdivision if perfectly overlapping items exceed capacity.\n // If the node we are trying to subdivide has an incredibly small physical bounds\n // or if it fails to separate objects during subdivide, it would loop forever. \n // Instead, we just forcefully add it here and allow the slot to overflow if needed,\n // but because our Float32Array nodes have strict memory limits for MAX_OBJECTS,\n // we must just drop it or accept that `MAX_OBJECTS_PER_NODE` is a hard physical limit.\n // To properly handle this without breaking the pool sizing, in a DOD engine we simply\n // stop adding when perfectly overlapping identical items exceed memory at the leaf bounds.\n try {\n this.subdivide(nodeIdx);\n this.insertIntoNode(nodeIdx, objectIndex);\n } catch (e) {\n if (e instanceof RangeError) {\n // Hard capacity limit hit on identical objects that refuse to separate. \n // Stop recursion. We cannot safely insert it into this perfectly overlapping bucket.\n } else {\n throw e; // Bubble up other errors.\n }\n }\n }\n }\n\n /** Split a leaf node into 8 children and redistribute its objects. */\n private subdivide(nodeIdx: number): void {\n const np = this.nodePool;\n\n const minX = np.getAABB(nodeIdx, 0);\n const minY = np.getAABB(nodeIdx, 1);\n const minZ = np.getAABB(nodeIdx, 2);\n const maxX = np.getAABB(nodeIdx, 3);\n const maxY = np.getAABB(nodeIdx, 4);\n const maxZ = np.getAABB(nodeIdx, 5);\n\n const midX = (minX + maxX) / 2;\n const midY = (minY + maxY) / 2;\n const midZ = (minZ + maxZ) / 2;\n\n // Allocate 8 consecutive child nodes (guarantees contiguous indices).\n const firstChild = np.allocate();\n for (let i = 1; i < 8; i++) {\n np.allocate();\n }\n np.setFirstChild(nodeIdx, firstChild);\n\n // Assign each child its octant AABB.\n // Bit decomposition: bit-0 → X half, bit-1 → Y half, bit-2 → Z half.\n // i & 1 === 0 means left half (minX to midX), i & 1 !== 0 means right half (midX to maxX)\n for (let i = 0; i < 8; i++) {\n const childIdx = firstChild + i;\n const cMinX = (i & 1) === 0 ? minX : midX;\n const cMaxX = (i & 1) === 0 ? midX : maxX;\n const cMinY = (i & 2) === 0 ? minY : midY;\n const cMaxY = (i & 2) === 0 ? midY : maxY;\n const cMinZ = (i & 4) === 0 ? minZ : midZ;\n const cMaxZ = (i & 4) === 0 ? midZ : maxZ;\n np.setAABB(childIdx, cMinX, cMinY, cMinZ, cMaxX, cMaxY, cMaxZ);\n np.setParent(childIdx, nodeIdx);\n }\n\n // Redistribute the existing objects.\n const count = np.getObjectCount(nodeIdx);\n const saved: number[] = [];\n for (let i = 0; i < count; i++) {\n saved.push(np.getObject(nodeIdx, i));\n }\n np.clearObjects(nodeIdx);\n\n for (const obj of saved) {\n // Use insertIntoNode so objectNodeMap stays consistent.\n this.objectNodeMap.delete(obj);\n this.insertIntoNode(nodeIdx, obj);\n }\n }\n\n /** Returns true when the AABB at `objectIndex` is fully contained by `nodeIdx`. */\n private fitsInNode(objectIndex: number, nodeIdx: number): boolean {\n const np = this.nodePool;\n const ap = this.aabbPool;\n return (\n ap.get(objectIndex, 0) >= np.getAABB(nodeIdx, 0) &&\n ap.get(objectIndex, 1) >= np.getAABB(nodeIdx, 1) &&\n ap.get(objectIndex, 2) >= np.getAABB(nodeIdx, 2) &&\n ap.get(objectIndex, 3) <= np.getAABB(nodeIdx, 3) &&\n ap.get(objectIndex, 4) <= np.getAABB(nodeIdx, 4) &&\n ap.get(objectIndex, 5) <= np.getAABB(nodeIdx, 5)\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACWO,IAAM,mBAAmB;AACzB,IAAM,0BAA0B;AAChC,IAAM,qBAAqB;AAC3B,IAAM,2BAA2B;AACjC,IAAM,sBAAsB;AAC5B,IAAM,uBAAuB;AAC7B,IAAM,cAAc,sBAAsB;AAS1C,IAAM,iBAAN,MAAM,gBAAe;AAAA,EACT;AAAA,EACT,QAAgB;AAAA,EAExB,YAAY,UAAkB,cAAkC;AAC9D,SAAK,SAAS,eACV,IAAI,aAAa,cAAc,GAAG,WAAW,WAAW,IACxD,IAAI,aAAa,WAAW,WAAW;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAO,aAAa,UAAoE;AACtF,UAAM,MAAM,IAAI,kBAAkB,WAAW,cAAc,aAAa,iBAAiB;AACzF,WAAO,EAAE,MAAM,IAAI,gBAAe,UAAU,GAAG,GAAG,IAAI;AAAA,EACxD;AAAA;AAAA,EAGA,WAAmB;AACjB,UAAM,QAAQ,KAAK;AACnB,UAAM,SAAS,QAAQ;AAEvB,SAAK,OAAO,SAAS,uBAAuB,IAAI;AAChD,SAAK,OAAO,SAAS,kBAAkB,IAAI;AAC3C,SAAK,OAAO,SAAS,wBAAwB,IAAI;AACjD,SAAK,SAAS;AACd,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,IAAI,OAAe;AACjB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,QAAQ;AAAA,EACf;AAAA;AAAA;AAAA,EAKA,QACE,OACA,MACA,MACA,MACA,MACA,MACA,MACM;AACN,UAAM,MAAM,QAAQ,cAAc;AAClC,SAAK,OAAO,GAAG,IAAI;AACnB,SAAK,OAAO,MAAM,CAAC,IAAI;AACvB,SAAK,OAAO,MAAM,CAAC,IAAI;AACvB,SAAK,OAAO,MAAM,CAAC,IAAI;AACvB,SAAK,OAAO,MAAM,CAAC,IAAI;AACvB,SAAK,OAAO,MAAM,CAAC,IAAI;AAAA,EACzB;AAAA;AAAA,EAGA,QAAQ,OAAe,WAA2B;AAChD,WAAO,KAAK,OAAO,QAAQ,cAAc,mBAAmB,SAAS,KAAK;AAAA,EAC5E;AAAA;AAAA;AAAA,EAKA,cAAc,OAAe,YAA0B;AACrD,SAAK,OAAO,QAAQ,cAAc,uBAAuB,IAAI;AAAA,EAC/D;AAAA;AAAA,EAGA,cAAc,OAAuB;AACnC,WAAO,KAAK,OAAO,QAAQ,cAAc,uBAAuB,KAAK;AAAA,EACvE;AAAA;AAAA;AAAA,EAKA,UAAU,OAAe,aAA2B;AAClD,SAAK,OAAO,QAAQ,cAAc,kBAAkB,IAAI;AAAA,EAC1D;AAAA;AAAA,EAGA,UAAU,OAAuB;AAC/B,WAAO,KAAK,OAAO,QAAQ,cAAc,kBAAkB,KAAK;AAAA,EAClE;AAAA;AAAA;AAAA,EAKA,eAAe,OAAuB;AACpC,WAAO,KAAK,OAAO,QAAQ,cAAc,wBAAwB,KAAK;AAAA,EACxE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAU,OAAe,aAA2B;AAClD,UAAM,WAAW,QAAQ,cAAc;AACvC,UAAM,QAAQ,KAAK,OAAO,QAAQ,KAAK;AACvC,QAAI,SAAS,sBAAsB;AACjC,YAAM,IAAI;AAAA,QACR,kCAAkC,KAAK,2CAA2C,oBAAoB;AAAA,MACxG;AAAA,IACF;AACA,SAAK,OAAO,QAAQ,cAAc,sBAAsB,KAAK,IAAI;AACjE,SAAK,OAAO,QAAQ,IAAI,QAAQ;AAAA,EAClC;AAAA;AAAA,EAGA,UAAU,OAAe,MAAsB;AAC7C,WAAO,KAAK,OAAO,QAAQ,cAAc,sBAAsB,IAAI,KAAK;AAAA,EAC1E;AAAA;AAAA,EAGA,aAAa,OAAqB;AAChC,SAAK,OAAO,QAAQ,cAAc,wBAAwB,IAAI;AAAA,EAChE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,OAAe,aAA8B;AACxD,UAAM,WAAW,QAAQ,cAAc;AACvC,UAAM,QAAQ,KAAK,OAAO,QAAQ,KAAK;AACvC,UAAM,UAAU,QAAQ,cAAc;AACtC,aAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,UAAI,KAAK,OAAO,UAAU,CAAC,MAAM,aAAa;AAE5C,aAAK,OAAO,UAAU,CAAC,IAAI,KAAK,OAAO,UAAU,QAAQ,CAAC,KAAK;AAC/D,aAAK,OAAO,QAAQ,IAAI,QAAQ;AAChC,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACF;;;ACrKO,IAAM,cAAc;AASpB,IAAM,WAAN,MAAM,UAAS;AAAA,EACH;AAAA,EACT,QAAgB;AAAA,EAExB,YAAY,UAAkB,cAAkC;AAC9D,SAAK,SAAS,eACV,IAAI,aAAa,cAAc,GAAG,WAAW,WAAW,IACxD,IAAI,aAAa,WAAW,WAAW;AAAA,EAC7C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAO,aAAa,UAA8D;AAChF,UAAM,MAAM,IAAI,kBAAkB,WAAW,cAAc,aAAa,iBAAiB;AACzF,WAAO,EAAE,MAAM,IAAI,UAAS,UAAU,GAAG,GAAG,IAAI;AAAA,EAClD;AAAA;AAAA,EAGA,WAAmB;AACjB,UAAM,QAAQ,KAAK;AACnB,SAAK,SAAS;AACd,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,IACE,OACA,MACA,MACA,MACA,MACA,MACA,MACM;AACN,UAAM,SAAS,QAAQ;AACvB,SAAK,OAAO,MAAM,IAAI;AACtB,SAAK,OAAO,SAAS,CAAC,IAAI;AAC1B,SAAK,OAAO,SAAS,CAAC,IAAI;AAC1B,SAAK,OAAO,SAAS,CAAC,IAAI;AAC1B,SAAK,OAAO,SAAS,CAAC,IAAI;AAC1B,SAAK,OAAO,SAAS,CAAC,IAAI;AAAA,EAC5B;AAAA;AAAA,EAGA,IAAI,OAAe,WAA2B;AAC5C,WAAO,KAAK,OAAO,QAAQ,cAAc,SAAS,KAAK;AAAA,EACzD;AAAA;AAAA,EAGA,IAAI,OAAe;AACjB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,QAAQ;AAAA,EACf;AACF;;;ACrEO,IAAM,aAAa;AAiFnB,SAAS,kBACd,QACA,WACA,SACA,YACQ;AACR,QAAM,KAAK,OAAO,SAAS,KAAK;AAChC,QAAM,KAAK,OAAO,YAAY,CAAC,KAAK;AACpC,QAAM,KAAK,OAAO,YAAY,CAAC,KAAK;AACpC,QAAM,MAAM,KAAK,OAAO,YAAY,CAAC,KAAK;AAC1C,QAAM,MAAM,KAAK,OAAO,YAAY,CAAC,KAAK;AAC1C,QAAM,MAAM,KAAK,OAAO,YAAY,CAAC,KAAK;AAE1C,QAAM,OAAO,QAAQ,UAAU,KAAK;AACpC,QAAM,OAAO,QAAQ,aAAa,CAAC,KAAK;AACxC,QAAM,OAAO,QAAQ,aAAa,CAAC,KAAK;AACxC,QAAM,OAAO,QAAQ,aAAa,CAAC,KAAK;AACxC,QAAM,OAAO,QAAQ,aAAa,CAAC,KAAK;AACxC,QAAM,OAAO,QAAQ,aAAa,CAAC,KAAK;AAGxC,QAAM,OAAO,OAAO,MAAM;AAC1B,QAAM,OAAO,OAAO,MAAM;AAC1B,QAAM,OAAO,OAAO,MAAM;AAC1B,QAAM,OAAO,OAAO,MAAM;AAC1B,QAAM,OAAO,OAAO,MAAM;AAC1B,QAAM,OAAO,OAAO,MAAM;AAE1B,QAAM,OAAO,KAAK,IAAI,KAAK,IAAI,KAAK,GAAG,GAAG,KAAK,IAAI,KAAK,GAAG,GAAG,KAAK,IAAI,KAAK,GAAG,CAAC;AAChF,QAAM,OAAO,KAAK,IAAI,KAAK,IAAI,KAAK,GAAG,GAAG,KAAK,IAAI,KAAK,GAAG,GAAG,KAAK,IAAI,KAAK,GAAG,CAAC;AAEhF,MAAI,OAAO,KAAK,EAAE,QAAQ,MAAO,QAAO;AACxC,SAAO,QAAQ,IAAI,OAAO;AAC5B;;;AC9GA,SAAS,gBACP,KACA,WACA,OACA,OACA,OACA,OACA,OACA,OACS;AACT,SACE,IAAI,SAAS,KAAM,SACnB,IAAI,YAAY,CAAC,KAAM,SACvB,IAAI,YAAY,CAAC,KAAM,SACvB,IAAI,YAAY,CAAC,KAAM,SACvB,IAAI,YAAY,CAAC,KAAM,SACvB,IAAI,YAAY,CAAC,KAAM;AAE3B;AAWO,IAAM,SAAN,MAAa;AAAA,EACD;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA,gBAAqC,oBAAI,IAAI;AAAA;AAAA,EAE7C,SAAmB,CAAC;AAAA,EAErC,YAAY,UAA0B,UAAoB;AACxD,SAAK,WAAW;AAChB,SAAK,WAAW;AAChB,SAAK,OAAO,SAAS,SAAS;AAAA,EAChC;AAAA;AAAA,EAGA,IAAI,YAAoB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAGA,UACE,MACA,MACA,MACA,MACA,MACA,MACM;AACN,SAAK,SAAS,QAAQ,KAAK,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,IAAI;AAAA,EACrE;AAAA;AAAA,EAGA,OAAO,aAA2B;AAChC,SAAK,eAAe,KAAK,MAAM,WAAW;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,OACE,aACA,SACA,SACA,SACA,SACA,SACA,SACM;AACN,UAAM,KAAK,KAAK;AAChB,UAAM,KAAK,KAAK;AAGhB,OAAG,IAAI,aAAa,SAAS,SAAS,SAAS,SAAS,SAAS,OAAO;AAGxE,UAAM,cAAc,KAAK,cAAc,IAAI,WAAW;AACtD,QAAI,gBAAgB,OAAW;AAG/B,QAAI,KAAK,WAAW,aAAa,WAAW,EAAG;AAG/C,OAAG,aAAa,aAAa,WAAW;AACxC,SAAK,cAAc,OAAO,WAAW;AAGrC,QAAI,eAAe,GAAG,UAAU,WAAW;AAC3C,WAAO,iBAAiB,MAAM,CAAC,KAAK,WAAW,aAAa,YAAY,GAAG;AACzE,qBAAe,GAAG,UAAU,YAAY;AAAA,IAC1C;AAEA,QAAI,iBAAiB,GAAI,gBAAe,KAAK;AAG7C,SAAK,eAAe,cAAc,WAAW;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,aAA2B;AAChC,UAAM,UAAU,KAAK,cAAc,IAAI,WAAW;AAClD,QAAI,YAAY,OAAW;AAC3B,SAAK,SAAS,aAAa,SAAS,WAAW;AAC/C,SAAK,cAAc,OAAO,WAAW;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,QACE,QACA,WAC2C;AAC3C,UAAM,KAAK,KAAK;AAEhB,UAAM,QAAS,GAA2C;AAC1D,UAAM,QAAS,KAAK,SAAiD;AAGrE,QAAI,kBAAkB,QAAQ,WAAW,OAAO,KAAK,OAAO,cAAc,gBAAgB,IAAI,GAAG;AAC/F,aAAO;AAAA,IACT;AAGA,SAAK,OAAO,SAAS;AACrB,SAAK,OAAO,KAAK,KAAK,IAAI;AAE1B,QAAI,WAAW;AACf,QAAI,eAAe;AAEnB,WAAO,KAAK,OAAO,SAAS,GAAG;AAC7B,YAAM,UAAU,KAAK,OAAO,IAAI;AAGhC,YAAM,WAAW,GAAG,eAAe,OAAO;AAC1C,eAAS,IAAI,GAAG,IAAI,UAAU,KAAK;AACjC,cAAM,SAAS,GAAG,UAAU,SAAS,CAAC;AACtC,cAAM,IAAI,kBAAkB,QAAQ,WAAW,OAAO,SAAS,WAAW;AAC1E,YAAI,KAAK,KAAK,IAAI,UAAU;AAC1B,qBAAW;AACX,yBAAe;AAAA,QACjB;AAAA,MACF;AAGA,YAAM,aAAa,GAAG,cAAc,OAAO;AAC3C,UAAI,eAAe,IAAI;AACrB,iBAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,gBAAM,WAAW,aAAa;AAC9B,cAAI,kBAAkB,QAAQ,WAAW,OAAO,WAAW,cAAc,gBAAgB,KAAK,GAAG;AAC/F,iBAAK,OAAO,KAAK,QAAQ;AAAA,UAC3B;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,QAAI,iBAAiB,GAAI,QAAO;AAChC,WAAO,EAAE,aAAa,cAAc,GAAG,SAAS;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,SACE,MACA,MACA,MACA,MACA,MACA,MACU;AACV,UAAM,KAAK,KAAK;AAChB,UAAM,QAAS,GAA2C;AAC1D,UAAM,QAAS,KAAK,SAAiD;AAErE,UAAM,UAAoB,CAAC;AAG3B,QAAI,CAAC,gBAAgB,OAAO,KAAK,OAAO,cAAc,kBAAkB,MAAM,MAAM,MAAM,MAAM,MAAM,IAAI,GAAG;AAC3G,aAAO;AAAA,IACT;AAGA,SAAK,OAAO,SAAS;AACrB,SAAK,OAAO,KAAK,KAAK,IAAI;AAE1B,WAAO,KAAK,OAAO,SAAS,GAAG;AAC7B,YAAM,UAAU,KAAK,OAAO,IAAI;AAGhC,YAAM,WAAW,GAAG,eAAe,OAAO;AAC1C,eAAS,IAAI,GAAG,IAAI,UAAU,KAAK;AACjC,cAAM,SAAS,GAAG,UAAU,SAAS,CAAC;AACtC,YAAI,gBAAgB,OAAO,SAAS,aAAa,MAAM,MAAM,MAAM,MAAM,MAAM,IAAI,GAAG;AACpF,kBAAQ,KAAK,MAAM;AAAA,QACrB;AAAA,MACF;AAGA,YAAM,aAAa,GAAG,cAAc,OAAO;AAC3C,UAAI,eAAe,IAAI;AACrB,iBAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,gBAAM,WAAW,aAAa;AAC9B,cAAI,gBAAgB,OAAO,WAAW,cAAc,kBAAkB,MAAM,MAAM,MAAM,MAAM,MAAM,IAAI,GAAG;AACzG,iBAAK,OAAO,KAAK,QAAQ;AAAA,UAC3B;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA,EAIQ,eAAe,SAAiB,aAA2B;AACjE,UAAM,KAAK,KAAK;AAChB,UAAM,aAAa,GAAG,cAAc,OAAO;AAG3C,QAAI,eAAe,IAAI;AACrB,eAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,YAAI,KAAK,WAAW,aAAa,aAAa,CAAC,GAAG;AAChD,eAAK,eAAe,aAAa,GAAG,WAAW;AAC/C;AAAA,QACF;AAAA,MACF;AAIA,SAAG,UAAU,SAAS,WAAW;AACjC,WAAK,cAAc,IAAI,aAAa,OAAO;AAC3C;AAAA,IACF;AAGA,UAAM,QAAQ,GAAG,eAAe,OAAO;AACvC,QAAI,QAAQ,sBAAsB;AAChC,SAAG,UAAU,SAAS,WAAW;AACjC,WAAK,cAAc,IAAI,aAAa,OAAO;AAAA,IAC7C,OAAO;AASL,UAAI;AACF,aAAK,UAAU,OAAO;AACtB,aAAK,eAAe,SAAS,WAAW;AAAA,MAC1C,SAAS,GAAG;AACV,YAAI,aAAa,YAAY;AAAA,QAG7B,OAAO;AACL,gBAAM;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAGQ,UAAU,SAAuB;AACvC,UAAM,KAAK,KAAK;AAEhB,UAAM,OAAO,GAAG,QAAQ,SAAS,CAAC;AAClC,UAAM,OAAO,GAAG,QAAQ,SAAS,CAAC;AAClC,UAAM,OAAO,GAAG,QAAQ,SAAS,CAAC;AAClC,UAAM,OAAO,GAAG,QAAQ,SAAS,CAAC;AAClC,UAAM,OAAO,GAAG,QAAQ,SAAS,CAAC;AAClC,UAAM,OAAO,GAAG,QAAQ,SAAS,CAAC;AAElC,UAAM,QAAQ,OAAO,QAAQ;AAC7B,UAAM,QAAQ,OAAO,QAAQ;AAC7B,UAAM,QAAQ,OAAO,QAAQ;AAG7B,UAAM,aAAa,GAAG,SAAS;AAC/B,aAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,SAAG,SAAS;AAAA,IACd;AACA,OAAG,cAAc,SAAS,UAAU;AAKpC,aAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,YAAM,WAAW,aAAa;AAC9B,YAAM,SAAS,IAAI,OAAO,IAAI,OAAO;AACrC,YAAM,SAAS,IAAI,OAAO,IAAI,OAAO;AACrC,YAAM,SAAS,IAAI,OAAO,IAAI,OAAO;AACrC,YAAM,SAAS,IAAI,OAAO,IAAI,OAAO;AACrC,YAAM,SAAS,IAAI,OAAO,IAAI,OAAO;AACrC,YAAM,SAAS,IAAI,OAAO,IAAI,OAAO;AACrC,SAAG,QAAQ,UAAU,OAAO,OAAO,OAAO,OAAO,OAAO,KAAK;AAC7D,SAAG,UAAU,UAAU,OAAO;AAAA,IAChC;AAGA,UAAM,QAAQ,GAAG,eAAe,OAAO;AACvC,UAAM,QAAkB,CAAC;AACzB,aAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,YAAM,KAAK,GAAG,UAAU,SAAS,CAAC,CAAC;AAAA,IACrC;AACA,OAAG,aAAa,OAAO;AAEvB,eAAW,OAAO,OAAO;AAEvB,WAAK,cAAc,OAAO,GAAG;AAC7B,WAAK,eAAe,SAAS,GAAG;AAAA,IAClC;AAAA,EACF;AAAA;AAAA,EAGQ,WAAW,aAAqB,SAA0B;AAChE,UAAM,KAAK,KAAK;AAChB,UAAM,KAAK,KAAK;AAChB,WACE,GAAG,IAAI,aAAa,CAAC,KAAK,GAAG,QAAQ,SAAS,CAAC,KAC/C,GAAG,IAAI,aAAa,CAAC,KAAK,GAAG,QAAQ,SAAS,CAAC,KAC/C,GAAG,IAAI,aAAa,CAAC,KAAK,GAAG,QAAQ,SAAS,CAAC,KAC/C,GAAG,IAAI,aAAa,CAAC,KAAK,GAAG,QAAQ,SAAS,CAAC,KAC/C,GAAG,IAAI,aAAa,CAAC,KAAK,GAAG,QAAQ,SAAS,CAAC,KAC/C,GAAG,IAAI,aAAa,CAAC,KAAK,GAAG,QAAQ,SAAS,CAAC;AAAA,EAEnD;AACF;;;AJtRO,SAAS,uBAGd;AACA,MAAI,SAAwB;AAC5B,MAAI,WAA4B;AAChC,MAAI,UAA+B;AACnC,MAAI,aAAkC;AACtC,MAAI,gBAAgB;AAEpB,MAAI,sBAAsB;AAE1B,SAAO;AAAA;AAAA,IAEL,KAAK,KAA0C;AAC7C,YAAM,WAAW,IAAI,eAAe,IAAI,cAAc,IAAI,QAAQ;AAClE,iBAAW,IAAI,SAAS,IAAI,gBAAgB,IAAI,QAAQ;AACxD,gBAAU,IAAI,aAAa,IAAI,OAAO;AACtC,mBAAa,IAAI,aAAa,IAAI,UAAU;AAC5C,sBAAgB,IAAI;AACpB,4BAAsB;AAEtB,eAAS,IAAI,OAAO,UAAU,QAAQ;AACtC,aAAO;AAAA,QACL,IAAI;AAAA,QAAW,IAAI;AAAA,QAAW,IAAI;AAAA,QAClC,IAAI;AAAA,QAAW,IAAI;AAAA,QAAW,IAAI;AAAA,MACpC;AAEA,aAAO,EAAE,MAAM,QAAQ;AAAA,IACzB;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,MAAM,KAA0C;AAC9C,UAAI,WAAW,QAAQ,aAAa,QAAQ,YAAY,QAAQ,eAAe,MAAM;AACnF,cAAM,IAAI,MAAM,0CAA0C;AAAA,MAC5D;AAEA,YAAM,cAAc,IAAI;AAKxB,aAAO,SAAS,OAAO,aAAa;AAClC,iBAAS,SAAS;AAAA,MACpB;AAGA,eAAS,IAAI,GAAG,IAAI,aAAa,KAAK;AACpC,cAAM,OAAO,SAAS,IAAI,GAAG,CAAC;AAC9B,cAAM,OAAO,SAAS,IAAI,GAAG,CAAC;AAC9B,cAAM,OAAO,SAAS,IAAI,GAAG,CAAC;AAC9B,cAAM,OAAO,SAAS,IAAI,GAAG,CAAC;AAC9B,cAAM,OAAO,SAAS,IAAI,GAAG,CAAC;AAC9B,cAAM,OAAO,SAAS,IAAI,GAAG,CAAC;AAE9B,YAAI,IAAI,qBAAqB;AAE3B,iBAAO,OAAO,GAAG,MAAM,MAAM,MAAM,MAAM,MAAM,IAAI;AAAA,QACrD,OAAO;AAEL,iBAAO,OAAO,CAAC;AACf;AAAA,QACF;AAAA,MACF;AAIA,eAAS,IAAI,GAAG,IAAI,eAAe,KAAK;AACtC,cAAM,MAAM,OAAO,QAAQ,SAAS,IAAI,UAAU;AAClD,YAAI,QAAQ,MAAM;AAChB,qBAAW,IAAI,CAAC,IAAI,IAAI;AACxB,qBAAW,IAAI,IAAI,CAAC,IAAI,IAAI;AAAA,QAC9B,OAAO;AACL,qBAAW,IAAI,CAAC,IAAI;AACpB,qBAAW,IAAI,IAAI,CAAC,IAAI;AAAA,QAC1B;AAAA,MACF;AAEA,aAAO,EAAE,MAAM,QAAQ,UAAU,cAAc;AAAA,IACjD;AAAA,EACF;AACF;AAaA,IAAM,cAAc;AACpB,IAAI,OAAO,YAAY,aAAa,MAAM,cAAc,OAAO,YAAY,WAAW,MAAM,aAAa;AACvG,QAAM,aAAa;AACnB,QAAM,YAAY,qBAAqB;AAEvC,aAAW,YAAY,CAAC,UAAU;AAChC,UAAM,MAAM,MAAM;AAClB,QAAI,IAAI,SAAS,QAAQ;AACvB,iBAAW,YAAY,UAAU,KAAK,GAAG,CAAC;AAAA,IAC5C,WAAW,IAAI,SAAS,SAAS;AAC/B,iBAAW,YAAY,UAAU,MAAM,GAAG,CAAC;AAAA,IAC7C;AAAA,EACF;AACF;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createLidarProcessor
3
- } from "./chunk-JF4RVZK3.js";
3
+ } from "./chunk-6B5AUBFL.js";
4
4
  export {
5
5
  createLidarProcessor
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spatial-engine/core",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "High-performance spatial partitioning and query library using Data-Oriented Design",
5
5
  "author": "Francisco Rueda Esquivel",
6
6
  "license": "MIT",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/octree-node.ts","../src/aabb.ts","../src/ray.ts","../src/octree.ts","../src/lidar-worker.ts"],"sourcesContent":["/**\n * Octree node stored as a flat Float32Array slice.\n *\n * Layout per node (NODE_STRIDE floats):\n * [0..5] – AABB (minX, minY, minZ, maxX, maxY, maxZ)\n * [6] – firstChild index (-1 = leaf / no children)\n * [7] – parent index (-1 = root)\n * [8] – object count\n * [9..] – object indices (MAX_OBJECTS_PER_NODE slots)\n */\n\nexport const NODE_AABB_OFFSET = 0;\nexport const NODE_FIRST_CHILD_OFFSET = 6;\nexport const NODE_PARENT_OFFSET = 7;\nexport const NODE_OBJECT_COUNT_OFFSET = 8;\nexport const NODE_OBJECTS_OFFSET = 9;\nexport const MAX_OBJECTS_PER_NODE = 8;\nexport const NODE_STRIDE = NODE_OBJECTS_OFFSET + MAX_OBJECTS_PER_NODE;\n\n/**\n * A pool-backed, zero-GC Octree node store.\n * Each node occupies NODE_STRIDE floats in the underlying Float32Array buffer.\n *\n * When a `SharedArrayBuffer` is provided, the pool's data is accessible from\n * multiple threads (e.g. a Web Worker) with no copying.\n */\nexport class OctreeNodePool {\n private readonly buffer: Float32Array;\n private count: number = 0;\n\n constructor(capacity: number, sharedBuffer?: SharedArrayBuffer) {\n this.buffer = sharedBuffer\n ? new Float32Array(sharedBuffer, 0, capacity * NODE_STRIDE)\n : new Float32Array(capacity * NODE_STRIDE);\n }\n\n /**\n * Create an OctreeNodePool backed by a new `SharedArrayBuffer`.\n * Both the pool and the underlying `SharedArrayBuffer` are returned so the\n * caller can transfer the buffer to a `Worker` via `postMessage`.\n */\n static createShared(capacity: number): { pool: OctreeNodePool; sab: SharedArrayBuffer } {\n const sab = new SharedArrayBuffer(capacity * NODE_STRIDE * Float32Array.BYTES_PER_ELEMENT);\n return { pool: new OctreeNodePool(capacity, sab), sab };\n }\n\n /** Allocate a new node slot, initialise sentinel values, and return its index. */\n allocate(): number {\n const index = this.count;\n const offset = index * NODE_STRIDE;\n // Initialise sentinel values for child/parent links and object count.\n this.buffer[offset + NODE_FIRST_CHILD_OFFSET] = -1;\n this.buffer[offset + NODE_PARENT_OFFSET] = -1;\n this.buffer[offset + NODE_OBJECT_COUNT_OFFSET] = 0;\n this.count += 1;\n return index;\n }\n\n /** Returns the number of allocated nodes. */\n get size(): number {\n return this.count;\n }\n\n /** Reset the pool (all allocations freed, no GC). */\n reset(): void {\n this.count = 0;\n }\n\n // ── AABB ──────────────────────────────────────────────────────────────────\n\n /** Set the AABB bounds for the node at the given index. */\n setAABB(\n index: number,\n minX: number,\n minY: number,\n minZ: number,\n maxX: number,\n maxY: number,\n maxZ: number,\n ): void {\n const off = index * NODE_STRIDE + NODE_AABB_OFFSET;\n this.buffer[off] = minX;\n this.buffer[off + 1] = minY;\n this.buffer[off + 2] = minZ;\n this.buffer[off + 3] = maxX;\n this.buffer[off + 4] = maxY;\n this.buffer[off + 5] = maxZ;\n }\n\n /** Read a single AABB component (0–5) from the node at the given index. */\n getAABB(index: number, component: number): number {\n return this.buffer[index * NODE_STRIDE + NODE_AABB_OFFSET + component] ?? 0;\n }\n\n // ── First-child link ──────────────────────────────────────────────────────\n\n /** Set the first-child index for the node (-1 = leaf). */\n setFirstChild(index: number, childIndex: number): void {\n this.buffer[index * NODE_STRIDE + NODE_FIRST_CHILD_OFFSET] = childIndex;\n }\n\n /** Get the first-child index for the node (-1 = leaf). */\n getFirstChild(index: number): number {\n return this.buffer[index * NODE_STRIDE + NODE_FIRST_CHILD_OFFSET] ?? -1;\n }\n\n // ── Parent link ───────────────────────────────────────────────────────────\n\n /** Set the parent index for the node (-1 = root). */\n setParent(index: number, parentIndex: number): void {\n this.buffer[index * NODE_STRIDE + NODE_PARENT_OFFSET] = parentIndex;\n }\n\n /** Get the parent index for the node (-1 = root). */\n getParent(index: number): number {\n return this.buffer[index * NODE_STRIDE + NODE_PARENT_OFFSET] ?? -1;\n }\n\n // ── Object pointers ───────────────────────────────────────────────────────\n\n /** Return the number of objects stored in the node at the given index. */\n getObjectCount(index: number): number {\n return this.buffer[index * NODE_STRIDE + NODE_OBJECT_COUNT_OFFSET] ?? 0;\n }\n\n /**\n * Append an object index to the node's object list.\n * Throws a RangeError when MAX_OBJECTS_PER_NODE is exceeded.\n */\n addObject(index: number, objectIndex: number): void {\n const countOff = index * NODE_STRIDE + NODE_OBJECT_COUNT_OFFSET;\n const count = this.buffer[countOff] ?? 0;\n if (count >= MAX_OBJECTS_PER_NODE) {\n throw new RangeError(\n `OctreeNodePool.addObject: node ${index} already contains MAX_OBJECTS_PER_NODE (${MAX_OBJECTS_PER_NODE}) objects`,\n );\n }\n this.buffer[index * NODE_STRIDE + NODE_OBJECTS_OFFSET + count] = objectIndex;\n this.buffer[countOff] = count + 1;\n }\n\n /** Return the object index stored at slot `slot` in the given node. */\n getObject(index: number, slot: number): number {\n return this.buffer[index * NODE_STRIDE + NODE_OBJECTS_OFFSET + slot] ?? 0;\n }\n\n /** Reset the object count of the given node to zero (does not zero the slots). */\n clearObjects(index: number): void {\n this.buffer[index * NODE_STRIDE + NODE_OBJECT_COUNT_OFFSET] = 0;\n }\n\n /**\n * Remove a single object index from the node's object list.\n * Uses swap-with-last to avoid shifting. Returns true if found and removed.\n */\n removeObject(index: number, objectIndex: number): boolean {\n const countOff = index * NODE_STRIDE + NODE_OBJECT_COUNT_OFFSET;\n const count = this.buffer[countOff] ?? 0;\n const baseOff = index * NODE_STRIDE + NODE_OBJECTS_OFFSET;\n for (let i = 0; i < count; i++) {\n if (this.buffer[baseOff + i] === objectIndex) {\n // Swap with the last element, then decrement count.\n this.buffer[baseOff + i] = this.buffer[baseOff + count - 1] ?? 0;\n this.buffer[countOff] = count - 1;\n return true;\n }\n }\n return false;\n }\n}\n","/**\n * AABB (Axis-Aligned Bounding Box) stored as a flat Float32Array slice.\n * Layout: [minX, minY, minZ, maxX, maxY, maxZ]\n */\nexport const AABB_STRIDE = 6;\n\n/**\n * A pool-backed, zero-GC AABB store.\n * Each AABB occupies AABB_STRIDE floats in the underlying buffer.\n *\n * When a `SharedArrayBuffer` is provided, the pool's data is accessible from\n * multiple threads (e.g. a Web Worker) with no copying.\n */\nexport class AABBPool {\n private readonly buffer: Float32Array;\n private count: number = 0;\n\n constructor(capacity: number, sharedBuffer?: SharedArrayBuffer) {\n this.buffer = sharedBuffer\n ? new Float32Array(sharedBuffer, 0, capacity * AABB_STRIDE)\n : new Float32Array(capacity * AABB_STRIDE);\n }\n\n /**\n * Create an AABBPool backed by a new `SharedArrayBuffer`.\n * Both the pool and the underlying `SharedArrayBuffer` are returned so the\n * caller can transfer the buffer to a `Worker` via `postMessage`.\n */\n static createShared(capacity: number): { pool: AABBPool; sab: SharedArrayBuffer } {\n const sab = new SharedArrayBuffer(capacity * AABB_STRIDE * Float32Array.BYTES_PER_ELEMENT);\n return { pool: new AABBPool(capacity, sab), sab };\n }\n\n /** Allocate a new AABB slot and return its index. */\n allocate(): number {\n const index = this.count;\n this.count += 1;\n return index;\n }\n\n /** Set the bounds of an AABB at the given index. */\n set(\n index: number,\n minX: number,\n minY: number,\n minZ: number,\n maxX: number,\n maxY: number,\n maxZ: number,\n ): void {\n const offset = index * AABB_STRIDE;\n this.buffer[offset] = minX;\n this.buffer[offset + 1] = minY;\n this.buffer[offset + 2] = minZ;\n this.buffer[offset + 3] = maxX;\n this.buffer[offset + 4] = maxY;\n this.buffer[offset + 5] = maxZ;\n }\n\n /** Read a single component value from the buffer. */\n get(index: number, component: number): number {\n return this.buffer[index * AABB_STRIDE + component] ?? 0;\n }\n\n /** Returns the number of allocated AABBs. */\n get size(): number {\n return this.count;\n }\n\n /** Reset the pool (all allocations freed, no GC). */\n reset(): void {\n this.count = 0;\n }\n}\n\n/**\n * Expand AABB `a` in-place so it also fully contains AABB `b`.\n */\nexport function aabbExpand(pool: AABBPool, a: number, b: number): void {\n const buf = (pool as unknown as { buffer: Float32Array }).buffer;\n const aOff = a * AABB_STRIDE;\n const bOff = b * AABB_STRIDE;\n buf[aOff] = Math.min(buf[aOff] ?? 0, buf[bOff] ?? 0);\n buf[aOff + 1] = Math.min(buf[aOff + 1] ?? 0, buf[bOff + 1] ?? 0);\n buf[aOff + 2] = Math.min(buf[aOff + 2] ?? 0, buf[bOff + 2] ?? 0);\n buf[aOff + 3] = Math.max(buf[aOff + 3] ?? 0, buf[bOff + 3] ?? 0);\n buf[aOff + 4] = Math.max(buf[aOff + 4] ?? 0, buf[bOff + 4] ?? 0);\n buf[aOff + 5] = Math.max(buf[aOff + 5] ?? 0, buf[bOff + 5] ?? 0);\n}\n\n/**\n * Merge AABBs `a` and `b` into `dest` (a pre-allocated slot), storing their union.\n */\nexport function aabbMerge(pool: AABBPool, dest: number, a: number, b: number): void {\n const buf = (pool as unknown as { buffer: Float32Array }).buffer;\n const aOff = a * AABB_STRIDE;\n const bOff = b * AABB_STRIDE;\n const dOff = dest * AABB_STRIDE;\n buf[dOff] = Math.min(buf[aOff] ?? 0, buf[bOff] ?? 0);\n buf[dOff + 1] = Math.min(buf[aOff + 1] ?? 0, buf[bOff + 1] ?? 0);\n buf[dOff + 2] = Math.min(buf[aOff + 2] ?? 0, buf[bOff + 2] ?? 0);\n buf[dOff + 3] = Math.max(buf[aOff + 3] ?? 0, buf[bOff + 3] ?? 0);\n buf[dOff + 4] = Math.max(buf[aOff + 4] ?? 0, buf[bOff + 4] ?? 0);\n buf[dOff + 5] = Math.max(buf[aOff + 5] ?? 0, buf[bOff + 5] ?? 0);\n}\n\n/**\n * Test whether two AABBs (by index in the same pool) intersect.\n */\nexport function aabbIntersects(pool: AABBPool, a: number, b: number): boolean {\n const aOff = a * AABB_STRIDE;\n const bOff = b * AABB_STRIDE;\n // Access via get() to stay safe with bounds, but direct buffer access is\n // equally valid when performance is critical.\n const buf = (pool as unknown as { buffer: Float32Array }).buffer;\n return (\n (buf[aOff] ?? 0) <= (buf[bOff + 3] ?? 0) &&\n (buf[aOff + 3] ?? 0) >= (buf[bOff] ?? 0) &&\n (buf[aOff + 1] ?? 0) <= (buf[bOff + 4] ?? 0) &&\n (buf[aOff + 4] ?? 0) >= (buf[bOff + 1] ?? 0) &&\n (buf[aOff + 2] ?? 0) <= (buf[bOff + 5] ?? 0) &&\n (buf[aOff + 5] ?? 0) >= (buf[bOff + 2] ?? 0)\n );\n}\n","/**\n * Ray stored as origin + direction (unit vector).\n * Layout: [ox, oy, oz, dx, dy, dz]\n */\nexport const RAY_STRIDE = 6;\n\n/** A pool-backed, zero-GC Ray store.\n *\n * When a `SharedArrayBuffer` is provided, the pool's data is accessible from\n * multiple threads (e.g. a Web Worker) with no copying.\n */\nexport class RayPool {\n private readonly buffer: Float32Array;\n private count: number = 0;\n\n constructor(capacity: number, sharedBuffer?: SharedArrayBuffer) {\n this.buffer = sharedBuffer\n ? new Float32Array(sharedBuffer, 0, capacity * RAY_STRIDE)\n : new Float32Array(capacity * RAY_STRIDE);\n }\n\n /**\n * Create a RayPool backed by a new `SharedArrayBuffer`.\n * Both the pool and the underlying `SharedArrayBuffer` are returned so the\n * caller can transfer the buffer to a `Worker` via `postMessage`.\n */\n static createShared(capacity: number): { pool: RayPool; sab: SharedArrayBuffer } {\n const sab = new SharedArrayBuffer(capacity * RAY_STRIDE * Float32Array.BYTES_PER_ELEMENT);\n return { pool: new RayPool(capacity, sab), sab };\n }\n\n /** Allocate a new Ray slot and return its index. */\n allocate(): number {\n const index = this.count;\n this.count += 1;\n return index;\n }\n\n /** Set origin and direction of a ray at the given index. */\n set(\n index: number,\n ox: number,\n oy: number,\n oz: number,\n dx: number,\n dy: number,\n dz: number,\n ): void {\n const offset = index * RAY_STRIDE;\n this.buffer[offset] = ox;\n this.buffer[offset + 1] = oy;\n this.buffer[offset + 2] = oz;\n this.buffer[offset + 3] = dx;\n this.buffer[offset + 4] = dy;\n this.buffer[offset + 5] = dz;\n }\n\n /** Read a single component value from the buffer. */\n get(index: number, component: number): number {\n return this.buffer[index * RAY_STRIDE + component] ?? 0;\n }\n\n /** Returns the number of allocated Rays. */\n get size(): number {\n return this.count;\n }\n\n /** Reset the pool (all allocations freed, no GC). */\n reset(): void {\n this.count = 0;\n }\n}\n\n/**\n * Optimized branchless slab-method ray–AABB intersection test.\n *\n * Operates purely on flat Float32Arrays with no per-axis direction branches.\n * When a direction component is zero, IEEE 754 produces ±Infinity for the\n * reciprocal, which propagates correctly through the min/max slab computation:\n * - origin inside the slab → [−∞, +∞] interval (no constraint)\n * - origin outside the slab → either [+∞, +∞] or [−∞, −∞] → forces a miss\n *\n * Returns the parametric hit distance `t` (>= 0) if the ray intersects the\n * AABB, or -1 if there is no intersection (miss or box entirely behind origin).\n */\nexport function rayIntersectsAABB(\n rayBuf: Float32Array,\n rayOffset: number,\n aabbBuf: Float32Array,\n aabbOffset: number,\n): number {\n const ox = rayBuf[rayOffset] ?? 0;\n const oy = rayBuf[rayOffset + 1] ?? 0;\n const oz = rayBuf[rayOffset + 2] ?? 0;\n const idx = 1 / (rayBuf[rayOffset + 3] ?? 0);\n const idy = 1 / (rayBuf[rayOffset + 4] ?? 0);\n const idz = 1 / (rayBuf[rayOffset + 5] ?? 0);\n\n const minX = aabbBuf[aabbOffset] ?? 0;\n const minY = aabbBuf[aabbOffset + 1] ?? 0;\n const minZ = aabbBuf[aabbOffset + 2] ?? 0;\n const maxX = aabbBuf[aabbOffset + 3] ?? 0;\n const maxY = aabbBuf[aabbOffset + 4] ?? 0;\n const maxZ = aabbBuf[aabbOffset + 5] ?? 0;\n\n // Branchless slab method: per-axis near/far t values.\n const t1x = (minX - ox) * idx;\n const t2x = (maxX - ox) * idx;\n const t1y = (minY - oy) * idy;\n const t2y = (maxY - oy) * idy;\n const t1z = (minZ - oz) * idz;\n const t2z = (maxZ - oz) * idz;\n\n const tmin = Math.max(Math.min(t1x, t2x), Math.min(t1y, t2y), Math.min(t1z, t2z));\n const tmax = Math.min(Math.max(t1x, t2x), Math.max(t1y, t2y), Math.max(t1z, t2z));\n\n if (tmax < 0 || !(tmin <= tmax)) return -1;\n return tmin >= 0 ? tmin : tmax;\n}\n","import { OctreeNodePool, MAX_OBJECTS_PER_NODE, NODE_STRIDE, NODE_AABB_OFFSET } from './octree-node.js';\nimport { AABBPool, AABB_STRIDE } from './aabb.js';\nimport { rayIntersectsAABB } from './ray.js';\n\n/**\n * Returns true when the AABB stored at `bufOffset` in `buf` overlaps the\n * axis-aligned box defined by the six query scalars.\n */\nfunction aabbOverlapsBox(\n buf: Float32Array,\n bufOffset: number,\n qMinX: number,\n qMinY: number,\n qMinZ: number,\n qMaxX: number,\n qMaxY: number,\n qMaxZ: number,\n): boolean {\n return (\n buf[bufOffset]! <= qMaxX &&\n buf[bufOffset + 3]! >= qMinX &&\n buf[bufOffset + 1]! <= qMaxY &&\n buf[bufOffset + 4]! >= qMinY &&\n buf[bufOffset + 2]! <= qMaxZ &&\n buf[bufOffset + 5]! >= qMinZ\n );\n}\n\n/**\n * Octree with insertion and automatic subdivision.\n *\n * - When a leaf node's object count reaches MAX_OBJECTS_PER_NODE it is\n * subdivided into 8 axis-aligned child octants.\n * - An inserted AABB that fits entirely within a child octant is pushed down.\n * - An AABB that straddles a boundary is kept in the nearest ancestor that\n * fully contains it.\n */\nexport class Octree {\n private readonly nodePool: OctreeNodePool;\n private readonly aabbPool: AABBPool;\n private readonly root: number;\n /** Tracks which node each object (by AABBPool index) is currently stored in. */\n private readonly objectNodeMap: Map<number, number> = new Map();\n /** Pre-allocated traversal stack reused across raycast calls to avoid GC pressure. */\n private readonly _stack: number[] = [];\n\n constructor(nodePool: OctreeNodePool, aabbPool: AABBPool) {\n this.nodePool = nodePool;\n this.aabbPool = aabbPool;\n this.root = nodePool.allocate();\n }\n\n /** The index of the root node in the underlying OctreeNodePool. */\n get rootIndex(): number {\n return this.root;\n }\n\n /** Set the world-space AABB that the root node covers. */\n setBounds(\n minX: number,\n minY: number,\n minZ: number,\n maxX: number,\n maxY: number,\n maxZ: number,\n ): void {\n this.nodePool.setAABB(this.root, minX, minY, minZ, maxX, maxY, maxZ);\n }\n\n /** Insert the AABB at `objectIndex` (in the AABBPool) into the tree. */\n insert(objectIndex: number): void {\n this.insertIntoNode(this.root, objectIndex);\n }\n\n /**\n * Update the AABB at `objectIndex` with new bounds and reposition it in the\n * tree without rebuilding. If the new bounds still fit in the current node\n * the object stays there; otherwise it is removed and re-inserted from the\n * lowest ancestor whose bounds fully contain the new AABB.\n */\n update(\n objectIndex: number,\n newMinX: number,\n newMinY: number,\n newMinZ: number,\n newMaxX: number,\n newMaxY: number,\n newMaxZ: number,\n ): void {\n const np = this.nodePool;\n const ap = this.aabbPool;\n\n // 1. Update the AABB data.\n ap.set(objectIndex, newMinX, newMinY, newMinZ, newMaxX, newMaxY, newMaxZ);\n\n // 2. Find current node.\n const currentNode = this.objectNodeMap.get(objectIndex);\n if (currentNode === undefined) return;\n\n // 3. If the new bounds still fit in the current node, nothing to move.\n if (this.fitsInNode(objectIndex, currentNode)) return;\n\n // 4. Remove from current node.\n np.removeObject(currentNode, objectIndex);\n this.objectNodeMap.delete(objectIndex);\n\n // 5. Walk up to find the lowest ancestor that fully contains the new bounds.\n let ancestorNode = np.getParent(currentNode);\n while (ancestorNode !== -1 && !this.fitsInNode(objectIndex, ancestorNode)) {\n ancestorNode = np.getParent(ancestorNode);\n }\n // If no ancestor fits (shouldn't happen for a well-formed tree), use root.\n if (ancestorNode === -1) ancestorNode = this.root;\n\n // 6. Re-insert downward from the ancestor.\n this.insertIntoNode(ancestorNode, objectIndex);\n }\n\n /**\n * Cast a ray through the octree and return the closest intersecting object.\n *\n * Uses an iterative stack traversal (pre-allocated, no recursion) to avoid\n * GC pressure. Only descends into child nodes whose AABBs are intersected by\n * the ray, providing efficient pruning of non-intersecting subtrees.\n *\n * @param rayBuf Float32Array containing the ray data [ox,oy,oz,dx,dy,dz].\n * @param rayOffset Element offset (in floats) within `rayBuf` to the ray's origin.\n * @returns The closest `{ objectIndex, t }` hit, or `null` if nothing is hit.\n */\n raycast(\n rayBuf: Float32Array,\n rayOffset: number,\n ): { objectIndex: number; t: number } | null {\n const np = this.nodePool;\n // Access the underlying flat buffers directly for zero-GC intersection tests.\n const npBuf = (np as unknown as { buffer: Float32Array }).buffer;\n const apBuf = (this.aabbPool as unknown as { buffer: Float32Array }).buffer;\n\n // Early-out: test the root AABB before touching any object data.\n if (rayIntersectsAABB(rayBuf, rayOffset, npBuf, this.root * NODE_STRIDE + NODE_AABB_OFFSET) < 0) {\n return null;\n }\n\n // Iterative DFS using the pre-allocated stack.\n this._stack.length = 0;\n this._stack.push(this.root);\n\n let closestT = Infinity;\n let closestIndex = -1;\n\n while (this._stack.length > 0) {\n const nodeIdx = this._stack.pop()!;\n\n // Test every object stored at this node level.\n const objCount = np.getObjectCount(nodeIdx);\n for (let i = 0; i < objCount; i++) {\n const objIdx = np.getObject(nodeIdx, i);\n const t = rayIntersectsAABB(rayBuf, rayOffset, apBuf, objIdx * AABB_STRIDE);\n if (t >= 0 && t < closestT) {\n closestT = t;\n closestIndex = objIdx;\n }\n }\n\n // Push only children whose AABB the ray actually intersects.\n const firstChild = np.getFirstChild(nodeIdx);\n if (firstChild !== -1) {\n for (let i = 0; i < 8; i++) {\n const childIdx = firstChild + i;\n if (rayIntersectsAABB(rayBuf, rayOffset, npBuf, childIdx * NODE_STRIDE + NODE_AABB_OFFSET) >= 0) {\n this._stack.push(childIdx);\n }\n }\n }\n }\n\n if (closestIndex === -1) return null;\n return { objectIndex: closestIndex, t: closestT };\n }\n\n /**\n * Query the octree for all objects whose AABB overlaps the given axis-aligned\n * box region and return their indices (in the AABBPool).\n *\n * Uses the same pre-allocated iterative stack as `raycast` to avoid GC\n * pressure. Descends only into child nodes whose AABBs overlap the query box,\n * pruning non-intersecting subtrees.\n *\n * @param minX Minimum X of the query box.\n * @param minY Minimum Y of the query box.\n * @param minZ Minimum Z of the query box.\n * @param maxX Maximum X of the query box.\n * @param maxY Maximum Y of the query box.\n * @param maxZ Maximum Z of the query box.\n * @returns Array of AABBPool indices for every object that overlaps the box.\n */\n queryBox(\n minX: number,\n minY: number,\n minZ: number,\n maxX: number,\n maxY: number,\n maxZ: number,\n ): number[] {\n const np = this.nodePool;\n const npBuf = (np as unknown as { buffer: Float32Array }).buffer;\n const apBuf = (this.aabbPool as unknown as { buffer: Float32Array }).buffer;\n\n const results: number[] = [];\n\n // Early-out: if the query box doesn't overlap the root, nothing to do.\n if (!aabbOverlapsBox(npBuf, this.root * NODE_STRIDE + NODE_AABB_OFFSET, minX, minY, minZ, maxX, maxY, maxZ)) {\n return results;\n }\n\n // Iterative DFS using the pre-allocated stack.\n this._stack.length = 0;\n this._stack.push(this.root);\n\n while (this._stack.length > 0) {\n const nodeIdx = this._stack.pop()!;\n\n // Test every object stored at this node level.\n const objCount = np.getObjectCount(nodeIdx);\n for (let i = 0; i < objCount; i++) {\n const objIdx = np.getObject(nodeIdx, i);\n if (aabbOverlapsBox(apBuf, objIdx * AABB_STRIDE, minX, minY, minZ, maxX, maxY, maxZ)) {\n results.push(objIdx);\n }\n }\n\n // Push only children whose AABB overlaps the query box.\n const firstChild = np.getFirstChild(nodeIdx);\n if (firstChild !== -1) {\n for (let i = 0; i < 8; i++) {\n const childIdx = firstChild + i;\n if (aabbOverlapsBox(npBuf, childIdx * NODE_STRIDE + NODE_AABB_OFFSET, minX, minY, minZ, maxX, maxY, maxZ)) {\n this._stack.push(childIdx);\n }\n }\n }\n }\n\n return results;\n }\n\n // ── Private helpers ──────────────────────────────────────────────────────\n\n private insertIntoNode(nodeIdx: number, objectIndex: number): void {\n const np = this.nodePool;\n const firstChild = np.getFirstChild(nodeIdx);\n\n if (firstChild !== -1) {\n // Internal node: try to push the object into a fitting child octant.\n for (let i = 0; i < 8; i++) {\n if (this.fitsInNode(objectIndex, firstChild + i)) {\n this.insertIntoNode(firstChild + i, objectIndex);\n return;\n }\n }\n // Object straddles one or more boundaries – keep it at this level.\n np.addObject(nodeIdx, objectIndex);\n this.objectNodeMap.set(objectIndex, nodeIdx);\n return;\n }\n\n // Leaf node.\n const count = np.getObjectCount(nodeIdx);\n if (count < MAX_OBJECTS_PER_NODE) {\n np.addObject(nodeIdx, objectIndex);\n this.objectNodeMap.set(objectIndex, nodeIdx);\n } else {\n // Capacity reached: subdivide, then retry the insertion.\n this.subdivide(nodeIdx);\n this.insertIntoNode(nodeIdx, objectIndex);\n }\n }\n\n /** Split a leaf node into 8 children and redistribute its objects. */\n private subdivide(nodeIdx: number): void {\n const np = this.nodePool;\n\n const minX = np.getAABB(nodeIdx, 0);\n const minY = np.getAABB(nodeIdx, 1);\n const minZ = np.getAABB(nodeIdx, 2);\n const maxX = np.getAABB(nodeIdx, 3);\n const maxY = np.getAABB(nodeIdx, 4);\n const maxZ = np.getAABB(nodeIdx, 5);\n\n const midX = (minX + maxX) / 2;\n const midY = (minY + maxY) / 2;\n const midZ = (minZ + maxZ) / 2;\n\n // Allocate 8 consecutive child nodes (guarantees contiguous indices).\n const firstChild = np.allocate();\n for (let i = 1; i < 8; i++) {\n np.allocate();\n }\n np.setFirstChild(nodeIdx, firstChild);\n\n // Assign each child its octant AABB.\n // Bit decomposition: bit-0 → X half, bit-1 → Y half, bit-2 → Z half.\n for (let i = 0; i < 8; i++) {\n const childIdx = firstChild + i;\n const cMinX = (i & 1) === 0 ? minX : midX;\n const cMaxX = (i & 1) === 0 ? midX : maxX;\n const cMinY = (i & 2) === 0 ? minY : midY;\n const cMaxY = (i & 2) === 0 ? midY : maxY;\n const cMinZ = (i & 4) === 0 ? minZ : midZ;\n const cMaxZ = (i & 4) === 0 ? midZ : maxZ;\n np.setAABB(childIdx, cMinX, cMinY, cMinZ, cMaxX, cMaxY, cMaxZ);\n np.setParent(childIdx, nodeIdx);\n }\n\n // Redistribute the existing objects.\n const count = np.getObjectCount(nodeIdx);\n const saved: number[] = [];\n for (let i = 0; i < count; i++) {\n saved.push(np.getObject(nodeIdx, i));\n }\n np.clearObjects(nodeIdx);\n\n for (const obj of saved) {\n // Use insertIntoNode so objectNodeMap stays consistent.\n this.objectNodeMap.delete(obj);\n this.insertIntoNode(nodeIdx, obj);\n }\n }\n\n /** Returns true when the AABB at `objectIndex` is fully contained by `nodeIdx`. */\n private fitsInNode(objectIndex: number, nodeIdx: number): boolean {\n const np = this.nodePool;\n const ap = this.aabbPool;\n return (\n ap.get(objectIndex, 0) >= np.getAABB(nodeIdx, 0) &&\n ap.get(objectIndex, 1) >= np.getAABB(nodeIdx, 1) &&\n ap.get(objectIndex, 2) >= np.getAABB(nodeIdx, 2) &&\n ap.get(objectIndex, 3) <= np.getAABB(nodeIdx, 3) &&\n ap.get(objectIndex, 4) <= np.getAABB(nodeIdx, 4) &&\n ap.get(objectIndex, 5) <= np.getAABB(nodeIdx, 5)\n );\n }\n}\n","/**\n * LiDAR Web Worker\n *\n * Handles heavy raycasting (LiDAR sweep logic) off the main thread.\n *\n * Protocol\n * --------\n * 1. Main thread sends one `LidarInitMessage` to set up shared buffers and\n * world bounds. The worker replies with `{ type: 'ready' }`.\n *\n * 2. For each sweep the main thread:\n * a. Writes up-to-date object AABB data into `aabbsSab`\n * (6 floats per object: minX, minY, minZ, maxX, maxY, maxZ).\n * b. Sends a `LidarSweepMessage` with the current `objectCount`.\n *\n * 3. The worker reads the transforms, inserts/updates objects in its local\n * Octree, casts every ray from `raysSab`, and writes results to\n * `resultsSab` (2 floats per ray: objectIndex, t; –1/–1 for a miss).\n * It then replies with `{ type: 'done', rayCount }`.\n *\n * SharedArrayBuffer layout\n * ------------------------\n * aabbsSab : Float32Array – objectCapacity × AABB_STRIDE (6) floats\n * nodesSab : Float32Array – nodeCapacity × NODE_STRIDE floats\n * raysSab : Float32Array – rayCount × RAY_STRIDE (6) floats\n * resultsSab: Float32Array – rayCount × 2 floats\n * [objectIndex (float), t (float)] per ray\n */\n\nimport { OctreeNodePool } from './octree-node.js';\nimport { AABBPool } from './aabb.js';\nimport { RAY_STRIDE } from './ray.js';\nimport { Octree } from './octree.js';\n\n// ── Public message types ──────────────────────────────────────────────────────\n\n/** Sent once from the main thread to initialise the shared buffers. */\nexport interface LidarInitMessage {\n type: 'init';\n /** SharedArrayBuffer for AABB data (objectCapacity × 6 floats). Written by the main thread. */\n aabbsSab: SharedArrayBuffer;\n /** SharedArrayBuffer for Octree node data (nodeCapacity × NODE_STRIDE floats). Managed by the worker. */\n nodesSab: SharedArrayBuffer;\n /** SharedArrayBuffer for ray data (rayCount × 6 floats). Written by the main thread (or set once). */\n raysSab: SharedArrayBuffer;\n /** SharedArrayBuffer for raycast results (rayCount × 2 floats). Written by the worker. */\n resultsSab: SharedArrayBuffer;\n /** Maximum number of objects the AABBPool can hold. */\n objectCapacity: number;\n /** Maximum number of Octree nodes the NodePool can hold. */\n nodeCapacity: number;\n /** Number of rays in the LiDAR sweep. */\n rayCount: number;\n /** World-space bounds for the root octree node. */\n worldMinX: number;\n worldMinY: number;\n worldMinZ: number;\n worldMaxX: number;\n worldMaxY: number;\n worldMaxZ: number;\n}\n\n/** Sent each frame to trigger a LiDAR sweep. */\nexport interface LidarSweepMessage {\n type: 'sweep';\n /** Number of active objects whose AABB data has been written to `aabbsSab`. */\n objectCount: number;\n}\n\nexport type LidarWorkerInMessage = LidarInitMessage | LidarSweepMessage;\n\n/** Posted by the worker after initialisation succeeds. */\nexport interface LidarReadyMessage {\n type: 'ready';\n}\n\n/** Posted by the worker after a sweep finishes. */\nexport interface LidarDoneMessage {\n type: 'done';\n /** Number of rays that were cast (equals the `rayCount` given in `init`). */\n rayCount: number;\n}\n\nexport type LidarWorkerOutMessage = LidarReadyMessage | LidarDoneMessage;\n\n// ── Processor (pure logic, no worker-global bindings) ────────────────────────\n\n/**\n * Stateful LiDAR sweep processor.\n *\n * Exported for direct use in tests and advanced integrations. The bottom of\n * this module wires it to the Web Worker global scope automatically when the\n * script runs inside a worker.\n */\nexport function createLidarProcessor(): {\n init(msg: LidarInitMessage): LidarReadyMessage;\n sweep(msg: LidarSweepMessage): LidarDoneMessage;\n} {\n let octree: Octree | null = null;\n let aabbPool: AABBPool | null = null;\n let raysBuf: Float32Array | null = null;\n let resultsBuf: Float32Array | null = null;\n let totalRayCount = 0;\n /** Tracks how many objects have been inserted into the local octree. */\n let insertedObjectCount = 0;\n\n return {\n /** Initialise shared buffers and create the Octree. */\n init(msg: LidarInitMessage): LidarReadyMessage {\n const nodePool = new OctreeNodePool(msg.nodeCapacity, msg.nodesSab);\n aabbPool = new AABBPool(msg.objectCapacity, msg.aabbsSab);\n raysBuf = new Float32Array(msg.raysSab);\n resultsBuf = new Float32Array(msg.resultsSab);\n totalRayCount = msg.rayCount;\n insertedObjectCount = 0;\n\n octree = new Octree(nodePool, aabbPool);\n octree.setBounds(\n msg.worldMinX, msg.worldMinY, msg.worldMinZ,\n msg.worldMaxX, msg.worldMaxY, msg.worldMaxZ,\n );\n\n return { type: 'ready' };\n },\n\n /**\n * Read current object AABB data from the shared buffer, update the Octree,\n * cast every sweep ray, and write results to the results buffer.\n */\n sweep(msg: LidarSweepMessage): LidarDoneMessage {\n if (octree === null || aabbPool === null || raysBuf === null || resultsBuf === null) {\n throw new Error('LidarProcessor: sweep called before init');\n }\n\n const objectCount = msg.objectCount;\n\n // Ensure the pool's allocation count reflects all active objects so that\n // the pool's `size` getter stays accurate (the buffer memory already exists\n // in the SAB – allocate() only increments the internal counter).\n while (aabbPool.size < objectCount) {\n aabbPool.allocate();\n }\n\n // Insert new objects or reposition existing ones in the octree.\n for (let i = 0; i < objectCount; i++) {\n const minX = aabbPool.get(i, 0);\n const minY = aabbPool.get(i, 1);\n const minZ = aabbPool.get(i, 2);\n const maxX = aabbPool.get(i, 3);\n const maxY = aabbPool.get(i, 4);\n const maxZ = aabbPool.get(i, 5);\n\n if (i < insertedObjectCount) {\n // Already in the tree – reposition without full rebuild.\n octree.update(i, minX, minY, minZ, maxX, maxY, maxZ);\n } else {\n // First appearance: insert using the bounds already in the SAB.\n octree.insert(i);\n insertedObjectCount++;\n }\n }\n\n // LiDAR sweep: cast each ray and write the closest hit (or miss) to\n // the results buffer. Layout: [objectIndex, t] per ray (–1/–1 = miss).\n for (let r = 0; r < totalRayCount; r++) {\n const hit = octree.raycast(raysBuf, r * RAY_STRIDE);\n if (hit !== null) {\n resultsBuf[r * 2] = hit.objectIndex;\n resultsBuf[r * 2 + 1] = hit.t;\n } else {\n resultsBuf[r * 2] = -1;\n resultsBuf[r * 2 + 1] = -1;\n }\n }\n\n return { type: 'done', rayCount: totalRayCount };\n },\n };\n}\n\n// ── Web Worker binding ────────────────────────────────────────────────────────\n// Automatically wire the processor to the worker's global message handler when\n// this script is loaded inside a Web Worker context (browser or Node.js worker).\n\ntype WorkerGlobalSelf = {\n onmessage: ((event: { data: LidarWorkerInMessage }) => void) | null;\n postMessage(data: LidarWorkerOutMessage): void;\n};\n\n// `postMessage` is available on the worker global but not in a regular Node.js\n// module context, so check for it before binding.\nconst globalScope = globalThis as Record<string, unknown>;\nif (typeof globalScope['postMessage'] === 'function' && typeof globalScope['onmessage'] !== 'undefined') {\n const workerSelf = globalThis as unknown as WorkerGlobalSelf;\n const processor = createLidarProcessor();\n\n workerSelf.onmessage = (event) => {\n const msg = event.data;\n if (msg.type === 'init') {\n workerSelf.postMessage(processor.init(msg));\n } else if (msg.type === 'sweep') {\n workerSelf.postMessage(processor.sweep(msg));\n }\n };\n}\n"],"mappings":"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","names":[]}