@spatial-engine/core 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/chunk-JF4RVZK3.js +586 -0
- package/dist/chunk-JF4RVZK3.js.map +1 -0
- package/dist/index.cjs +709 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +295 -0
- package/dist/index.d.ts +295 -0
- package/dist/index.js +116 -0
- package/dist/index.js.map +1 -0
- package/dist/lidar-worker.cjs +522 -0
- package/dist/lidar-worker.cjs.map +1 -0
- package/dist/lidar-worker.d.cts +84 -0
- package/dist/lidar-worker.d.ts +84 -0
- package/dist/lidar-worker.js +7 -0
- package/dist/lidar-worker.js.map +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/lidar-worker.ts
|
|
21
|
+
var lidar_worker_exports = {};
|
|
22
|
+
__export(lidar_worker_exports, {
|
|
23
|
+
createLidarProcessor: () => createLidarProcessor
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(lidar_worker_exports);
|
|
26
|
+
|
|
27
|
+
// src/octree-node.ts
|
|
28
|
+
var NODE_AABB_OFFSET = 0;
|
|
29
|
+
var NODE_FIRST_CHILD_OFFSET = 6;
|
|
30
|
+
var NODE_PARENT_OFFSET = 7;
|
|
31
|
+
var NODE_OBJECT_COUNT_OFFSET = 8;
|
|
32
|
+
var NODE_OBJECTS_OFFSET = 9;
|
|
33
|
+
var MAX_OBJECTS_PER_NODE = 8;
|
|
34
|
+
var NODE_STRIDE = NODE_OBJECTS_OFFSET + MAX_OBJECTS_PER_NODE;
|
|
35
|
+
var OctreeNodePool = class _OctreeNodePool {
|
|
36
|
+
buffer;
|
|
37
|
+
count = 0;
|
|
38
|
+
constructor(capacity, sharedBuffer) {
|
|
39
|
+
this.buffer = sharedBuffer ? new Float32Array(sharedBuffer, 0, capacity * NODE_STRIDE) : new Float32Array(capacity * NODE_STRIDE);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Create an OctreeNodePool backed by a new `SharedArrayBuffer`.
|
|
43
|
+
* Both the pool and the underlying `SharedArrayBuffer` are returned so the
|
|
44
|
+
* caller can transfer the buffer to a `Worker` via `postMessage`.
|
|
45
|
+
*/
|
|
46
|
+
static createShared(capacity) {
|
|
47
|
+
const sab = new SharedArrayBuffer(capacity * NODE_STRIDE * Float32Array.BYTES_PER_ELEMENT);
|
|
48
|
+
return { pool: new _OctreeNodePool(capacity, sab), sab };
|
|
49
|
+
}
|
|
50
|
+
/** Allocate a new node slot, initialise sentinel values, and return its index. */
|
|
51
|
+
allocate() {
|
|
52
|
+
const index = this.count;
|
|
53
|
+
const offset = index * NODE_STRIDE;
|
|
54
|
+
this.buffer[offset + NODE_FIRST_CHILD_OFFSET] = -1;
|
|
55
|
+
this.buffer[offset + NODE_PARENT_OFFSET] = -1;
|
|
56
|
+
this.buffer[offset + NODE_OBJECT_COUNT_OFFSET] = 0;
|
|
57
|
+
this.count += 1;
|
|
58
|
+
return index;
|
|
59
|
+
}
|
|
60
|
+
/** Returns the number of allocated nodes. */
|
|
61
|
+
get size() {
|
|
62
|
+
return this.count;
|
|
63
|
+
}
|
|
64
|
+
/** Reset the pool (all allocations freed, no GC). */
|
|
65
|
+
reset() {
|
|
66
|
+
this.count = 0;
|
|
67
|
+
}
|
|
68
|
+
// ── AABB ──────────────────────────────────────────────────────────────────
|
|
69
|
+
/** Set the AABB bounds for the node at the given index. */
|
|
70
|
+
setAABB(index, minX, minY, minZ, maxX, maxY, maxZ) {
|
|
71
|
+
const off = index * NODE_STRIDE + NODE_AABB_OFFSET;
|
|
72
|
+
this.buffer[off] = minX;
|
|
73
|
+
this.buffer[off + 1] = minY;
|
|
74
|
+
this.buffer[off + 2] = minZ;
|
|
75
|
+
this.buffer[off + 3] = maxX;
|
|
76
|
+
this.buffer[off + 4] = maxY;
|
|
77
|
+
this.buffer[off + 5] = maxZ;
|
|
78
|
+
}
|
|
79
|
+
/** Read a single AABB component (0–5) from the node at the given index. */
|
|
80
|
+
getAABB(index, component) {
|
|
81
|
+
return this.buffer[index * NODE_STRIDE + NODE_AABB_OFFSET + component] ?? 0;
|
|
82
|
+
}
|
|
83
|
+
// ── First-child link ──────────────────────────────────────────────────────
|
|
84
|
+
/** Set the first-child index for the node (-1 = leaf). */
|
|
85
|
+
setFirstChild(index, childIndex) {
|
|
86
|
+
this.buffer[index * NODE_STRIDE + NODE_FIRST_CHILD_OFFSET] = childIndex;
|
|
87
|
+
}
|
|
88
|
+
/** Get the first-child index for the node (-1 = leaf). */
|
|
89
|
+
getFirstChild(index) {
|
|
90
|
+
return this.buffer[index * NODE_STRIDE + NODE_FIRST_CHILD_OFFSET] ?? -1;
|
|
91
|
+
}
|
|
92
|
+
// ── Parent link ───────────────────────────────────────────────────────────
|
|
93
|
+
/** Set the parent index for the node (-1 = root). */
|
|
94
|
+
setParent(index, parentIndex) {
|
|
95
|
+
this.buffer[index * NODE_STRIDE + NODE_PARENT_OFFSET] = parentIndex;
|
|
96
|
+
}
|
|
97
|
+
/** Get the parent index for the node (-1 = root). */
|
|
98
|
+
getParent(index) {
|
|
99
|
+
return this.buffer[index * NODE_STRIDE + NODE_PARENT_OFFSET] ?? -1;
|
|
100
|
+
}
|
|
101
|
+
// ── Object pointers ───────────────────────────────────────────────────────
|
|
102
|
+
/** Return the number of objects stored in the node at the given index. */
|
|
103
|
+
getObjectCount(index) {
|
|
104
|
+
return this.buffer[index * NODE_STRIDE + NODE_OBJECT_COUNT_OFFSET] ?? 0;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Append an object index to the node's object list.
|
|
108
|
+
* Throws a RangeError when MAX_OBJECTS_PER_NODE is exceeded.
|
|
109
|
+
*/
|
|
110
|
+
addObject(index, objectIndex) {
|
|
111
|
+
const countOff = index * NODE_STRIDE + NODE_OBJECT_COUNT_OFFSET;
|
|
112
|
+
const count = this.buffer[countOff] ?? 0;
|
|
113
|
+
if (count >= MAX_OBJECTS_PER_NODE) {
|
|
114
|
+
throw new RangeError(
|
|
115
|
+
`OctreeNodePool.addObject: node ${index} already contains MAX_OBJECTS_PER_NODE (${MAX_OBJECTS_PER_NODE}) objects`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
this.buffer[index * NODE_STRIDE + NODE_OBJECTS_OFFSET + count] = objectIndex;
|
|
119
|
+
this.buffer[countOff] = count + 1;
|
|
120
|
+
}
|
|
121
|
+
/** Return the object index stored at slot `slot` in the given node. */
|
|
122
|
+
getObject(index, slot) {
|
|
123
|
+
return this.buffer[index * NODE_STRIDE + NODE_OBJECTS_OFFSET + slot] ?? 0;
|
|
124
|
+
}
|
|
125
|
+
/** Reset the object count of the given node to zero (does not zero the slots). */
|
|
126
|
+
clearObjects(index) {
|
|
127
|
+
this.buffer[index * NODE_STRIDE + NODE_OBJECT_COUNT_OFFSET] = 0;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Remove a single object index from the node's object list.
|
|
131
|
+
* Uses swap-with-last to avoid shifting. Returns true if found and removed.
|
|
132
|
+
*/
|
|
133
|
+
removeObject(index, objectIndex) {
|
|
134
|
+
const countOff = index * NODE_STRIDE + NODE_OBJECT_COUNT_OFFSET;
|
|
135
|
+
const count = this.buffer[countOff] ?? 0;
|
|
136
|
+
const baseOff = index * NODE_STRIDE + NODE_OBJECTS_OFFSET;
|
|
137
|
+
for (let i = 0; i < count; i++) {
|
|
138
|
+
if (this.buffer[baseOff + i] === objectIndex) {
|
|
139
|
+
this.buffer[baseOff + i] = this.buffer[baseOff + count - 1] ?? 0;
|
|
140
|
+
this.buffer[countOff] = count - 1;
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// src/aabb.ts
|
|
149
|
+
var AABB_STRIDE = 6;
|
|
150
|
+
var AABBPool = class _AABBPool {
|
|
151
|
+
buffer;
|
|
152
|
+
count = 0;
|
|
153
|
+
constructor(capacity, sharedBuffer) {
|
|
154
|
+
this.buffer = sharedBuffer ? new Float32Array(sharedBuffer, 0, capacity * AABB_STRIDE) : new Float32Array(capacity * AABB_STRIDE);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Create an AABBPool backed by a new `SharedArrayBuffer`.
|
|
158
|
+
* Both the pool and the underlying `SharedArrayBuffer` are returned so the
|
|
159
|
+
* caller can transfer the buffer to a `Worker` via `postMessage`.
|
|
160
|
+
*/
|
|
161
|
+
static createShared(capacity) {
|
|
162
|
+
const sab = new SharedArrayBuffer(capacity * AABB_STRIDE * Float32Array.BYTES_PER_ELEMENT);
|
|
163
|
+
return { pool: new _AABBPool(capacity, sab), sab };
|
|
164
|
+
}
|
|
165
|
+
/** Allocate a new AABB slot and return its index. */
|
|
166
|
+
allocate() {
|
|
167
|
+
const index = this.count;
|
|
168
|
+
this.count += 1;
|
|
169
|
+
return index;
|
|
170
|
+
}
|
|
171
|
+
/** Set the bounds of an AABB at the given index. */
|
|
172
|
+
set(index, minX, minY, minZ, maxX, maxY, maxZ) {
|
|
173
|
+
const offset = index * AABB_STRIDE;
|
|
174
|
+
this.buffer[offset] = minX;
|
|
175
|
+
this.buffer[offset + 1] = minY;
|
|
176
|
+
this.buffer[offset + 2] = minZ;
|
|
177
|
+
this.buffer[offset + 3] = maxX;
|
|
178
|
+
this.buffer[offset + 4] = maxY;
|
|
179
|
+
this.buffer[offset + 5] = maxZ;
|
|
180
|
+
}
|
|
181
|
+
/** Read a single component value from the buffer. */
|
|
182
|
+
get(index, component) {
|
|
183
|
+
return this.buffer[index * AABB_STRIDE + component] ?? 0;
|
|
184
|
+
}
|
|
185
|
+
/** Returns the number of allocated AABBs. */
|
|
186
|
+
get size() {
|
|
187
|
+
return this.count;
|
|
188
|
+
}
|
|
189
|
+
/** Reset the pool (all allocations freed, no GC). */
|
|
190
|
+
reset() {
|
|
191
|
+
this.count = 0;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// src/ray.ts
|
|
196
|
+
var RAY_STRIDE = 6;
|
|
197
|
+
function rayIntersectsAABB(rayBuf, rayOffset, aabbBuf, aabbOffset) {
|
|
198
|
+
const ox = rayBuf[rayOffset] ?? 0;
|
|
199
|
+
const oy = rayBuf[rayOffset + 1] ?? 0;
|
|
200
|
+
const oz = rayBuf[rayOffset + 2] ?? 0;
|
|
201
|
+
const idx = 1 / (rayBuf[rayOffset + 3] ?? 0);
|
|
202
|
+
const idy = 1 / (rayBuf[rayOffset + 4] ?? 0);
|
|
203
|
+
const idz = 1 / (rayBuf[rayOffset + 5] ?? 0);
|
|
204
|
+
const minX = aabbBuf[aabbOffset] ?? 0;
|
|
205
|
+
const minY = aabbBuf[aabbOffset + 1] ?? 0;
|
|
206
|
+
const minZ = aabbBuf[aabbOffset + 2] ?? 0;
|
|
207
|
+
const maxX = aabbBuf[aabbOffset + 3] ?? 0;
|
|
208
|
+
const maxY = aabbBuf[aabbOffset + 4] ?? 0;
|
|
209
|
+
const maxZ = aabbBuf[aabbOffset + 5] ?? 0;
|
|
210
|
+
const t1x = (minX - ox) * idx;
|
|
211
|
+
const t2x = (maxX - ox) * idx;
|
|
212
|
+
const t1y = (minY - oy) * idy;
|
|
213
|
+
const t2y = (maxY - oy) * idy;
|
|
214
|
+
const t1z = (minZ - oz) * idz;
|
|
215
|
+
const t2z = (maxZ - oz) * idz;
|
|
216
|
+
const tmin = Math.max(Math.min(t1x, t2x), Math.min(t1y, t2y), Math.min(t1z, t2z));
|
|
217
|
+
const tmax = Math.min(Math.max(t1x, t2x), Math.max(t1y, t2y), Math.max(t1z, t2z));
|
|
218
|
+
if (tmax < 0 || !(tmin <= tmax)) return -1;
|
|
219
|
+
return tmin >= 0 ? tmin : tmax;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// src/octree.ts
|
|
223
|
+
function aabbOverlapsBox(buf, bufOffset, qMinX, qMinY, qMinZ, qMaxX, qMaxY, qMaxZ) {
|
|
224
|
+
return buf[bufOffset] <= qMaxX && buf[bufOffset + 3] >= qMinX && buf[bufOffset + 1] <= qMaxY && buf[bufOffset + 4] >= qMinY && buf[bufOffset + 2] <= qMaxZ && buf[bufOffset + 5] >= qMinZ;
|
|
225
|
+
}
|
|
226
|
+
var Octree = class {
|
|
227
|
+
nodePool;
|
|
228
|
+
aabbPool;
|
|
229
|
+
root;
|
|
230
|
+
/** Tracks which node each object (by AABBPool index) is currently stored in. */
|
|
231
|
+
objectNodeMap = /* @__PURE__ */ new Map();
|
|
232
|
+
/** Pre-allocated traversal stack reused across raycast calls to avoid GC pressure. */
|
|
233
|
+
_stack = [];
|
|
234
|
+
constructor(nodePool, aabbPool) {
|
|
235
|
+
this.nodePool = nodePool;
|
|
236
|
+
this.aabbPool = aabbPool;
|
|
237
|
+
this.root = nodePool.allocate();
|
|
238
|
+
}
|
|
239
|
+
/** The index of the root node in the underlying OctreeNodePool. */
|
|
240
|
+
get rootIndex() {
|
|
241
|
+
return this.root;
|
|
242
|
+
}
|
|
243
|
+
/** Set the world-space AABB that the root node covers. */
|
|
244
|
+
setBounds(minX, minY, minZ, maxX, maxY, maxZ) {
|
|
245
|
+
this.nodePool.setAABB(this.root, minX, minY, minZ, maxX, maxY, maxZ);
|
|
246
|
+
}
|
|
247
|
+
/** Insert the AABB at `objectIndex` (in the AABBPool) into the tree. */
|
|
248
|
+
insert(objectIndex) {
|
|
249
|
+
this.insertIntoNode(this.root, objectIndex);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Update the AABB at `objectIndex` with new bounds and reposition it in the
|
|
253
|
+
* tree without rebuilding. If the new bounds still fit in the current node
|
|
254
|
+
* the object stays there; otherwise it is removed and re-inserted from the
|
|
255
|
+
* lowest ancestor whose bounds fully contain the new AABB.
|
|
256
|
+
*/
|
|
257
|
+
update(objectIndex, newMinX, newMinY, newMinZ, newMaxX, newMaxY, newMaxZ) {
|
|
258
|
+
const np = this.nodePool;
|
|
259
|
+
const ap = this.aabbPool;
|
|
260
|
+
ap.set(objectIndex, newMinX, newMinY, newMinZ, newMaxX, newMaxY, newMaxZ);
|
|
261
|
+
const currentNode = this.objectNodeMap.get(objectIndex);
|
|
262
|
+
if (currentNode === void 0) return;
|
|
263
|
+
if (this.fitsInNode(objectIndex, currentNode)) return;
|
|
264
|
+
np.removeObject(currentNode, objectIndex);
|
|
265
|
+
this.objectNodeMap.delete(objectIndex);
|
|
266
|
+
let ancestorNode = np.getParent(currentNode);
|
|
267
|
+
while (ancestorNode !== -1 && !this.fitsInNode(objectIndex, ancestorNode)) {
|
|
268
|
+
ancestorNode = np.getParent(ancestorNode);
|
|
269
|
+
}
|
|
270
|
+
if (ancestorNode === -1) ancestorNode = this.root;
|
|
271
|
+
this.insertIntoNode(ancestorNode, objectIndex);
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Cast a ray through the octree and return the closest intersecting object.
|
|
275
|
+
*
|
|
276
|
+
* Uses an iterative stack traversal (pre-allocated, no recursion) to avoid
|
|
277
|
+
* GC pressure. Only descends into child nodes whose AABBs are intersected by
|
|
278
|
+
* the ray, providing efficient pruning of non-intersecting subtrees.
|
|
279
|
+
*
|
|
280
|
+
* @param rayBuf Float32Array containing the ray data [ox,oy,oz,dx,dy,dz].
|
|
281
|
+
* @param rayOffset Element offset (in floats) within `rayBuf` to the ray's origin.
|
|
282
|
+
* @returns The closest `{ objectIndex, t }` hit, or `null` if nothing is hit.
|
|
283
|
+
*/
|
|
284
|
+
raycast(rayBuf, rayOffset) {
|
|
285
|
+
const np = this.nodePool;
|
|
286
|
+
const npBuf = np.buffer;
|
|
287
|
+
const apBuf = this.aabbPool.buffer;
|
|
288
|
+
if (rayIntersectsAABB(rayBuf, rayOffset, npBuf, this.root * NODE_STRIDE + NODE_AABB_OFFSET) < 0) {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
this._stack.length = 0;
|
|
292
|
+
this._stack.push(this.root);
|
|
293
|
+
let closestT = Infinity;
|
|
294
|
+
let closestIndex = -1;
|
|
295
|
+
while (this._stack.length > 0) {
|
|
296
|
+
const nodeIdx = this._stack.pop();
|
|
297
|
+
const objCount = np.getObjectCount(nodeIdx);
|
|
298
|
+
for (let i = 0; i < objCount; i++) {
|
|
299
|
+
const objIdx = np.getObject(nodeIdx, i);
|
|
300
|
+
const t = rayIntersectsAABB(rayBuf, rayOffset, apBuf, objIdx * AABB_STRIDE);
|
|
301
|
+
if (t >= 0 && t < closestT) {
|
|
302
|
+
closestT = t;
|
|
303
|
+
closestIndex = objIdx;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
const firstChild = np.getFirstChild(nodeIdx);
|
|
307
|
+
if (firstChild !== -1) {
|
|
308
|
+
for (let i = 0; i < 8; i++) {
|
|
309
|
+
const childIdx = firstChild + i;
|
|
310
|
+
if (rayIntersectsAABB(rayBuf, rayOffset, npBuf, childIdx * NODE_STRIDE + NODE_AABB_OFFSET) >= 0) {
|
|
311
|
+
this._stack.push(childIdx);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (closestIndex === -1) return null;
|
|
317
|
+
return { objectIndex: closestIndex, t: closestT };
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Query the octree for all objects whose AABB overlaps the given axis-aligned
|
|
321
|
+
* box region and return their indices (in the AABBPool).
|
|
322
|
+
*
|
|
323
|
+
* Uses the same pre-allocated iterative stack as `raycast` to avoid GC
|
|
324
|
+
* pressure. Descends only into child nodes whose AABBs overlap the query box,
|
|
325
|
+
* pruning non-intersecting subtrees.
|
|
326
|
+
*
|
|
327
|
+
* @param minX Minimum X of the query box.
|
|
328
|
+
* @param minY Minimum Y of the query box.
|
|
329
|
+
* @param minZ Minimum Z of the query box.
|
|
330
|
+
* @param maxX Maximum X of the query box.
|
|
331
|
+
* @param maxY Maximum Y of the query box.
|
|
332
|
+
* @param maxZ Maximum Z of the query box.
|
|
333
|
+
* @returns Array of AABBPool indices for every object that overlaps the box.
|
|
334
|
+
*/
|
|
335
|
+
queryBox(minX, minY, minZ, maxX, maxY, maxZ) {
|
|
336
|
+
const np = this.nodePool;
|
|
337
|
+
const npBuf = np.buffer;
|
|
338
|
+
const apBuf = this.aabbPool.buffer;
|
|
339
|
+
const results = [];
|
|
340
|
+
if (!aabbOverlapsBox(npBuf, this.root * NODE_STRIDE + NODE_AABB_OFFSET, minX, minY, minZ, maxX, maxY, maxZ)) {
|
|
341
|
+
return results;
|
|
342
|
+
}
|
|
343
|
+
this._stack.length = 0;
|
|
344
|
+
this._stack.push(this.root);
|
|
345
|
+
while (this._stack.length > 0) {
|
|
346
|
+
const nodeIdx = this._stack.pop();
|
|
347
|
+
const objCount = np.getObjectCount(nodeIdx);
|
|
348
|
+
for (let i = 0; i < objCount; i++) {
|
|
349
|
+
const objIdx = np.getObject(nodeIdx, i);
|
|
350
|
+
if (aabbOverlapsBox(apBuf, objIdx * AABB_STRIDE, minX, minY, minZ, maxX, maxY, maxZ)) {
|
|
351
|
+
results.push(objIdx);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
const firstChild = np.getFirstChild(nodeIdx);
|
|
355
|
+
if (firstChild !== -1) {
|
|
356
|
+
for (let i = 0; i < 8; i++) {
|
|
357
|
+
const childIdx = firstChild + i;
|
|
358
|
+
if (aabbOverlapsBox(npBuf, childIdx * NODE_STRIDE + NODE_AABB_OFFSET, minX, minY, minZ, maxX, maxY, maxZ)) {
|
|
359
|
+
this._stack.push(childIdx);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return results;
|
|
365
|
+
}
|
|
366
|
+
// ── Private helpers ──────────────────────────────────────────────────────
|
|
367
|
+
insertIntoNode(nodeIdx, objectIndex) {
|
|
368
|
+
const np = this.nodePool;
|
|
369
|
+
const firstChild = np.getFirstChild(nodeIdx);
|
|
370
|
+
if (firstChild !== -1) {
|
|
371
|
+
for (let i = 0; i < 8; i++) {
|
|
372
|
+
if (this.fitsInNode(objectIndex, firstChild + i)) {
|
|
373
|
+
this.insertIntoNode(firstChild + i, objectIndex);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
np.addObject(nodeIdx, objectIndex);
|
|
378
|
+
this.objectNodeMap.set(objectIndex, nodeIdx);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
const count = np.getObjectCount(nodeIdx);
|
|
382
|
+
if (count < MAX_OBJECTS_PER_NODE) {
|
|
383
|
+
np.addObject(nodeIdx, objectIndex);
|
|
384
|
+
this.objectNodeMap.set(objectIndex, nodeIdx);
|
|
385
|
+
} else {
|
|
386
|
+
this.subdivide(nodeIdx);
|
|
387
|
+
this.insertIntoNode(nodeIdx, objectIndex);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
/** Split a leaf node into 8 children and redistribute its objects. */
|
|
391
|
+
subdivide(nodeIdx) {
|
|
392
|
+
const np = this.nodePool;
|
|
393
|
+
const minX = np.getAABB(nodeIdx, 0);
|
|
394
|
+
const minY = np.getAABB(nodeIdx, 1);
|
|
395
|
+
const minZ = np.getAABB(nodeIdx, 2);
|
|
396
|
+
const maxX = np.getAABB(nodeIdx, 3);
|
|
397
|
+
const maxY = np.getAABB(nodeIdx, 4);
|
|
398
|
+
const maxZ = np.getAABB(nodeIdx, 5);
|
|
399
|
+
const midX = (minX + maxX) / 2;
|
|
400
|
+
const midY = (minY + maxY) / 2;
|
|
401
|
+
const midZ = (minZ + maxZ) / 2;
|
|
402
|
+
const firstChild = np.allocate();
|
|
403
|
+
for (let i = 1; i < 8; i++) {
|
|
404
|
+
np.allocate();
|
|
405
|
+
}
|
|
406
|
+
np.setFirstChild(nodeIdx, firstChild);
|
|
407
|
+
for (let i = 0; i < 8; i++) {
|
|
408
|
+
const childIdx = firstChild + i;
|
|
409
|
+
const cMinX = (i & 1) === 0 ? minX : midX;
|
|
410
|
+
const cMaxX = (i & 1) === 0 ? midX : maxX;
|
|
411
|
+
const cMinY = (i & 2) === 0 ? minY : midY;
|
|
412
|
+
const cMaxY = (i & 2) === 0 ? midY : maxY;
|
|
413
|
+
const cMinZ = (i & 4) === 0 ? minZ : midZ;
|
|
414
|
+
const cMaxZ = (i & 4) === 0 ? midZ : maxZ;
|
|
415
|
+
np.setAABB(childIdx, cMinX, cMinY, cMinZ, cMaxX, cMaxY, cMaxZ);
|
|
416
|
+
np.setParent(childIdx, nodeIdx);
|
|
417
|
+
}
|
|
418
|
+
const count = np.getObjectCount(nodeIdx);
|
|
419
|
+
const saved = [];
|
|
420
|
+
for (let i = 0; i < count; i++) {
|
|
421
|
+
saved.push(np.getObject(nodeIdx, i));
|
|
422
|
+
}
|
|
423
|
+
np.clearObjects(nodeIdx);
|
|
424
|
+
for (const obj of saved) {
|
|
425
|
+
this.objectNodeMap.delete(obj);
|
|
426
|
+
this.insertIntoNode(nodeIdx, obj);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
/** Returns true when the AABB at `objectIndex` is fully contained by `nodeIdx`. */
|
|
430
|
+
fitsInNode(objectIndex, nodeIdx) {
|
|
431
|
+
const np = this.nodePool;
|
|
432
|
+
const ap = this.aabbPool;
|
|
433
|
+
return ap.get(objectIndex, 0) >= np.getAABB(nodeIdx, 0) && ap.get(objectIndex, 1) >= np.getAABB(nodeIdx, 1) && ap.get(objectIndex, 2) >= np.getAABB(nodeIdx, 2) && ap.get(objectIndex, 3) <= np.getAABB(nodeIdx, 3) && ap.get(objectIndex, 4) <= np.getAABB(nodeIdx, 4) && ap.get(objectIndex, 5) <= np.getAABB(nodeIdx, 5);
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
// src/lidar-worker.ts
|
|
438
|
+
function createLidarProcessor() {
|
|
439
|
+
let octree = null;
|
|
440
|
+
let aabbPool = null;
|
|
441
|
+
let raysBuf = null;
|
|
442
|
+
let resultsBuf = null;
|
|
443
|
+
let totalRayCount = 0;
|
|
444
|
+
let insertedObjectCount = 0;
|
|
445
|
+
return {
|
|
446
|
+
/** Initialise shared buffers and create the Octree. */
|
|
447
|
+
init(msg) {
|
|
448
|
+
const nodePool = new OctreeNodePool(msg.nodeCapacity, msg.nodesSab);
|
|
449
|
+
aabbPool = new AABBPool(msg.objectCapacity, msg.aabbsSab);
|
|
450
|
+
raysBuf = new Float32Array(msg.raysSab);
|
|
451
|
+
resultsBuf = new Float32Array(msg.resultsSab);
|
|
452
|
+
totalRayCount = msg.rayCount;
|
|
453
|
+
insertedObjectCount = 0;
|
|
454
|
+
octree = new Octree(nodePool, aabbPool);
|
|
455
|
+
octree.setBounds(
|
|
456
|
+
msg.worldMinX,
|
|
457
|
+
msg.worldMinY,
|
|
458
|
+
msg.worldMinZ,
|
|
459
|
+
msg.worldMaxX,
|
|
460
|
+
msg.worldMaxY,
|
|
461
|
+
msg.worldMaxZ
|
|
462
|
+
);
|
|
463
|
+
return { type: "ready" };
|
|
464
|
+
},
|
|
465
|
+
/**
|
|
466
|
+
* Read current object AABB data from the shared buffer, update the Octree,
|
|
467
|
+
* cast every sweep ray, and write results to the results buffer.
|
|
468
|
+
*/
|
|
469
|
+
sweep(msg) {
|
|
470
|
+
if (octree === null || aabbPool === null || raysBuf === null || resultsBuf === null) {
|
|
471
|
+
throw new Error("LidarProcessor: sweep called before init");
|
|
472
|
+
}
|
|
473
|
+
const objectCount = msg.objectCount;
|
|
474
|
+
while (aabbPool.size < objectCount) {
|
|
475
|
+
aabbPool.allocate();
|
|
476
|
+
}
|
|
477
|
+
for (let i = 0; i < objectCount; i++) {
|
|
478
|
+
const minX = aabbPool.get(i, 0);
|
|
479
|
+
const minY = aabbPool.get(i, 1);
|
|
480
|
+
const minZ = aabbPool.get(i, 2);
|
|
481
|
+
const maxX = aabbPool.get(i, 3);
|
|
482
|
+
const maxY = aabbPool.get(i, 4);
|
|
483
|
+
const maxZ = aabbPool.get(i, 5);
|
|
484
|
+
if (i < insertedObjectCount) {
|
|
485
|
+
octree.update(i, minX, minY, minZ, maxX, maxY, maxZ);
|
|
486
|
+
} else {
|
|
487
|
+
octree.insert(i);
|
|
488
|
+
insertedObjectCount++;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
for (let r = 0; r < totalRayCount; r++) {
|
|
492
|
+
const hit = octree.raycast(raysBuf, r * RAY_STRIDE);
|
|
493
|
+
if (hit !== null) {
|
|
494
|
+
resultsBuf[r * 2] = hit.objectIndex;
|
|
495
|
+
resultsBuf[r * 2 + 1] = hit.t;
|
|
496
|
+
} else {
|
|
497
|
+
resultsBuf[r * 2] = -1;
|
|
498
|
+
resultsBuf[r * 2 + 1] = -1;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return { type: "done", rayCount: totalRayCount };
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
var globalScope = globalThis;
|
|
506
|
+
if (typeof globalScope["postMessage"] === "function" && typeof globalScope["onmessage"] !== "undefined") {
|
|
507
|
+
const workerSelf = globalThis;
|
|
508
|
+
const processor = createLidarProcessor();
|
|
509
|
+
workerSelf.onmessage = (event) => {
|
|
510
|
+
const msg = event.data;
|
|
511
|
+
if (msg.type === "init") {
|
|
512
|
+
workerSelf.postMessage(processor.init(msg));
|
|
513
|
+
} else if (msg.type === "sweep") {
|
|
514
|
+
workerSelf.postMessage(processor.sweep(msg));
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
519
|
+
0 && (module.exports = {
|
|
520
|
+
createLidarProcessor
|
|
521
|
+
});
|
|
522
|
+
//# sourceMappingURL=lidar-worker.cjs.map
|
|
@@ -0,0 +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":[]}
|