@manycore/aholo-splat-transform 1.2.7 → 1.2.9

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.
Files changed (98) hide show
  1. package/CHANGELOG.md +120 -106
  2. package/COPYRIGHT.md +17 -0
  3. package/README.md +39 -39
  4. package/THIRD_PARTY_LICENSES.txt +1373 -0
  5. package/bin/cli.js +125 -118
  6. package/dist/SplatData.d.ts +67 -67
  7. package/dist/SplatData.js +167 -156
  8. package/dist/constant.d.ts +3 -3
  9. package/dist/constant.js +13 -13
  10. package/dist/file/IFile.d.ts +5 -5
  11. package/dist/file/IFile.js +1 -1
  12. package/dist/file/esz.d.ts +11 -0
  13. package/dist/file/esz.js +337 -0
  14. package/dist/file/index.d.ts +8 -7
  15. package/dist/file/index.js +7 -6
  16. package/dist/file/ksplat.d.ts +12 -12
  17. package/dist/file/ksplat.js +293 -232
  18. package/dist/file/lcc.d.ts +11 -11
  19. package/dist/file/lcc.js +161 -157
  20. package/dist/file/ply.d.ts +13 -13
  21. package/dist/file/ply.js +439 -388
  22. package/dist/file/sog.d.ts +80 -80
  23. package/dist/file/sog.js +525 -504
  24. package/dist/file/splat.d.ts +6 -6
  25. package/dist/file/splat.js +119 -99
  26. package/dist/file/spz.d.ts +11 -8
  27. package/dist/file/spz.js +597 -400
  28. package/dist/file/voxel.d.ts +43 -37
  29. package/dist/file/voxel.js +411 -280
  30. package/dist/index.d.ts +33 -33
  31. package/dist/index.js +54 -54
  32. package/dist/native/index.d.ts +54 -54
  33. package/dist/native/index.js +122 -128
  34. package/dist/native/utils.d.ts +1 -0
  35. package/dist/native/utils.js +54 -0
  36. package/dist/tasks/AutoChunkLodTask.d.ts +13 -13
  37. package/dist/tasks/AutoChunkLodTask.js +117 -117
  38. package/dist/tasks/AutoLodTask.d.ts +10 -10
  39. package/dist/tasks/AutoLodTask.js +20 -20
  40. package/dist/tasks/BaseTask.d.ts +15 -15
  41. package/dist/tasks/BaseTask.js +5 -5
  42. package/dist/tasks/FlexLodTask.d.ts +12 -12
  43. package/dist/tasks/FlexLodTask.js +54 -44
  44. package/dist/tasks/ModifyTask.d.ts +9 -9
  45. package/dist/tasks/ModifyTask.js +166 -156
  46. package/dist/tasks/ReadTask.d.ts +9 -9
  47. package/dist/tasks/ReadTask.js +29 -29
  48. package/dist/tasks/SkeletonLodTask.d.ts +10 -10
  49. package/dist/tasks/SkeletonLodTask.js +176 -156
  50. package/dist/tasks/VoxelTask.d.ts +35 -30
  51. package/dist/tasks/VoxelTask.js +40 -37
  52. package/dist/tasks/WriteTask.d.ts +12 -11
  53. package/dist/tasks/WriteTask.js +70 -70
  54. package/dist/utils/BufferReader.d.ts +12 -12
  55. package/dist/utils/BufferReader.js +45 -47
  56. package/dist/utils/Logger.d.ts +11 -11
  57. package/dist/utils/Logger.js +40 -38
  58. package/dist/utils/StreamChunkDecoder.d.ts +16 -16
  59. package/dist/utils/StreamChunkDecoder.js +31 -36
  60. package/dist/utils/index.d.ts +27 -27
  61. package/dist/utils/index.js +101 -101
  62. package/dist/utils/k-means.d.ts +4 -4
  63. package/dist/utils/k-means.js +340 -350
  64. package/dist/utils/math.d.ts +46 -46
  65. package/dist/utils/math.js +350 -351
  66. package/dist/utils/quantize-1d.d.ts +4 -4
  67. package/dist/utils/quantize-1d.js +164 -164
  68. package/dist/utils/sh-rotate.d.ts +2 -2
  69. package/dist/utils/sh-rotate.js +236 -175
  70. package/dist/utils/splat.d.ts +21 -20
  71. package/dist/utils/splat.js +397 -378
  72. package/dist/utils/voxel/binary.d.ts +8 -0
  73. package/dist/utils/voxel/binary.js +176 -0
  74. package/dist/utils/voxel/common.d.ts +178 -162
  75. package/dist/utils/voxel/common.js +1752 -1700
  76. package/dist/utils/voxel/coplanar-merge.d.ts +63 -63
  77. package/dist/utils/voxel/coplanar-merge.js +818 -819
  78. package/dist/utils/voxel/filter-cluster.d.ts +20 -0
  79. package/dist/utils/voxel/filter-cluster.js +628 -0
  80. package/dist/utils/voxel/gpu-dilation.d.ts +2 -2
  81. package/dist/utils/voxel/gpu-dilation.js +677 -665
  82. package/dist/utils/voxel/marching-cubes.d.ts +42 -42
  83. package/dist/utils/voxel/marching-cubes.js +1645 -1657
  84. package/dist/utils/voxel/mesh.d.ts +3 -3
  85. package/dist/utils/voxel/mesh.js +130 -130
  86. package/dist/utils/voxel/nav.d.ts +29 -29
  87. package/dist/utils/voxel/nav.js +1068 -1043
  88. package/dist/utils/voxel/postprocess.d.ts +23 -23
  89. package/dist/utils/voxel/postprocess.js +408 -375
  90. package/dist/utils/voxel/voxel-faces.d.ts +18 -18
  91. package/dist/utils/voxel/voxel-faces.js +662 -663
  92. package/dist/utils/voxel/voxelize.d.ts +34 -33
  93. package/dist/utils/voxel/voxelize.js +1208 -1193
  94. package/dist/utils/webgpu.d.ts +8 -8
  95. package/dist/utils/webgpu.js +122 -122
  96. package/package.json +37 -30
  97. package/dist/native/cpp/bin/linux/binding.node +0 -0
  98. package/dist/native/cpp/bin/windows/binding.node +0 -0
@@ -1,1700 +1,1752 @@
1
- /** 3D Morton (Z-order) for integer block coordinates. */
2
- export const encodeMorton3 = (x, y, z) => {
3
- let result = 0;
4
- let shift = 1;
5
- for (let i = 0; i < 17; i++) {
6
- if (x & 1) {
7
- result += shift;
8
- }
9
- if (y & 1) {
10
- result += shift * 2;
11
- }
12
- if (z & 1) {
13
- result += shift * 4;
14
- }
15
- x >>>= 1;
16
- y >>>= 1;
17
- z >>>= 1;
18
- shift *= 8;
19
- }
20
- return result;
21
- };
22
- export const decodeMorton3 = (m) => {
23
- let x = 0, y = 0, z = 0;
24
- let bit = 1;
25
- while (m > 0) {
26
- const triplet = m % 8;
27
- if (triplet & 1) {
28
- x |= bit;
29
- }
30
- if (triplet & 2) {
31
- y |= bit;
32
- }
33
- if (triplet & 4) {
34
- z |= bit;
35
- }
36
- bit <<= 1;
37
- m = Math.trunc(m / 8);
38
- }
39
- return [x, y, z];
40
- };
41
- /** Voxel leaf edge length in voxels (4³ block). */
42
- export const LEAF_SIZE = 4;
43
- export const ALPHA_THRESHOLD = 1. / 255.;
44
- export const alignGridBounds = (bounds, voxelResolution) => {
45
- const blockSize = LEAF_SIZE * voxelResolution;
46
- return {
47
- min: {
48
- x: Math.floor(bounds.min.x / blockSize) * blockSize,
49
- y: Math.floor(bounds.min.y / blockSize) * blockSize,
50
- z: Math.floor(bounds.min.z / blockSize) * blockSize
51
- },
52
- max: {
53
- x: Math.ceil(bounds.max.x / blockSize) * blockSize,
54
- y: Math.ceil(bounds.max.y / blockSize) * blockSize,
55
- z: Math.ceil(bounds.max.z / blockSize) * blockSize
56
- }
57
- };
58
- };
59
- /** Opacity-aware AABB half-extents from scale + unit quaternion. */
60
- export const extentsFromQuatScale = (sx, sy, sz, qx, qy, qz, qw, opacity, opacityThreshold = ALPHA_THRESHOLD) => {
61
- let extend = 3;
62
- if (opacity !== undefined &&
63
- opacity > opacityThreshold) {
64
- // Tight bound from opacity threshold, clamped by default 3-sigma bound.
65
- const opacityAware = Math.sqrt(2 * Math.log(opacity / opacityThreshold));
66
- if (Number.isFinite(opacityAware)) {
67
- extend = Math.min(extend, opacityAware);
68
- }
69
- }
70
- else if (opacity !== undefined && opacity <= opacityThreshold) {
71
- return { ex: 0, ey: 0, ez: 0 };
72
- }
73
- const sX = extend * sx;
74
- const sY = extend * sy;
75
- const sZ = extend * sz;
76
- const xx = qx * qx;
77
- const yy = qy * qy;
78
- const zz = qz * qz;
79
- const xy = qx * qy;
80
- const xz = qx * qz;
81
- const yz = qy * qz;
82
- const wx = qw * qx;
83
- const wy = qw * qy;
84
- const wz = qw * qz;
85
- const m00 = 1 - 2 * (yy + zz);
86
- const m01 = 2 * (xy - wz);
87
- const m02 = 2 * (xz + wy);
88
- const m10 = 2 * (xy + wz);
89
- const m11 = 1 - 2 * (xx + zz);
90
- const m12 = 2 * (yz - wx);
91
- const m20 = 2 * (xz - wy);
92
- const m21 = 2 * (yz + wx);
93
- const m22 = 1 - 2 * (xx + yy);
94
- const abs00 = Math.abs(m00);
95
- const abs01 = Math.abs(m01);
96
- const abs02 = Math.abs(m02);
97
- const abs10 = Math.abs(m10);
98
- const abs11 = Math.abs(m11);
99
- const abs12 = Math.abs(m12);
100
- const abs20 = Math.abs(m20);
101
- const abs21 = Math.abs(m21);
102
- const abs22 = Math.abs(m22);
103
- const ex = abs00 * sX + abs01 * sY + abs02 * sZ;
104
- const ey = abs10 * sX + abs11 * sY + abs12 * sZ;
105
- const ez = abs20 * sX + abs21 * sY + abs22 * sZ;
106
- return { ex, ey, ez };
107
- };
108
- const boundsOverlap = (a, bMinX, bMinY, bMinZ, bMaxX, bMaxY, bMaxZ) => {
109
- return !(a.maxX < bMinX || a.minX > bMaxX || a.maxY < bMinY || a.minY > bMaxY || a.maxZ < bMinZ || a.minZ > bMaxZ);
110
- };
111
- const quickselect = (axisData, idx, k) => {
112
- const valAt = (p) => axisData[idx[p]];
113
- const swap = (i, j) => {
114
- const t = idx[i];
115
- idx[i] = idx[j];
116
- idx[j] = t;
117
- };
118
- const n = idx.length;
119
- let l = 0;
120
- let r = n - 1;
121
- while (true) {
122
- if (r <= l + 1) {
123
- if (r === l + 1 && valAt(r) < valAt(l)) {
124
- swap(l, r);
125
- }
126
- return idx[k];
127
- }
128
- const mid = (l + r) >>> 1;
129
- swap(mid, l + 1);
130
- if (valAt(l) > valAt(r)) {
131
- swap(l, r);
132
- }
133
- if (valAt(l + 1) > valAt(r)) {
134
- swap(l + 1, r);
135
- }
136
- if (valAt(l) > valAt(l + 1)) {
137
- swap(l, l + 1);
138
- }
139
- let i = l + 1;
140
- let j = r;
141
- const pivotIdxVal = valAt(l + 1);
142
- const pivotIdx = idx[l + 1];
143
- while (true) {
144
- do {
145
- i++;
146
- } while (i <= r && valAt(i) < pivotIdxVal);
147
- do {
148
- j--;
149
- } while (j >= l && valAt(j) > pivotIdxVal);
150
- if (j < i) {
151
- break;
152
- }
153
- swap(i, j);
154
- }
155
- idx[l + 1] = idx[j];
156
- idx[j] = pivotIdx;
157
- if (j >= k) {
158
- r = j - 1;
159
- }
160
- if (j <= k) {
161
- l = i;
162
- }
163
- }
164
- };
165
- export class GaussianBVH {
166
- static MAX_LEAF_SIZE = 64;
167
- x;
168
- y;
169
- z;
170
- extents;
171
- root;
172
- constructor(x, y, z, extents) {
173
- this.x = x;
174
- this.y = y;
175
- this.z = z;
176
- this.extents = extents;
177
- const indices = new Uint32Array(x.length);
178
- for (let i = 0; i < x.length; i++) {
179
- indices[i] = i;
180
- }
181
- this.root = this.buildNode(indices);
182
- }
183
- queryOverlappingRaw(minX, minY, minZ, maxX, maxY, maxZ) {
184
- const result = [];
185
- this.queryNode(this.root, minX, minY, minZ, maxX, maxY, maxZ, result);
186
- return result;
187
- }
188
- queryOverlappingRawInto(minX, minY, minZ, maxX, maxY, maxZ, output, offset = 0) {
189
- return this.queryNodeInto(this.root, minX, minY, minZ, maxX, maxY, maxZ, output, offset, 0);
190
- }
191
- computeBounds(indices) {
192
- let minX = Infinity, minY = Infinity, minZ = Infinity;
193
- let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
194
- for (let i = 0; i < indices.length; i++) {
195
- const idx = indices[i];
196
- const ex = this.extents[idx * 3], ey = this.extents[idx * 3 + 1], ez = this.extents[idx * 3 + 2];
197
- const gx = this.x[idx], gy = this.y[idx], gz = this.z[idx];
198
- if (gx - ex < minX) {
199
- minX = gx - ex;
200
- }
201
- if (gy - ey < minY) {
202
- minY = gy - ey;
203
- }
204
- if (gz - ez < minZ) {
205
- minZ = gz - ez;
206
- }
207
- if (gx + ex > maxX) {
208
- maxX = gx + ex;
209
- }
210
- if (gy + ey > maxY) {
211
- maxY = gy + ey;
212
- }
213
- if (gz + ez > maxZ) {
214
- maxZ = gz + ez;
215
- }
216
- }
217
- return { minX, minY, minZ, maxX, maxY, maxZ };
218
- }
219
- buildNode(indices) {
220
- const bounds = this.computeBounds(indices);
221
- if (indices.length <= GaussianBVH.MAX_LEAF_SIZE) {
222
- return { bounds, indices };
223
- }
224
- let minCx = Infinity, minCy = Infinity, minCz = Infinity;
225
- let maxCx = -Infinity, maxCy = -Infinity, maxCz = -Infinity;
226
- for (let i = 0; i < indices.length; i++) {
227
- const idx = indices[i];
228
- const cx = this.x[idx], cy = this.y[idx], cz = this.z[idx];
229
- if (cx < minCx) {
230
- minCx = cx;
231
- }
232
- if (cy < minCy) {
233
- minCy = cy;
234
- }
235
- if (cz < minCz) {
236
- minCz = cz;
237
- }
238
- if (cx > maxCx) {
239
- maxCx = cx;
240
- }
241
- if (cy > maxCy) {
242
- maxCy = cy;
243
- }
244
- if (cz > maxCz) {
245
- maxCz = cz;
246
- }
247
- }
248
- const ex = maxCx - minCx, ey = maxCy - minCy, ez = maxCz - minCz;
249
- const axis = ex >= ey && ex >= ez ? this.x : (ey >= ez ? this.y : this.z);
250
- const mid = indices.length >>> 1;
251
- quickselect(axis, indices, mid);
252
- return { bounds, left: this.buildNode(indices.subarray(0, mid)), right: this.buildNode(indices.subarray(mid)) };
253
- }
254
- queryNode(node, minX, minY, minZ, maxX, maxY, maxZ, result) {
255
- if (!boundsOverlap(node.bounds, minX, minY, minZ, maxX, maxY, maxZ)) {
256
- return;
257
- }
258
- if (node.indices) {
259
- for (let i = 0; i < node.indices.length; i++) {
260
- const idx = node.indices[i];
261
- const ex = this.extents[idx * 3], ey = this.extents[idx * 3 + 1], ez = this.extents[idx * 3 + 2];
262
- const gx = this.x[idx], gy = this.y[idx], gz = this.z[idx];
263
- if (!(gx + ex < minX || gx - ex > maxX || gy + ey < minY || gy - ey > maxY || gz + ez < minZ || gz - ez > maxZ)) {
264
- result.push(idx);
265
- }
266
- }
267
- return;
268
- }
269
- if (node.left) {
270
- this.queryNode(node.left, minX, minY, minZ, maxX, maxY, maxZ, result);
271
- }
272
- if (node.right) {
273
- this.queryNode(node.right, minX, minY, minZ, maxX, maxY, maxZ, result);
274
- }
275
- }
276
- queryNodeInto(node, minX, minY, minZ, maxX, maxY, maxZ, output, offset, count) {
277
- if (!boundsOverlap(node.bounds, minX, minY, minZ, maxX, maxY, maxZ)) {
278
- return count;
279
- }
280
- if (node.indices) {
281
- for (let i = 0; i < node.indices.length; i++) {
282
- const idx = node.indices[i];
283
- const ex = this.extents[idx * 3], ey = this.extents[idx * 3 + 1], ez = this.extents[idx * 3 + 2];
284
- const gx = this.x[idx], gy = this.y[idx], gz = this.z[idx];
285
- if (!(gx + ex < minX || gx - ex > maxX || gy + ey < minY || gy - ey > maxY || gz + ez < minZ || gz - ez > maxZ)) {
286
- if (offset + count < output.length) {
287
- output[offset + count] = idx;
288
- }
289
- count++;
290
- }
291
- }
292
- return count;
293
- }
294
- if (node.left) {
295
- count = this.queryNodeInto(node.left, minX, minY, minZ, maxX, maxY, maxZ, output, offset, count);
296
- }
297
- if (node.right) {
298
- count = this.queryNodeInto(node.right, minX, minY, minZ, maxX, maxY, maxZ, output, offset, count);
299
- }
300
- return count;
301
- }
302
- }
303
- const SOLID_LO = 0xFFFFFFFF >>> 0;
304
- const SOLID_HI = 0xFFFFFFFF >>> 0;
305
- const SOLID_MASK = 0xFFFFFFFF >>> 0;
306
- const INITIAL_BLOCK_BUFFER_CAPACITY = 1024;
307
- const growFloat64 = (src, newCap) => {
308
- const grown = new Float64Array(newCap);
309
- grown.set(src);
310
- return grown;
311
- };
312
- const growUint32 = (src, newCap) => {
313
- const grown = new Uint32Array(newCap);
314
- grown.set(src);
315
- return grown;
316
- };
317
- /**
318
- * Append-only buffer for streaming voxelization results.
319
- * Stores (linear blockIdx, voxel mask) pairs for non-empty 4x4x4 blocks.
320
- *
321
- * Block keys are linear block indices `bx + by*nbx + bz*nbx*nby` in the
322
- * producer's grid coordinate system. Producers and consumers must agree on
323
- * the grid dimensions; the buffer itself is dimension-agnostic.
324
- */
325
- export class BlockMaskBuffer {
326
- solidIdx = new Float64Array(0);
327
- solidCountValue = 0;
328
- solidCap = 0;
329
- mixedIdx = new Float64Array(0);
330
- mixedCountValue = 0;
331
- mixedCap = 0;
332
- mixedMasks = new Uint32Array(0);
333
- addBlock(blockIdx, lo, hi) {
334
- if ((lo | hi) === 0) {
335
- return;
336
- }
337
- if ((lo >>> 0) === SOLID_MASK && (hi >>> 0) === SOLID_MASK) {
338
- if (this.solidCountValue === this.solidCap) {
339
- this.solidCap = this.solidCap === 0 ? INITIAL_BLOCK_BUFFER_CAPACITY : this.solidCap * 2;
340
- this.solidIdx = growFloat64(this.solidIdx, this.solidCap);
341
- }
342
- this.solidIdx[this.solidCountValue++] = blockIdx;
343
- return;
344
- }
345
- if (this.mixedCountValue === this.mixedCap) {
346
- this.mixedCap = this.mixedCap === 0 ? INITIAL_BLOCK_BUFFER_CAPACITY : this.mixedCap * 2;
347
- this.mixedIdx = growFloat64(this.mixedIdx, this.mixedCap);
348
- this.mixedMasks = growUint32(this.mixedMasks, this.mixedCap * 2);
349
- }
350
- this.mixedIdx[this.mixedCountValue] = blockIdx;
351
- this.mixedMasks[this.mixedCountValue * 2] = lo >>> 0;
352
- this.mixedMasks[this.mixedCountValue * 2 + 1] = hi >>> 0;
353
- this.mixedCountValue++;
354
- }
355
- getMixedBlocks() {
356
- return {
357
- blockIdx: this.mixedIdx.subarray(0, this.mixedCountValue),
358
- masks: this.mixedMasks.subarray(0, this.mixedCountValue * 2)
359
- };
360
- }
361
- getSolidBlocks() {
362
- return this.solidIdx.subarray(0, this.solidCountValue);
363
- }
364
- get count() {
365
- return this.mixedCountValue + this.solidCountValue;
366
- }
367
- get mixedCount() {
368
- return this.mixedCountValue;
369
- }
370
- get solidCount() {
371
- return this.solidCountValue;
372
- }
373
- clear() {
374
- this.solidIdx = new Float64Array(0);
375
- this.solidCountValue = 0;
376
- this.solidCap = 0;
377
- this.mixedIdx = new Float64Array(0);
378
- this.mixedMasks = new Uint32Array(0);
379
- this.mixedCountValue = 0;
380
- this.mixedCap = 0;
381
- }
382
- }
383
- const BLOCK_EMPTY = 0;
384
- const BLOCK_SOLID = 1;
385
- const BLOCK_MIXED = 2;
386
- const TYPE_MASK = 0x3;
387
- const BLOCKS_PER_WORD = 16;
388
- const EVEN_BITS = 0x55555555 >>> 0;
389
- const readBlockType = (types, blockIdx) => {
390
- const word = blockIdx >>> 4;
391
- const shift = (blockIdx & 15) << 1;
392
- return (types[word] >>> shift) & TYPE_MASK;
393
- };
394
- const writeBlockType = (types, blockIdx, blockType) => {
395
- const word = blockIdx >>> 4;
396
- const shift = (blockIdx & 15) << 1;
397
- const mask = TYPE_MASK << shift;
398
- types[word] = (types[word] & (~mask)) | ((blockType & TYPE_MASK) << shift);
399
- };
400
- const EMPTY = -1;
401
- class BlockMaskMap {
402
- keys;
403
- lo;
404
- hi;
405
- _size;
406
- _capacity;
407
- _mask;
408
- constructor(initialCapacity = 4096) {
409
- const cap = 1 << (32 - Math.clz32(Math.max(15, initialCapacity - 1)));
410
- this._capacity = cap;
411
- this._mask = cap - 1;
412
- this._size = 0;
413
- this.keys = new Int32Array(cap).fill(EMPTY);
414
- this.lo = new Uint32Array(cap);
415
- this.hi = new Uint32Array(cap);
416
- }
417
- slot(key) {
418
- const mask = this._mask;
419
- let i = (Math.imul(key, 0x9E3779B9) >>> 0) & mask;
420
- while (true) {
421
- const k = this.keys[i];
422
- if (k === key || k === EMPTY) {
423
- return i;
424
- }
425
- i = (i + 1) & mask;
426
- }
427
- }
428
- set(key, loVal, hiVal) {
429
- let s = this.slot(key);
430
- if (this.keys[s] === EMPTY) {
431
- this.keys[s] = key;
432
- this._size++;
433
- if (this._size > ((this._capacity * 0.7) | 0)) {
434
- this._grow();
435
- s = this.slot(key);
436
- }
437
- }
438
- this.lo[s] = loVal;
439
- this.hi[s] = hiVal;
440
- }
441
- removeAt(slot) {
442
- this._size--;
443
- const mask = this._mask;
444
- let i = slot;
445
- let j = slot;
446
- while (true) {
447
- j = (j + 1) & mask;
448
- if (this.keys[j] === EMPTY) {
449
- break;
450
- }
451
- const k = ((Math.imul(this.keys[j], 0x9E3779B9) >>> 0) & mask);
452
- if ((i < j) ? (k <= i || k > j) : (k <= i && k > j)) {
453
- this.keys[i] = this.keys[j];
454
- this.lo[i] = this.lo[j];
455
- this.hi[i] = this.hi[j];
456
- i = j;
457
- }
458
- }
459
- this.keys[i] = EMPTY;
460
- }
461
- clear() {
462
- this.keys.fill(EMPTY);
463
- this._size = 0;
464
- }
465
- get size() {
466
- return this._size;
467
- }
468
- releaseStorage() {
469
- this.keys = new Int32Array(0);
470
- this.lo = new Uint32Array(0);
471
- this.hi = new Uint32Array(0);
472
- this._size = 0;
473
- this._capacity = 0;
474
- this._mask = 0;
475
- }
476
- clone() {
477
- const c = new BlockMaskMap(this._capacity);
478
- c.keys.set(this.keys);
479
- c.lo.set(this.lo);
480
- c.hi.set(this.hi);
481
- c._size = this._size;
482
- return c;
483
- }
484
- _grow() {
485
- const oldKeys = this.keys;
486
- const oldLo = this.lo;
487
- const oldHi = this.hi;
488
- const oldCap = this._capacity;
489
- this._capacity *= 2;
490
- this._mask = this._capacity - 1;
491
- this.keys = new Int32Array(this._capacity).fill(EMPTY);
492
- this.lo = new Uint32Array(this._capacity);
493
- this.hi = new Uint32Array(this._capacity);
494
- this._size = 0;
495
- for (let i = 0; i < oldCap; i++) {
496
- if (oldKeys[i] !== EMPTY) {
497
- const s = this.slot(oldKeys[i]);
498
- this.keys[s] = oldKeys[i];
499
- this.lo[s] = oldLo[i];
500
- this.hi[s] = oldHi[i];
501
- this._size++;
502
- }
503
- }
504
- }
505
- }
506
- class SparseVoxelGrid {
507
- nx;
508
- ny;
509
- nz;
510
- nbx;
511
- nby;
512
- nbz;
513
- bStride;
514
- types;
515
- masks;
516
- constructor(nx, ny, nz) {
517
- this.nx = nx;
518
- this.ny = ny;
519
- this.nz = nz;
520
- this.nbx = nx >> 2;
521
- this.nby = ny >> 2;
522
- this.nbz = nz >> 2;
523
- this.bStride = this.nbx * this.nby;
524
- const totalBlocks = this.nbx * this.nby * this.nbz;
525
- this.types = new Uint32Array((totalBlocks + BLOCKS_PER_WORD - 1) >>> 4);
526
- this.masks = new BlockMaskMap();
527
- }
528
- getVoxel(ix, iy, iz) {
529
- const blockIdx = (ix >> 2) + (iy >> 2) * this.nbx + (iz >> 2) * this.bStride;
530
- const bt = readBlockType(this.types, blockIdx);
531
- if (bt === BLOCK_EMPTY) {
532
- return 0;
533
- }
534
- if (bt === BLOCK_SOLID) {
535
- return 1;
536
- }
537
- const s = this.masks.slot(blockIdx);
538
- const bitIdx = (ix & 3) + ((iy & 3) << 2) + ((iz & 3) << 4);
539
- return bitIdx < 32 ? (this.masks.lo[s] >>> bitIdx) & 1 : (this.masks.hi[s] >>> (bitIdx - 32)) & 1;
540
- }
541
- setVoxel(ix, iy, iz) {
542
- const blockIdx = (ix >> 2) + (iy >> 2) * this.nbx + (iz >> 2) * this.bStride;
543
- const bt = readBlockType(this.types, blockIdx);
544
- if (bt === BLOCK_SOLID) {
545
- return;
546
- }
547
- const bitIdx = (ix & 3) + ((iy & 3) << 2) + ((iz & 3) << 4);
548
- if (bt === BLOCK_MIXED) {
549
- const s = this.masks.slot(blockIdx);
550
- if (bitIdx < 32) {
551
- this.masks.lo[s] = (this.masks.lo[s] | (1 << bitIdx)) >>> 0;
552
- }
553
- else {
554
- this.masks.hi[s] = (this.masks.hi[s] | (1 << (bitIdx - 32))) >>> 0;
555
- }
556
- if (this.masks.lo[s] === SOLID_LO && this.masks.hi[s] === SOLID_HI) {
557
- this.masks.removeAt(s);
558
- writeBlockType(this.types, blockIdx, BLOCK_SOLID);
559
- }
560
- }
561
- else {
562
- writeBlockType(this.types, blockIdx, BLOCK_MIXED);
563
- this.masks.set(blockIdx, bitIdx < 32 ? (1 << bitIdx) >>> 0 : 0, bitIdx >= 32 ? (1 << (bitIdx - 32)) >>> 0 : 0);
564
- }
565
- }
566
- orBlock(blockIdx, lo, hi) {
567
- if (lo === 0 && hi === 0) {
568
- return;
569
- }
570
- const bt = readBlockType(this.types, blockIdx);
571
- if (bt === BLOCK_SOLID) {
572
- return;
573
- }
574
- if (bt === BLOCK_MIXED) {
575
- const s = this.masks.slot(blockIdx);
576
- this.masks.lo[s] = (this.masks.lo[s] | lo) >>> 0;
577
- this.masks.hi[s] = (this.masks.hi[s] | hi) >>> 0;
578
- if (this.masks.lo[s] === SOLID_LO && this.masks.hi[s] === SOLID_HI) {
579
- this.masks.removeAt(s);
580
- writeBlockType(this.types, blockIdx, BLOCK_SOLID);
581
- }
582
- }
583
- else {
584
- if ((lo >>> 0) === SOLID_LO && (hi >>> 0) === SOLID_HI) {
585
- writeBlockType(this.types, blockIdx, BLOCK_SOLID);
586
- }
587
- else {
588
- writeBlockType(this.types, blockIdx, BLOCK_MIXED);
589
- this.masks.set(blockIdx, lo >>> 0, hi >>> 0);
590
- }
591
- }
592
- }
593
- clear() {
594
- this.types.fill(0);
595
- this.masks.clear();
596
- }
597
- releaseStorage() {
598
- this.types = new Uint32Array(0);
599
- this.masks.releaseStorage();
600
- }
601
- clone() {
602
- const g = new SparseVoxelGrid(this.nx, this.ny, this.nz);
603
- g.types.set(this.types);
604
- g.masks = this.masks.clone();
605
- return g;
606
- }
607
- cropTo(cropMinBx, cropMinBy, cropMinBz, cropMaxBx, cropMaxBy, cropMaxBz, onProgress) {
608
- const outNbx = cropMaxBx - cropMinBx;
609
- const outNby = cropMaxBy - cropMinBy;
610
- const outNbz = cropMaxBz - cropMinBz;
611
- const out = new SparseVoxelGrid(outNbx * 4, outNby * 4, outNbz * 4);
612
- const outBStride = outNbx * outNby;
613
- const { nbx, nby } = this;
614
- const totalBlocks = nbx * nby * this.nbz;
615
- const types = this.types;
616
- const masks = this.masks;
617
- const outTypes = out.types;
618
- const outMasks = out.masks;
619
- if (out.nbx * out.nby * out.nbz === 0) {
620
- if (onProgress) {
621
- onProgress(0, 0);
622
- }
623
- return out;
624
- }
625
- const PROGRESS_INTERVAL = 1 << 13;
626
- let nextTick = PROGRESS_INTERVAL;
627
- for (let w = 0; w < types.length; w++) {
628
- if (onProgress && w >= nextTick) {
629
- onProgress(w, types.length);
630
- nextTick = w + PROGRESS_INTERVAL;
631
- }
632
- const word = types[w];
633
- if (word === 0) {
634
- continue;
635
- }
636
- let nonEmpty = ((word & EVEN_BITS) | ((word >>> 1) & EVEN_BITS)) >>> 0;
637
- const baseIdx = w * BLOCKS_PER_WORD;
638
- let bx = baseIdx % nbx;
639
- const byBz = (baseIdx / nbx) | 0;
640
- let by = byBz % nby;
641
- let bz = (byBz / nby) | 0;
642
- let coordLane = 0;
643
- while (nonEmpty) {
644
- const bp = 31 - Math.clz32(nonEmpty & -nonEmpty);
645
- const lane = bp >>> 1;
646
- nonEmpty &= nonEmpty - 1;
647
- const blockIdx = baseIdx + lane;
648
- if (blockIdx >= totalBlocks) {
649
- break;
650
- }
651
- bx += lane - coordLane;
652
- coordLane = lane;
653
- while (bx >= nbx) {
654
- bx -= nbx;
655
- by++;
656
- if (by >= nby) {
657
- by = 0;
658
- bz++;
659
- }
660
- }
661
- if (bx < cropMinBx || bx >= cropMaxBx ||
662
- by < cropMinBy || by >= cropMaxBy ||
663
- bz < cropMinBz || bz >= cropMaxBz) {
664
- continue;
665
- }
666
- const outIdx = (bx - cropMinBx) + (by - cropMinBy) * outNbx + (bz - cropMinBz) * outBStride;
667
- const bt = (word >>> (lane << 1)) & TYPE_MASK;
668
- writeBlockType(outTypes, outIdx, bt);
669
- if (bt === BLOCK_MIXED) {
670
- const s = masks.slot(blockIdx);
671
- outMasks.set(outIdx, masks.lo[s], masks.hi[s]);
672
- }
673
- }
674
- }
675
- if (onProgress) {
676
- onProgress(types.length, types.length);
677
- }
678
- return out;
679
- }
680
- cropToInverted(cropMinBx, cropMinBy, cropMinBz, cropMaxBx, cropMaxBy, cropMaxBz, onProgress) {
681
- const outNbx = cropMaxBx - cropMinBx;
682
- const outNby = cropMaxBy - cropMinBy;
683
- const outNbz = cropMaxBz - cropMinBz;
684
- const out = new SparseVoxelGrid(outNbx * 4, outNby * 4, outNbz * 4);
685
- const outBStride = outNbx * outNby;
686
- const outTotalBlocks = outNbx * outNby * outNbz;
687
- const outTypes = out.types;
688
- const outMasks = out.masks;
689
- if (outTotalBlocks === 0) {
690
- if (onProgress) {
691
- onProgress(0, 0);
692
- }
693
- return out;
694
- }
695
- const SOLID_WORD = 0x55555555 >>> 0;
696
- outTypes.fill(SOLID_WORD);
697
- const lastWord = outTypes.length - 1;
698
- const lastLanes = outTotalBlocks - lastWord * BLOCKS_PER_WORD;
699
- if (lastLanes < BLOCKS_PER_WORD) {
700
- const validBits = (1 << (lastLanes * 2)) - 1;
701
- outTypes[lastWord] = (outTypes[lastWord] & validBits) >>> 0;
702
- }
703
- const { nbx, nby } = this;
704
- const types = this.types;
705
- const masks = this.masks;
706
- const totalBlocks = nbx * nby * this.nbz;
707
- const PROGRESS_INTERVAL = 1 << 13;
708
- let nextTick = PROGRESS_INTERVAL;
709
- for (let w = 0; w < types.length; w++) {
710
- if (onProgress && w >= nextTick) {
711
- onProgress(w, types.length);
712
- nextTick = w + PROGRESS_INTERVAL;
713
- }
714
- const word = types[w];
715
- if (word === 0) {
716
- continue;
717
- }
718
- let nonEmpty = ((word & EVEN_BITS) | ((word >>> 1) & EVEN_BITS)) >>> 0;
719
- const baseIdx = w * BLOCKS_PER_WORD;
720
- let bx = baseIdx % nbx;
721
- const byBz = (baseIdx / nbx) | 0;
722
- let by = byBz % nby;
723
- let bz = (byBz / nby) | 0;
724
- let coordLane = 0;
725
- while (nonEmpty) {
726
- const bp = 31 - Math.clz32(nonEmpty & -nonEmpty);
727
- const lane = bp >>> 1;
728
- nonEmpty &= nonEmpty - 1;
729
- const blockIdx = baseIdx + lane;
730
- if (blockIdx >= totalBlocks) {
731
- break;
732
- }
733
- bx += lane - coordLane;
734
- coordLane = lane;
735
- while (bx >= nbx) {
736
- bx -= nbx;
737
- by++;
738
- if (by >= nby) {
739
- by = 0;
740
- bz++;
741
- }
742
- }
743
- if (bx < cropMinBx || bx >= cropMaxBx ||
744
- by < cropMinBy || by >= cropMaxBy ||
745
- bz < cropMinBz || bz >= cropMaxBz) {
746
- continue;
747
- }
748
- const outIdx = (bx - cropMinBx) + (by - cropMinBy) * outNbx + (bz - cropMinBz) * outBStride;
749
- const bt = (word >>> (lane << 1)) & TYPE_MASK;
750
- if (bt === BLOCK_SOLID) {
751
- writeBlockType(outTypes, outIdx, BLOCK_EMPTY);
752
- }
753
- else {
754
- writeBlockType(outTypes, outIdx, BLOCK_MIXED);
755
- const s = masks.slot(blockIdx);
756
- outMasks.set(outIdx, (~masks.lo[s]) >>> 0, (~masks.hi[s]) >>> 0);
757
- }
758
- }
759
- }
760
- if (onProgress) {
761
- onProgress(types.length, types.length);
762
- }
763
- return out;
764
- }
765
- static fromBuffer(acc, nx, ny, nz) {
766
- const g = new SparseVoxelGrid(nx, ny, nz);
767
- const solidBlocks = acc.getSolidBlocks();
768
- const totalBlocks = g.nbx * g.nby * g.nbz;
769
- for (let i = 0; i < solidBlocks.length; i++) {
770
- const blockIdx = solidBlocks[i];
771
- if (blockIdx < 0 || blockIdx >= totalBlocks) {
772
- continue;
773
- }
774
- writeBlockType(g.types, blockIdx, BLOCK_SOLID);
775
- }
776
- const mixed = acc.getMixedBlocks();
777
- for (let i = 0; i < mixed.blockIdx.length; i++) {
778
- const blockIdx = mixed.blockIdx[i];
779
- if (blockIdx < 0 || blockIdx >= totalBlocks) {
780
- continue;
781
- }
782
- if (readBlockType(g.types, blockIdx) === BLOCK_SOLID) {
783
- continue;
784
- }
785
- writeBlockType(g.types, blockIdx, BLOCK_MIXED);
786
- g.masks.set(blockIdx, mixed.masks[i * 2], mixed.masks[i * 2 + 1]);
787
- }
788
- return g;
789
- }
790
- toBuffer(cropMinBx, cropMinBy, cropMinBz, cropMaxBx, cropMaxBy, cropMaxBz, defaultSolid = false) {
791
- const out = new BlockMaskBuffer();
792
- for (let bz = cropMinBz; bz < cropMaxBz; bz++) {
793
- for (let by = cropMinBy; by < cropMaxBy; by++) {
794
- for (let bx = cropMinBx; bx < cropMaxBx; bx++) {
795
- const blockIdx = bx + by * this.nbx + bz * this.bStride;
796
- const bt = readBlockType(this.types, blockIdx);
797
- let lo;
798
- let hi;
799
- if (bt === BLOCK_SOLID) {
800
- lo = SOLID_LO;
801
- hi = SOLID_HI;
802
- }
803
- else if (bt === BLOCK_MIXED) {
804
- const s = this.masks.slot(blockIdx);
805
- lo = this.masks.lo[s];
806
- hi = this.masks.hi[s];
807
- }
808
- else if (defaultSolid) {
809
- lo = SOLID_LO;
810
- hi = SOLID_HI;
811
- }
812
- else {
813
- continue;
814
- }
815
- if ((lo | hi) !== 0) {
816
- const outNbx = cropMaxBx - cropMinBx;
817
- const outNby = cropMaxBy - cropMinBy;
818
- out.addBlock((bx - cropMinBx) + (by - cropMinBy) * outNbx + (bz - cropMinBz) * outNbx * outNby, lo, hi);
819
- }
820
- }
821
- }
822
- }
823
- return out;
824
- }
825
- toBufferInverted(cropMinBx, cropMinBy, cropMinBz, cropMaxBx, cropMaxBy, cropMaxBz) {
826
- const out = new BlockMaskBuffer();
827
- for (let bz = cropMinBz; bz < cropMaxBz; bz++) {
828
- for (let by = cropMinBy; by < cropMaxBy; by++) {
829
- for (let bx = cropMinBx; bx < cropMaxBx; bx++) {
830
- const blockIdx = bx + by * this.nbx + bz * this.bStride;
831
- const bt = readBlockType(this.types, blockIdx);
832
- let lo;
833
- let hi;
834
- if (bt === BLOCK_SOLID) {
835
- continue;
836
- }
837
- if (bt === BLOCK_MIXED) {
838
- const s = this.masks.slot(blockIdx);
839
- lo = (~this.masks.lo[s]) >>> 0;
840
- hi = (~this.masks.hi[s]) >>> 0;
841
- }
842
- else {
843
- lo = SOLID_LO;
844
- hi = SOLID_HI;
845
- }
846
- if ((lo | hi) !== 0) {
847
- const outNbx = cropMaxBx - cropMinBx;
848
- const outNby = cropMaxBy - cropMinBy;
849
- out.addBlock((bx - cropMinBx) + (by - cropMinBy) * outNbx + (bz - cropMinBz) * outNbx * outNby, lo, hi);
850
- }
851
- }
852
- }
853
- }
854
- return out;
855
- }
856
- getOccupiedBlockBounds(onProgress) {
857
- const { nbx, nby } = this;
858
- const totalBlocks = nbx * nby * this.nbz;
859
- let minBx = nbx, minBy = nby, minBz = this.nbz;
860
- let maxBx = 0, maxBy = 0, maxBz = 0;
861
- let found = false;
862
- const PROGRESS_INTERVAL = 1 << 13;
863
- let nextTick = PROGRESS_INTERVAL;
864
- for (let w = 0; w < this.types.length; w++) {
865
- if (onProgress && w >= nextTick) {
866
- onProgress(w, this.types.length);
867
- nextTick = w + PROGRESS_INTERVAL;
868
- }
869
- const word = this.types[w];
870
- if (word === 0) {
871
- continue;
872
- }
873
- let nonEmpty = ((word & EVEN_BITS) | ((word >>> 1) & EVEN_BITS)) >>> 0;
874
- const baseIdx = w * BLOCKS_PER_WORD;
875
- let bx = baseIdx % nbx;
876
- const byBz = (baseIdx / nbx) | 0;
877
- let by = byBz % nby;
878
- let bz = (byBz / nby) | 0;
879
- let coordLane = 0;
880
- while (nonEmpty) {
881
- const bitPos = 31 - Math.clz32(nonEmpty & -nonEmpty);
882
- const lane = bitPos >>> 1;
883
- const blockIdx = baseIdx + lane;
884
- if (blockIdx >= totalBlocks) {
885
- nonEmpty = 0;
886
- break;
887
- }
888
- bx += lane - coordLane;
889
- coordLane = lane;
890
- while (bx >= nbx) {
891
- bx -= nbx;
892
- by++;
893
- if (by >= nby) {
894
- by = 0;
895
- bz++;
896
- }
897
- }
898
- if (bx < minBx) {
899
- minBx = bx;
900
- }
901
- if (bx > maxBx) {
902
- maxBx = bx;
903
- }
904
- if (by < minBy) {
905
- minBy = by;
906
- }
907
- if (by > maxBy) {
908
- maxBy = by;
909
- }
910
- if (bz < minBz) {
911
- minBz = bz;
912
- }
913
- if (bz > maxBz) {
914
- maxBz = bz;
915
- }
916
- found = true;
917
- nonEmpty &= nonEmpty - 1;
918
- }
919
- }
920
- if (onProgress) {
921
- onProgress(this.types.length, this.types.length);
922
- }
923
- return found ? { minBx, minBy, minBz, maxBx, maxBy, maxBz } : null;
924
- }
925
- getNavigableBlockBounds(onProgress) {
926
- const { nbx, nby } = this;
927
- const totalBlocks = nbx * nby * this.nbz;
928
- if (totalBlocks === 0) {
929
- if (onProgress) {
930
- onProgress(0, 0);
931
- }
932
- return null;
933
- }
934
- const SOLID_WORD = 0x55555555 >>> 0;
935
- const lastWordIdx = this.types.length - 1;
936
- const lastLanes = totalBlocks - lastWordIdx * BLOCKS_PER_WORD;
937
- const lastNonEmptyMask = lastLanes >= BLOCKS_PER_WORD ? EVEN_BITS : ((((1 << (lastLanes * 2)) - 1) >>> 0) & EVEN_BITS);
938
- let minBx = nbx, minBy = nby, minBz = this.nbz;
939
- let maxBx = -1, maxBy = 0, maxBz = 0;
940
- const PROGRESS_INTERVAL = 1 << 13;
941
- let nextTick = PROGRESS_INTERVAL;
942
- for (let w = 0; w < this.types.length; w++) {
943
- if (onProgress && w >= nextTick) {
944
- onProgress(w, this.types.length);
945
- nextTick = w + PROGRESS_INTERVAL;
946
- }
947
- const baseIdx = w * BLOCKS_PER_WORD;
948
- const word = this.types[w];
949
- const flipped = (word ^ SOLID_WORD) >>> 0;
950
- let navMask = ((flipped & EVEN_BITS) | ((flipped >>> 1) & EVEN_BITS)) >>> 0;
951
- if (w === lastWordIdx) {
952
- navMask &= lastNonEmptyMask;
953
- }
954
- let bx = baseIdx % nbx;
955
- const byBz = (baseIdx / nbx) | 0;
956
- let by = byBz % nby;
957
- let bz = (byBz / nby) | 0;
958
- let coordLane = 0;
959
- while (navMask) {
960
- const bp = 31 - Math.clz32(navMask & -navMask);
961
- const lane = bp >>> 1;
962
- bx += lane - coordLane;
963
- coordLane = lane;
964
- while (bx >= nbx) {
965
- bx -= nbx;
966
- by++;
967
- if (by >= nby) {
968
- by = 0;
969
- bz++;
970
- }
971
- }
972
- if (bx < minBx) {
973
- minBx = bx;
974
- }
975
- if (bx > maxBx) {
976
- maxBx = bx;
977
- }
978
- if (by < minBy) {
979
- minBy = by;
980
- }
981
- if (by > maxBy) {
982
- maxBy = by;
983
- }
984
- if (bz < minBz) {
985
- minBz = bz;
986
- }
987
- if (bz > maxBz) {
988
- maxBz = bz;
989
- }
990
- navMask &= navMask - 1;
991
- }
992
- }
993
- if (onProgress) {
994
- onProgress(this.types.length, this.types.length);
995
- }
996
- return maxBx >= 0 ? { minBx, minBy, minBz, maxBx, maxBy, maxBz } : null;
997
- }
998
- static findNearestFreeCell(blocked, seedIx, seedIy, seedIz, maxRadius) {
999
- const { nx, ny, nz } = blocked;
1000
- for (let r = 1; r <= maxRadius; r++) {
1001
- for (let dz = -r; dz <= r; dz++) {
1002
- for (let dy = -r; dy <= r; dy++) {
1003
- for (let dx = -r; dx <= r; dx++) {
1004
- if (Math.abs(dx) !== r && Math.abs(dy) !== r && Math.abs(dz) !== r) {
1005
- continue;
1006
- }
1007
- const ix = seedIx + dx;
1008
- const iy = seedIy + dy;
1009
- const iz = seedIz + dz;
1010
- if (ix < 0 || ix >= nx || iy < 0 || iy >= ny || iz < 0 || iz >= nz) {
1011
- continue;
1012
- }
1013
- if (!blocked.getVoxel(ix, iy, iz)) {
1014
- return { ix, iy, iz };
1015
- }
1016
- }
1017
- }
1018
- }
1019
- }
1020
- return null;
1021
- }
1022
- }
1023
- export const SOLID_LEAF_MARKER = 0xFF000000 >>> 0;
1024
- const MAX_24BIT_OFFSET = 0x00FFFFFF;
1025
- const DENSE_SOLID_STREAM_THRESHOLD = 8_000_000;
1026
- export const getChildOffset = (mask, octant) => {
1027
- const prefix = mask & ((1 << octant) - 1);
1028
- let n = prefix >>> 0;
1029
- n -= ((n >>> 1) & 0x55555555);
1030
- n = (n & 0x33333333) + ((n >>> 2) & 0x33333333);
1031
- return (((n + (n >>> 4)) & 0x0F0F0F0F) * 0x01010101) >>> 24;
1032
- };
1033
- const bitCount = (n) => {
1034
- let v = n >>> 0;
1035
- v -= ((v >>> 1) & 0x55555555);
1036
- v = (v & 0x33333333) + ((v >>> 2) & 0x33333333);
1037
- return (((v + (v >>> 4)) & 0x0F0F0F0F) * 0x01010101) >>> 24;
1038
- };
1039
- const sortMixedByMorton = (mortons, masks, n = mortons.length) => {
1040
- if (n <= 1) {
1041
- return;
1042
- }
1043
- const stackLo = [0];
1044
- const stackHi = [n - 1];
1045
- const swap = (a, b) => {
1046
- const km = mortons[a];
1047
- mortons[a] = mortons[b];
1048
- mortons[b] = km;
1049
- const alo = masks[a * 2];
1050
- const ahi = masks[a * 2 + 1];
1051
- masks[a * 2] = masks[b * 2];
1052
- masks[a * 2 + 1] = masks[b * 2 + 1];
1053
- masks[b * 2] = alo;
1054
- masks[b * 2 + 1] = ahi;
1055
- };
1056
- while (stackLo.length > 0) {
1057
- const lo = stackLo.pop();
1058
- const hi = stackHi.pop();
1059
- if (hi - lo < 16) {
1060
- for (let i = lo + 1; i <= hi; i++) {
1061
- const km = mortons[i];
1062
- const m0 = masks[i * 2];
1063
- const m1 = masks[i * 2 + 1];
1064
- let j = i - 1;
1065
- while (j >= lo && mortons[j] > km) {
1066
- mortons[j + 1] = mortons[j];
1067
- masks[(j + 1) * 2] = masks[j * 2];
1068
- masks[(j + 1) * 2 + 1] = masks[j * 2 + 1];
1069
- j--;
1070
- }
1071
- mortons[j + 1] = km;
1072
- masks[(j + 1) * 2] = m0;
1073
- masks[(j + 1) * 2 + 1] = m1;
1074
- }
1075
- continue;
1076
- }
1077
- const mid = (lo + hi) >>> 1;
1078
- if (mortons[mid] < mortons[lo]) {
1079
- swap(mid, lo);
1080
- }
1081
- if (mortons[hi] < mortons[lo]) {
1082
- swap(hi, lo);
1083
- }
1084
- if (mortons[hi] < mortons[mid]) {
1085
- swap(hi, mid);
1086
- }
1087
- const pivot = mortons[mid];
1088
- let i = lo;
1089
- let j = hi;
1090
- while (i <= j) {
1091
- while (mortons[i] < pivot) {
1092
- i++;
1093
- }
1094
- while (mortons[j] > pivot) {
1095
- j--;
1096
- }
1097
- if (i <= j) {
1098
- if (i !== j) {
1099
- swap(i, j);
1100
- }
1101
- i++;
1102
- j--;
1103
- }
1104
- }
1105
- if (j - lo > hi - i) {
1106
- if (lo < j) {
1107
- stackLo.push(lo);
1108
- stackHi.push(j);
1109
- }
1110
- if (i < hi) {
1111
- stackLo.push(i);
1112
- stackHi.push(hi);
1113
- }
1114
- }
1115
- else {
1116
- if (i < hi) {
1117
- stackLo.push(i);
1118
- stackHi.push(hi);
1119
- }
1120
- if (lo < j) {
1121
- stackLo.push(lo);
1122
- stackHi.push(j);
1123
- }
1124
- }
1125
- }
1126
- };
1127
- const createInteriorWave = (initialCapacity) => {
1128
- const cap = Math.max(16, initialCapacity);
1129
- return { pos: new Uint32Array(cap), li: new Uint32Array(cap), ii: new Uint32Array(cap), length: 0 };
1130
- };
1131
- const pushInteriorWave = (wave, pos, li, ii) => {
1132
- if (wave.length === wave.pos.length) {
1133
- const cap = wave.pos.length * 2;
1134
- const grownPos = new Uint32Array(cap);
1135
- const grownLi = new Uint32Array(cap);
1136
- const grownIi = new Uint32Array(cap);
1137
- grownPos.set(wave.pos);
1138
- grownLi.set(wave.li);
1139
- grownIi.set(wave.ii);
1140
- wave.pos = grownPos;
1141
- wave.li = grownLi;
1142
- wave.ii = grownIi;
1143
- }
1144
- const i = wave.length++;
1145
- wave.pos[i] = pos;
1146
- wave.li[i] = li;
1147
- wave.ii[i] = ii;
1148
- };
1149
- const shouldUseDenseMipBuild = (totalBlocks, nSolid, nMixed) => {
1150
- return nSolid >= DENSE_SOLID_STREAM_THRESHOLD &&
1151
- nSolid > nMixed * 4 &&
1152
- nSolid > totalBlocks * 0.25;
1153
- };
1154
- const buildDenseTypeLevels = (grid, maxDepth) => {
1155
- const levels = [{
1156
- types: grid.types,
1157
- nbx: grid.nbx,
1158
- nby: grid.nby,
1159
- nbz: grid.nbz,
1160
- nonEmptyCount: 0
1161
- }];
1162
- for (let li = 1; li <= maxDepth; li++) {
1163
- const prev = levels[li - 1];
1164
- const nbx = Math.max(1, Math.ceil(prev.nbx / 2));
1165
- const nby = Math.max(1, Math.ceil(prev.nby / 2));
1166
- const nbz = Math.max(1, Math.ceil(prev.nbz / 2));
1167
- const total = nbx * nby * nbz;
1168
- const types = new Uint32Array((total + BLOCKS_PER_WORD - 1) >>> 4);
1169
- const prevStride = prev.nbx * prev.nby;
1170
- const stride = nbx * nby;
1171
- let nonEmptyCount = 0;
1172
- for (let pz = 0; pz < nbz; pz++) {
1173
- const childZ0 = pz << 1;
1174
- for (let py = 0; py < nby; py++) {
1175
- const childY0 = py << 1;
1176
- for (let px = 0; px < nbx; px++) {
1177
- const childX0 = px << 1;
1178
- let childMask = 0;
1179
- let allSolid = true;
1180
- let childCount = 0;
1181
- for (let oct = 0; oct < 8; oct++) {
1182
- const cx = childX0 + (oct & 1);
1183
- const cy = childY0 + ((oct >> 1) & 1);
1184
- const cz = childZ0 + ((oct >> 2) & 1);
1185
- if (cx >= prev.nbx || cy >= prev.nby || cz >= prev.nbz) {
1186
- continue;
1187
- }
1188
- const childIdx = cx + cy * prev.nbx + cz * prevStride;
1189
- const bt = readBlockType(prev.types, childIdx);
1190
- if (bt === BLOCK_EMPTY) {
1191
- continue;
1192
- }
1193
- childMask |= 1 << oct;
1194
- childCount++;
1195
- if (bt !== BLOCK_SOLID) {
1196
- allSolid = false;
1197
- }
1198
- }
1199
- if (childMask !== 0) {
1200
- const parentIdx = px + py * nbx + pz * stride;
1201
- writeBlockType(types, parentIdx, allSolid && childCount === 8 ? BLOCK_SOLID : BLOCK_MIXED);
1202
- nonEmptyCount++;
1203
- }
1204
- }
1205
- }
1206
- }
1207
- levels.push({ types, nbx, nby, nbz, nonEmptyCount });
1208
- if (nonEmptyCount === 0) {
1209
- break;
1210
- }
1211
- if (nonEmptyCount === 1 && readBlockType(types, 0) !== BLOCK_EMPTY) {
1212
- break;
1213
- }
1214
- }
1215
- return levels;
1216
- };
1217
- const lowerBoundF64 = (arr, target, n) => {
1218
- let lo = 0;
1219
- let hi = n;
1220
- while (lo < hi) {
1221
- const mid = (lo + hi) >>> 1;
1222
- if (arr[mid] < target) {
1223
- lo = mid + 1;
1224
- }
1225
- else {
1226
- hi = mid;
1227
- }
1228
- }
1229
- return lo;
1230
- };
1231
- const flattenTreeFromLevels = (interiorLevels, solidStream, mixedStream, mixedMasks, nSolid, nMixed, gridBounds, sceneBounds, voxelResolution, treeDepth) => {
1232
- if (interiorLevels.length === 0) {
1233
- return {
1234
- gridBounds,
1235
- sceneBounds,
1236
- voxelResolution,
1237
- leafSize: LEAF_SIZE,
1238
- treeDepth,
1239
- numInteriorNodes: 0,
1240
- numMixedLeaves: 0,
1241
- nodes: new Uint32Array(0),
1242
- leafData: new Uint32Array(0)
1243
- };
1244
- }
1245
- const rootLevel = interiorLevels[interiorLevels.length - 1];
1246
- let maxNodes = nSolid + nMixed;
1247
- for (let l = 0; l < interiorLevels.length; l++) {
1248
- maxNodes += interiorLevels[l].mortons.length;
1249
- }
1250
- const nodes = new Uint32Array(maxNodes);
1251
- const leafData = new Uint32Array(nMixed * 2);
1252
- let leafDataLen = 0;
1253
- let numInteriorNodes = 0;
1254
- let numMixedLeaves = 0;
1255
- let emitPos = 0;
1256
- let waveLi = [];
1257
- let waveIi = [];
1258
- const rootLi = interiorLevels.length - 1;
1259
- for (let i = 0; i < rootLevel.mortons.length; i++) {
1260
- waveLi.push(rootLi);
1261
- waveIi.push(i);
1262
- }
1263
- const intPos = [];
1264
- const intLi = [];
1265
- const intIi = [];
1266
- const intMask = [];
1267
- while (waveLi.length > 0) {
1268
- intPos.length = 0;
1269
- intLi.length = 0;
1270
- intIi.length = 0;
1271
- intMask.length = 0;
1272
- for (let w = 0; w < waveLi.length; w++) {
1273
- const li = waveLi[w];
1274
- const ii = waveIi[w];
1275
- if (li === -1) {
1276
- if (ii < nMixed) {
1277
- const leafDataIndex = leafDataLen >> 1;
1278
- if (leafDataIndex > MAX_24BIT_OFFSET) {
1279
- throw new Error(`Sparse octree mixed-leaf count (${leafDataIndex + 1}) exceeds the Laine-Karras 24-bit baseOffset limit (${MAX_24BIT_OFFSET + 1}). Reduce the grid size or split the scene.`);
1280
- }
1281
- leafData[leafDataLen++] = mixedMasks[ii * 2];
1282
- leafData[leafDataLen++] = mixedMasks[ii * 2 + 1];
1283
- nodes[emitPos] = leafDataIndex;
1284
- numMixedLeaves++;
1285
- }
1286
- else {
1287
- nodes[emitPos] = SOLID_LEAF_MARKER;
1288
- }
1289
- emitPos++;
1290
- continue;
1291
- }
1292
- const level = interiorLevels[li];
1293
- const type = level.types[ii];
1294
- if (type === 1 /* OctreeNodeType.Solid */) {
1295
- nodes[emitPos] = SOLID_LEAF_MARKER;
1296
- }
1297
- else {
1298
- intPos.push(emitPos);
1299
- intLi.push(li);
1300
- intIi.push(ii);
1301
- intMask.push(level.childMasks[ii]);
1302
- numInteriorNodes++;
1303
- nodes[emitPos] = 0;
1304
- }
1305
- emitPos++;
1306
- }
1307
- const nextWaveLi = [];
1308
- const nextWaveIi = [];
1309
- let nextChildStart = emitPos;
1310
- for (let j = 0; j < intPos.length; j++) {
1311
- const childMask = intMask[j];
1312
- const childCount = bitCount(childMask);
1313
- if (nextChildStart > MAX_24BIT_OFFSET) {
1314
- throw new Error(`Sparse octree node count (${nextChildStart + 1}) exceeds the Laine-Karras 24-bit baseOffset limit (${MAX_24BIT_OFFSET + 1}). Reduce the grid size or split the scene.`);
1315
- }
1316
- nodes[intPos[j]] = ((childMask & 0xFF) << 24) | nextChildStart;
1317
- const myLi = intLi[j];
1318
- const myMorton = interiorLevels[myLi].mortons[intIi[j]];
1319
- const childMortonBase = myMorton * 8;
1320
- const childMortonEnd = childMortonBase + 8;
1321
- if (myLi === 0) {
1322
- let sIdx = lowerBoundF64(solidStream, childMortonBase, nSolid);
1323
- let mIdx = lowerBoundF64(mixedStream, childMortonBase, nMixed);
1324
- while (true) {
1325
- const sM = sIdx < nSolid && solidStream[sIdx] < childMortonEnd ? solidStream[sIdx] : Number.POSITIVE_INFINITY;
1326
- const mM = mIdx < nMixed && mixedStream[mIdx] < childMortonEnd ? mixedStream[mIdx] : Number.POSITIVE_INFINITY;
1327
- if (!isFinite(sM) && !isFinite(mM)) {
1328
- break;
1329
- }
1330
- if (sM < mM) {
1331
- nextWaveLi.push(-1);
1332
- nextWaveIi.push(nMixed + sIdx);
1333
- sIdx++;
1334
- }
1335
- else {
1336
- nextWaveLi.push(-1);
1337
- nextWaveIi.push(mIdx);
1338
- mIdx++;
1339
- }
1340
- }
1341
- }
1342
- else {
1343
- const childLi = myLi - 1;
1344
- const childLevel = interiorLevels[childLi];
1345
- const childMortons = childLevel.mortons;
1346
- let lo = 0;
1347
- let hi = childMortons.length;
1348
- while (lo < hi) {
1349
- const mid = (lo + hi) >> 1;
1350
- if (childMortons[mid] < childMortonBase) {
1351
- lo = mid + 1;
1352
- }
1353
- else {
1354
- hi = mid;
1355
- }
1356
- }
1357
- while (lo < childMortons.length && childMortons[lo] < childMortonEnd) {
1358
- nextWaveLi.push(childLi);
1359
- nextWaveIi.push(lo);
1360
- lo++;
1361
- }
1362
- }
1363
- nextChildStart += childCount;
1364
- }
1365
- waveLi = nextWaveLi;
1366
- waveIi = nextWaveIi;
1367
- }
1368
- return {
1369
- gridBounds,
1370
- sceneBounds,
1371
- voxelResolution,
1372
- leafSize: LEAF_SIZE,
1373
- treeDepth,
1374
- numInteriorNodes,
1375
- numMixedLeaves,
1376
- nodes: emitPos === maxNodes ? nodes : nodes.slice(0, emitPos),
1377
- leafData: leafDataLen === leafData.length ? leafData : leafData.slice(0, leafDataLen)
1378
- };
1379
- };
1380
- const flattenDenseLevels = (levels, grid, gridBounds, sceneBounds, voxelResolution) => {
1381
- const treeDepth = Math.max(1, levels.length - 1);
1382
- const rootLi = levels.length - 1;
1383
- const rootLevel = levels[rootLi];
1384
- const rootType = readBlockType(rootLevel.types, 0);
1385
- if (rootType === BLOCK_EMPTY) {
1386
- return {
1387
- gridBounds, sceneBounds, voxelResolution, leafSize: LEAF_SIZE, treeDepth,
1388
- numInteriorNodes: 0, numMixedLeaves: 0, nodes: new Uint32Array(0), leafData: new Uint32Array(0)
1389
- };
1390
- }
1391
- let nodes = new Uint32Array(Math.max(1024, Math.min(MAX_24BIT_OFFSET + 1, grid.masks.size * 3)));
1392
- let nodeLen = 0;
1393
- let leafData = new Uint32Array(Math.max(1024, grid.masks.size * 2));
1394
- let leafDataLen = 0;
1395
- let numInteriorNodes = 0;
1396
- let numMixedLeaves = 0;
1397
- const appendNode = (value) => {
1398
- if (nodeLen === nodes.length) {
1399
- const grown = new Uint32Array(nodes.length * 2);
1400
- grown.set(nodes);
1401
- nodes = grown;
1402
- }
1403
- nodes[nodeLen] = value >>> 0;
1404
- return nodeLen++;
1405
- };
1406
- const appendMixedLeaf = (blockIdx) => {
1407
- const leafDataIndex = leafDataLen >> 1;
1408
- if (leafDataIndex > MAX_24BIT_OFFSET) {
1409
- throw new Error(`Sparse octree mixed-leaf count (${leafDataIndex + 1}) exceeds the Laine-Karras 24-bit baseOffset limit (${MAX_24BIT_OFFSET + 1}). Reduce the grid size or split the scene.`);
1410
- }
1411
- if (leafDataLen + 2 > leafData.length) {
1412
- const grown = new Uint32Array(leafData.length * 2);
1413
- grown.set(leafData);
1414
- leafData = grown;
1415
- }
1416
- const s = grid.masks.slot(blockIdx);
1417
- leafData[leafDataLen++] = grid.masks.lo[s];
1418
- leafData[leafDataLen++] = grid.masks.hi[s];
1419
- appendNode(leafDataIndex);
1420
- numMixedLeaves++;
1421
- };
1422
- let curWave = createInteriorWave(1);
1423
- let nextWave = createInteriorWave(1024);
1424
- const appendDenseNode = (li, idx, wave) => {
1425
- const level = levels[li];
1426
- const bt = readBlockType(level.types, idx);
1427
- if (bt === BLOCK_SOLID) {
1428
- appendNode(SOLID_LEAF_MARKER);
1429
- }
1430
- else if (bt === BLOCK_MIXED) {
1431
- const pos = appendNode(0);
1432
- pushInteriorWave(wave, pos, li, idx);
1433
- numInteriorNodes++;
1434
- }
1435
- };
1436
- appendDenseNode(rootLi, 0, curWave);
1437
- while (curWave.length > 0) {
1438
- nextWave.length = 0;
1439
- const currentLi = curWave.li[0];
1440
- for (let w = 0; w < curWave.length; w++) {
1441
- const li = curWave.li[w];
1442
- const parentLevel = levels[li];
1443
- const childLevel = levels[li - 1];
1444
- const parentIdx = curWave.ii[w];
1445
- const px = parentIdx % parentLevel.nbx;
1446
- const pyBz = (parentIdx / parentLevel.nbx) | 0;
1447
- const py = pyBz % parentLevel.nby;
1448
- const pz = (pyBz / parentLevel.nby) | 0;
1449
- const childX0 = px << 1;
1450
- const childY0 = py << 1;
1451
- const childZ0 = pz << 1;
1452
- const childStride = childLevel.nbx * childLevel.nby;
1453
- const childStart = nodeLen;
1454
- let childMask = 0;
1455
- if (childStart > MAX_24BIT_OFFSET) {
1456
- throw new Error(`Sparse octree node count (${childStart + 1}) exceeds the Laine-Karras 24-bit baseOffset limit (${MAX_24BIT_OFFSET + 1}). Reduce the grid size or split the scene.`);
1457
- }
1458
- for (let oct = 0; oct < 8; oct++) {
1459
- const cx = childX0 + (oct & 1);
1460
- const cy = childY0 + ((oct >> 1) & 1);
1461
- const cz = childZ0 + ((oct >> 2) & 1);
1462
- if (cx >= childLevel.nbx || cy >= childLevel.nby || cz >= childLevel.nbz) {
1463
- continue;
1464
- }
1465
- const childIdx = cx + cy * childLevel.nbx + cz * childStride;
1466
- const bt = readBlockType(childLevel.types, childIdx);
1467
- if (bt === BLOCK_EMPTY) {
1468
- continue;
1469
- }
1470
- childMask |= 1 << oct;
1471
- if (li === 1) {
1472
- if (bt === BLOCK_SOLID) {
1473
- appendNode(SOLID_LEAF_MARKER);
1474
- }
1475
- else {
1476
- appendMixedLeaf(childIdx);
1477
- }
1478
- }
1479
- else {
1480
- appendDenseNode(li - 1, childIdx, nextWave);
1481
- }
1482
- }
1483
- nodes[curWave.pos[w]] = ((childMask & 0xFF) << 24) | childStart;
1484
- }
1485
- levels[currentLi] = null;
1486
- const tmp = curWave;
1487
- curWave = nextWave;
1488
- nextWave = tmp;
1489
- }
1490
- return {
1491
- gridBounds,
1492
- sceneBounds,
1493
- voxelResolution,
1494
- leafSize: LEAF_SIZE,
1495
- treeDepth,
1496
- numInteriorNodes,
1497
- numMixedLeaves,
1498
- nodes: nodes.slice(0, nodeLen),
1499
- leafData: leafData.slice(0, leafDataLen)
1500
- };
1501
- };
1502
- const buildSparseOctreeDense = (grid, gridBounds, sceneBounds, voxelResolution, maxDepth, consumeGrid) => {
1503
- const levels = buildDenseTypeLevels(grid, maxDepth);
1504
- const result = flattenDenseLevels(levels, grid, gridBounds, sceneBounds, voxelResolution);
1505
- if (consumeGrid) {
1506
- grid.releaseStorage();
1507
- }
1508
- return result;
1509
- };
1510
- /**
1511
- * Build a sparse octree from block masks using:
1512
- * 1) mixed+solid SoA merge and Morton sort
1513
- * 2) bottom-up level construction by parent Morton grouping
1514
- * 3) BFS flatten to node/leafData arrays.
1515
- */
1516
- export const buildSparseOctree = (grid, gridBounds, sceneBounds, voxelResolution, options = {}) => {
1517
- const { nbx, nby, nbz, types: gridTypes, masks: gridMasks } = grid;
1518
- const totalBlocks = nbx * nby * nbz;
1519
- const blocksPerAxis = Math.max(nbx, nby, nbz);
1520
- const treeDepth = Math.max(1, Math.ceil(Math.log2(blocksPerAxis)));
1521
- const lastWordIdx = gridTypes.length - 1;
1522
- const lastLanes = totalBlocks - lastWordIdx * BLOCKS_PER_WORD;
1523
- const lastValidWordMask = lastLanes >= BLOCKS_PER_WORD ? 0xFFFFFFFF >>> 0 : ((1 << (lastLanes * 2)) - 1) >>> 0;
1524
- let nSolid = 0;
1525
- let nMixed = 0;
1526
- for (let w = 0; w < gridTypes.length; w++) {
1527
- let word = gridTypes[w];
1528
- if (w === lastWordIdx) {
1529
- word = (word & lastValidWordMask) >>> 0;
1530
- }
1531
- if (word === 0) {
1532
- continue;
1533
- }
1534
- const solidMask = (word & EVEN_BITS) & ~((word >>> 1) & EVEN_BITS);
1535
- const mixedMask = ((word >>> 1) & EVEN_BITS) & ~(word & EVEN_BITS);
1536
- nSolid += bitCount(solidMask >>> 0);
1537
- nMixed += bitCount(mixedMask >>> 0);
1538
- }
1539
- if (nSolid + nMixed === 0) {
1540
- return {
1541
- gridBounds,
1542
- sceneBounds,
1543
- voxelResolution,
1544
- leafSize: LEAF_SIZE,
1545
- treeDepth: 1,
1546
- numInteriorNodes: 0,
1547
- numMixedLeaves: 0,
1548
- nodes: new Uint32Array(0),
1549
- leafData: new Uint32Array(0)
1550
- };
1551
- }
1552
- if (options.dense || shouldUseDenseMipBuild(totalBlocks, nSolid, nMixed)) {
1553
- return buildSparseOctreeDense(grid, gridBounds, sceneBounds, voxelResolution, treeDepth, !!options.consumeGrid);
1554
- }
1555
- const solidStream = new Float64Array(nSolid);
1556
- const mixedStream = new Float64Array(nMixed);
1557
- const mixedMasks = new Uint32Array(nMixed * 2);
1558
- let solidWriteIdx = 0;
1559
- let mixedWriteIdx = 0;
1560
- for (let w = 0; w < gridTypes.length; w++) {
1561
- let word = gridTypes[w];
1562
- if (w === lastWordIdx) {
1563
- word = (word & lastValidWordMask) >>> 0;
1564
- }
1565
- if (word === 0) {
1566
- continue;
1567
- }
1568
- let nonEmpty = ((word & EVEN_BITS) | ((word >>> 1) & EVEN_BITS)) >>> 0;
1569
- const baseIdx = w * BLOCKS_PER_WORD;
1570
- while (nonEmpty) {
1571
- const bp = 31 - Math.clz32(nonEmpty & -nonEmpty);
1572
- const lane = bp >>> 1;
1573
- nonEmpty &= nonEmpty - 1;
1574
- const blockIdx = baseIdx + lane;
1575
- if (blockIdx >= totalBlocks) {
1576
- break;
1577
- }
1578
- const bx = blockIdx % nbx;
1579
- const byBz = (blockIdx / nbx) | 0;
1580
- const by = byBz % nby;
1581
- const bz = (byBz / nby) | 0;
1582
- const morton = encodeMorton3(bx, by, bz);
1583
- const bt = (word >>> (lane << 1)) & TYPE_MASK;
1584
- if (bt === 1 /* OctreeNodeType.Solid */) {
1585
- solidStream[solidWriteIdx++] = morton;
1586
- }
1587
- else if (bt === 2 /* OctreeNodeType.Mixed */) {
1588
- mixedStream[mixedWriteIdx] = morton;
1589
- const s = gridMasks.slot(blockIdx);
1590
- mixedMasks[mixedWriteIdx * 2] = gridMasks.lo[s];
1591
- mixedMasks[mixedWriteIdx * 2 + 1] = gridMasks.hi[s];
1592
- mixedWriteIdx++;
1593
- }
1594
- }
1595
- }
1596
- if (options.consumeGrid) {
1597
- grid.releaseStorage();
1598
- }
1599
- if (nSolid > 1) {
1600
- solidStream.sort();
1601
- }
1602
- if (nMixed > 1) {
1603
- sortMixedByMorton(mixedStream, mixedMasks, nMixed);
1604
- }
1605
- const interiorLevels = [];
1606
- let curMortons = [];
1607
- let curTypes = [];
1608
- let curChildMasks = [];
1609
- {
1610
- let sI = 0;
1611
- let mI = 0;
1612
- while (sI < nSolid || mI < nMixed) {
1613
- const sM0 = sI < nSolid ? solidStream[sI] : Number.POSITIVE_INFINITY;
1614
- const mM0 = mI < nMixed ? mixedStream[mI] : Number.POSITIVE_INFINITY;
1615
- const minMorton = sM0 < mM0 ? sM0 : mM0;
1616
- const parentMorton = Math.floor(minMorton / 8);
1617
- let childMask = 0;
1618
- let allSolid = true;
1619
- let childCount = 0;
1620
- while (true) {
1621
- const sM = sI < nSolid ? solidStream[sI] : Number.POSITIVE_INFINITY;
1622
- const mM = mI < nMixed ? mixedStream[mI] : Number.POSITIVE_INFINITY;
1623
- const cur = sM < mM ? sM : mM;
1624
- if (!isFinite(cur) || Math.floor(cur / 8) !== parentMorton) {
1625
- break;
1626
- }
1627
- childMask |= 1 << (cur % 8);
1628
- childCount++;
1629
- if (sM < mM) {
1630
- sI++;
1631
- }
1632
- else {
1633
- allSolid = false;
1634
- mI++;
1635
- }
1636
- }
1637
- curMortons.push(parentMorton);
1638
- if (allSolid && childCount === 8) {
1639
- curTypes.push(1 /* OctreeNodeType.Solid */);
1640
- curChildMasks.push(0);
1641
- }
1642
- else {
1643
- curTypes.push(2 /* OctreeNodeType.Mixed */);
1644
- curChildMasks.push(childMask);
1645
- }
1646
- }
1647
- }
1648
- let actualDepth = treeDepth;
1649
- if (curMortons.length === 0) {
1650
- actualDepth = 1;
1651
- }
1652
- else if (curMortons.length === 1 && curMortons[0] === 0) {
1653
- actualDepth = 1;
1654
- interiorLevels.push({ mortons: curMortons, types: curTypes, childMasks: curChildMasks });
1655
- }
1656
- else {
1657
- for (let level = 1; level < treeDepth; level++) {
1658
- interiorLevels.push({ mortons: curMortons, types: curTypes, childMasks: curChildMasks });
1659
- const n = curMortons.length;
1660
- const nextMortons = [];
1661
- const nextTypes = [];
1662
- const nextChildMasks = [];
1663
- let i = 0;
1664
- while (i < n) {
1665
- const parentMorton = Math.floor(curMortons[i] / 8);
1666
- let childMask = 0;
1667
- let allSolid = true;
1668
- let childCount = 0;
1669
- while (i < n && Math.floor(curMortons[i] / 8) === parentMorton) {
1670
- const octant = curMortons[i] % 8;
1671
- childMask |= (1 << octant);
1672
- if (curTypes[i] !== 1 /* OctreeNodeType.Solid */) {
1673
- allSolid = false;
1674
- }
1675
- childCount++;
1676
- i++;
1677
- }
1678
- nextMortons.push(parentMorton);
1679
- if (allSolid && childCount === 8) {
1680
- nextTypes.push(1 /* OctreeNodeType.Solid */);
1681
- nextChildMasks.push(0);
1682
- }
1683
- else {
1684
- nextTypes.push(2 /* OctreeNodeType.Mixed */);
1685
- nextChildMasks.push(childMask);
1686
- }
1687
- }
1688
- curMortons = nextMortons;
1689
- curTypes = nextTypes;
1690
- curChildMasks = nextChildMasks;
1691
- if (curMortons.length === 1 && curMortons[0] === 0) {
1692
- actualDepth = level + 1;
1693
- break;
1694
- }
1695
- }
1696
- interiorLevels.push({ mortons: curMortons, types: curTypes, childMasks: curChildMasks });
1697
- }
1698
- return flattenTreeFromLevels(interiorLevels, solidStream, mixedStream, mixedMasks, nSolid, nMixed, gridBounds, sceneBounds, voxelResolution, actualDepth);
1699
- };
1700
- export { BLOCK_EMPTY, BLOCK_SOLID, BLOCK_MIXED, BLOCKS_PER_WORD, TYPE_MASK, EVEN_BITS, readBlockType, writeBlockType, SOLID_LO, SOLID_HI, SparseVoxelGrid };
1
+ /** 3D Morton (Z-order) for integer block coordinates. */
2
+ export function encodeMorton3(x, y, z) {
3
+ let result = 0;
4
+ let shift = 1;
5
+ for (let i = 0; i < 17; i++) {
6
+ if (x & 1) {
7
+ result += shift;
8
+ }
9
+ if (y & 1) {
10
+ result += shift * 2;
11
+ }
12
+ if (z & 1) {
13
+ result += shift * 4;
14
+ }
15
+ x >>>= 1;
16
+ y >>>= 1;
17
+ z >>>= 1;
18
+ shift *= 8;
19
+ }
20
+ return result;
21
+ }
22
+ export function decodeMorton3(m) {
23
+ let x = 0, y = 0, z = 0;
24
+ let bit = 1;
25
+ while (m > 0) {
26
+ const triplet = m % 8;
27
+ if (triplet & 1) {
28
+ x |= bit;
29
+ }
30
+ if (triplet & 2) {
31
+ y |= bit;
32
+ }
33
+ if (triplet & 4) {
34
+ z |= bit;
35
+ }
36
+ bit <<= 1;
37
+ m = Math.trunc(m / 8);
38
+ }
39
+ return [x, y, z];
40
+ }
41
+ /** Voxel leaf edge length in voxels (4³ block). */
42
+ export const LEAF_SIZE = 4;
43
+ export const ALPHA_THRESHOLD = 1 / 255;
44
+ export function alignGridBounds(bounds, voxelResolution) {
45
+ const blockSize = LEAF_SIZE * voxelResolution;
46
+ return {
47
+ min: {
48
+ x: Math.floor(bounds.min.x / blockSize) * blockSize,
49
+ y: Math.floor(bounds.min.y / blockSize) * blockSize,
50
+ z: Math.floor(bounds.min.z / blockSize) * blockSize,
51
+ },
52
+ max: {
53
+ x: Math.ceil(bounds.max.x / blockSize) * blockSize,
54
+ y: Math.ceil(bounds.max.y / blockSize) * blockSize,
55
+ z: Math.ceil(bounds.max.z / blockSize) * blockSize,
56
+ },
57
+ };
58
+ }
59
+ /**
60
+ * Max linear block index (signed 32-bit): BlockMaskMap keys, worker `blockIdx >>> 0`,
61
+ * and types bitmap indexing. Implies types bitmap <= ceil(MAX / 16) * 4 ~= 512 MB.
62
+ */
63
+ export const MAX_VOXEL_BLOCK_COUNT_INT32 = 0x7fff_ffff;
64
+ /** Preflight hard grid limits before voxelization allocates the types bitmap. */
65
+ export function checkVoxelGridCapacity(gridBounds, voxelResolution) {
66
+ const blockSize = LEAF_SIZE * voxelResolution;
67
+ const nbx = Math.round((gridBounds.max.x - gridBounds.min.x) / blockSize);
68
+ const nby = Math.round((gridBounds.max.y - gridBounds.min.y) / blockSize);
69
+ const nbz = Math.round((gridBounds.max.z - gridBounds.min.z) / blockSize);
70
+ const totalBlocks = nbx * nby * nbz;
71
+ if (totalBlocks > MAX_VOXEL_BLOCK_COUNT_INT32) {
72
+ throw new Error(`voxel grid block count ${totalBlocks} exceeds limit ${MAX_VOXEL_BLOCK_COUNT_INT32} ` +
73
+ `(linear block index must fit in 31 bits). ` +
74
+ `Tighten the box parameter or increase voxelResolution. grid blocks=${nbx}x${nby}x${nbz}`);
75
+ }
76
+ }
77
+ /** Opacity-aware AABB half-extents from scale + unit quaternion. */
78
+ export function extentsFromQuatScale(sx, sy, sz, qx, qy, qz, qw, opacity, opacityThreshold = ALPHA_THRESHOLD) {
79
+ let extend = 3;
80
+ if (opacity !== undefined && opacity > opacityThreshold) {
81
+ // Tight bound from opacity threshold, clamped by default 3-sigma bound.
82
+ const opacityAware = Math.sqrt(2 * Math.log(opacity / opacityThreshold));
83
+ if (Number.isFinite(opacityAware)) {
84
+ extend = Math.min(extend, opacityAware);
85
+ }
86
+ }
87
+ else if (opacity !== undefined && opacity <= opacityThreshold) {
88
+ return { ex: 0, ey: 0, ez: 0 };
89
+ }
90
+ const sX = extend * sx;
91
+ const sY = extend * sy;
92
+ const sZ = extend * sz;
93
+ const xx = qx * qx;
94
+ const yy = qy * qy;
95
+ const zz = qz * qz;
96
+ const xy = qx * qy;
97
+ const xz = qx * qz;
98
+ const yz = qy * qz;
99
+ const wx = qw * qx;
100
+ const wy = qw * qy;
101
+ const wz = qw * qz;
102
+ const m00 = 1 - 2 * (yy + zz);
103
+ const m01 = 2 * (xy - wz);
104
+ const m02 = 2 * (xz + wy);
105
+ const m10 = 2 * (xy + wz);
106
+ const m11 = 1 - 2 * (xx + zz);
107
+ const m12 = 2 * (yz - wx);
108
+ const m20 = 2 * (xz - wy);
109
+ const m21 = 2 * (yz + wx);
110
+ const m22 = 1 - 2 * (xx + yy);
111
+ const abs00 = Math.abs(m00);
112
+ const abs01 = Math.abs(m01);
113
+ const abs02 = Math.abs(m02);
114
+ const abs10 = Math.abs(m10);
115
+ const abs11 = Math.abs(m11);
116
+ const abs12 = Math.abs(m12);
117
+ const abs20 = Math.abs(m20);
118
+ const abs21 = Math.abs(m21);
119
+ const abs22 = Math.abs(m22);
120
+ const ex = abs00 * sX + abs01 * sY + abs02 * sZ;
121
+ const ey = abs10 * sX + abs11 * sY + abs12 * sZ;
122
+ const ez = abs20 * sX + abs21 * sY + abs22 * sZ;
123
+ return { ex, ey, ez };
124
+ }
125
+ function boundsOverlap(a, bMinX, bMinY, bMinZ, bMaxX, bMaxY, bMaxZ) {
126
+ return !(a.maxX < bMinX || a.minX > bMaxX || a.maxY < bMinY || a.minY > bMaxY || a.maxZ < bMinZ || a.minZ > bMaxZ);
127
+ }
128
+ function quickselect(axisData, idx, k) {
129
+ function valAt(p) {
130
+ return axisData[idx[p]];
131
+ }
132
+ function swap(i, j) {
133
+ const t = idx[i];
134
+ idx[i] = idx[j];
135
+ idx[j] = t;
136
+ }
137
+ const n = idx.length;
138
+ let l = 0;
139
+ let r = n - 1;
140
+ while (true) {
141
+ if (r <= l + 1) {
142
+ if (r === l + 1 && valAt(r) < valAt(l)) {
143
+ swap(l, r);
144
+ }
145
+ return idx[k];
146
+ }
147
+ const mid = (l + r) >>> 1;
148
+ swap(mid, l + 1);
149
+ if (valAt(l) > valAt(r)) {
150
+ swap(l, r);
151
+ }
152
+ if (valAt(l + 1) > valAt(r)) {
153
+ swap(l + 1, r);
154
+ }
155
+ if (valAt(l) > valAt(l + 1)) {
156
+ swap(l, l + 1);
157
+ }
158
+ let i = l + 1;
159
+ let j = r;
160
+ const pivotIdxVal = valAt(l + 1);
161
+ const pivotIdx = idx[l + 1];
162
+ while (true) {
163
+ do {
164
+ i++;
165
+ } while (i <= r && valAt(i) < pivotIdxVal);
166
+ do {
167
+ j--;
168
+ } while (j >= l && valAt(j) > pivotIdxVal);
169
+ if (j < i) {
170
+ break;
171
+ }
172
+ swap(i, j);
173
+ }
174
+ idx[l + 1] = idx[j];
175
+ idx[j] = pivotIdx;
176
+ if (j >= k) {
177
+ r = j - 1;
178
+ }
179
+ if (j <= k) {
180
+ l = i;
181
+ }
182
+ }
183
+ }
184
+ export class GaussianBVH {
185
+ static { this.MAX_LEAF_SIZE = 64; }
186
+ constructor(x, y, z, extents) {
187
+ this.x = x;
188
+ this.y = y;
189
+ this.z = z;
190
+ this.extents = extents;
191
+ const indices = new Uint32Array(x.length);
192
+ for (let i = 0; i < x.length; i++) {
193
+ indices[i] = i;
194
+ }
195
+ this.root = this.buildNode(indices);
196
+ }
197
+ queryOverlappingRaw(minX, minY, minZ, maxX, maxY, maxZ) {
198
+ const result = [];
199
+ this.queryNode(this.root, minX, minY, minZ, maxX, maxY, maxZ, result);
200
+ return result;
201
+ }
202
+ queryOverlappingRawInto(minX, minY, minZ, maxX, maxY, maxZ, output, offset = 0) {
203
+ return this.queryNodeInto(this.root, minX, minY, minZ, maxX, maxY, maxZ, output, offset, 0);
204
+ }
205
+ computeBounds(indices) {
206
+ let minX = Infinity, minY = Infinity, minZ = Infinity;
207
+ let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
208
+ for (let i = 0; i < indices.length; i++) {
209
+ const idx = indices[i];
210
+ const ex = this.extents[idx * 3], ey = this.extents[idx * 3 + 1], ez = this.extents[idx * 3 + 2];
211
+ const gx = this.x[idx], gy = this.y[idx], gz = this.z[idx];
212
+ if (gx - ex < minX) {
213
+ minX = gx - ex;
214
+ }
215
+ if (gy - ey < minY) {
216
+ minY = gy - ey;
217
+ }
218
+ if (gz - ez < minZ) {
219
+ minZ = gz - ez;
220
+ }
221
+ if (gx + ex > maxX) {
222
+ maxX = gx + ex;
223
+ }
224
+ if (gy + ey > maxY) {
225
+ maxY = gy + ey;
226
+ }
227
+ if (gz + ez > maxZ) {
228
+ maxZ = gz + ez;
229
+ }
230
+ }
231
+ return { minX, minY, minZ, maxX, maxY, maxZ };
232
+ }
233
+ buildNode(indices) {
234
+ const bounds = this.computeBounds(indices);
235
+ if (indices.length <= GaussianBVH.MAX_LEAF_SIZE) {
236
+ return { bounds, indices };
237
+ }
238
+ let minCx = Infinity, minCy = Infinity, minCz = Infinity;
239
+ let maxCx = -Infinity, maxCy = -Infinity, maxCz = -Infinity;
240
+ for (let i = 0; i < indices.length; i++) {
241
+ const idx = indices[i];
242
+ const cx = this.x[idx], cy = this.y[idx], cz = this.z[idx];
243
+ if (cx < minCx) {
244
+ minCx = cx;
245
+ }
246
+ if (cy < minCy) {
247
+ minCy = cy;
248
+ }
249
+ if (cz < minCz) {
250
+ minCz = cz;
251
+ }
252
+ if (cx > maxCx) {
253
+ maxCx = cx;
254
+ }
255
+ if (cy > maxCy) {
256
+ maxCy = cy;
257
+ }
258
+ if (cz > maxCz) {
259
+ maxCz = cz;
260
+ }
261
+ }
262
+ const ex = maxCx - minCx, ey = maxCy - minCy, ez = maxCz - minCz;
263
+ const axis = ex >= ey && ex >= ez ? this.x : ey >= ez ? this.y : this.z;
264
+ const mid = indices.length >>> 1;
265
+ quickselect(axis, indices, mid);
266
+ return { bounds, left: this.buildNode(indices.subarray(0, mid)), right: this.buildNode(indices.subarray(mid)) };
267
+ }
268
+ queryNode(node, minX, minY, minZ, maxX, maxY, maxZ, result) {
269
+ if (!boundsOverlap(node.bounds, minX, minY, minZ, maxX, maxY, maxZ)) {
270
+ return;
271
+ }
272
+ if (node.indices) {
273
+ for (let i = 0; i < node.indices.length; i++) {
274
+ const idx = node.indices[i];
275
+ const ex = this.extents[idx * 3], ey = this.extents[idx * 3 + 1], ez = this.extents[idx * 3 + 2];
276
+ const gx = this.x[idx], gy = this.y[idx], gz = this.z[idx];
277
+ if (!(gx + ex < minX ||
278
+ gx - ex > maxX ||
279
+ gy + ey < minY ||
280
+ gy - ey > maxY ||
281
+ gz + ez < minZ ||
282
+ gz - ez > maxZ)) {
283
+ result.push(idx);
284
+ }
285
+ }
286
+ return;
287
+ }
288
+ if (node.left) {
289
+ this.queryNode(node.left, minX, minY, minZ, maxX, maxY, maxZ, result);
290
+ }
291
+ if (node.right) {
292
+ this.queryNode(node.right, minX, minY, minZ, maxX, maxY, maxZ, result);
293
+ }
294
+ }
295
+ queryNodeInto(node, minX, minY, minZ, maxX, maxY, maxZ, output, offset, count) {
296
+ if (!boundsOverlap(node.bounds, minX, minY, minZ, maxX, maxY, maxZ)) {
297
+ return count;
298
+ }
299
+ if (node.indices) {
300
+ for (let i = 0; i < node.indices.length; i++) {
301
+ const idx = node.indices[i];
302
+ const ex = this.extents[idx * 3], ey = this.extents[idx * 3 + 1], ez = this.extents[idx * 3 + 2];
303
+ const gx = this.x[idx], gy = this.y[idx], gz = this.z[idx];
304
+ if (!(gx + ex < minX ||
305
+ gx - ex > maxX ||
306
+ gy + ey < minY ||
307
+ gy - ey > maxY ||
308
+ gz + ez < minZ ||
309
+ gz - ez > maxZ)) {
310
+ if (offset + count < output.length) {
311
+ output[offset + count] = idx;
312
+ }
313
+ count++;
314
+ }
315
+ }
316
+ return count;
317
+ }
318
+ if (node.left) {
319
+ count = this.queryNodeInto(node.left, minX, minY, minZ, maxX, maxY, maxZ, output, offset, count);
320
+ }
321
+ if (node.right) {
322
+ count = this.queryNodeInto(node.right, minX, minY, minZ, maxX, maxY, maxZ, output, offset, count);
323
+ }
324
+ return count;
325
+ }
326
+ }
327
+ const SOLID_LO = 0xffffffff >>> 0;
328
+ const SOLID_HI = 0xffffffff >>> 0;
329
+ const SOLID_MASK = 0xffffffff >>> 0;
330
+ const INITIAL_BLOCK_BUFFER_CAPACITY = 1024;
331
+ function growFloat64(src, newCap) {
332
+ const grown = new Float64Array(newCap);
333
+ grown.set(src);
334
+ return grown;
335
+ }
336
+ function growUint32(src, newCap) {
337
+ const grown = new Uint32Array(newCap);
338
+ grown.set(src);
339
+ return grown;
340
+ }
341
+ /**
342
+ * Append-only buffer for streaming voxelization results.
343
+ * Stores (linear blockIdx, voxel mask) pairs for non-empty 4x4x4 blocks.
344
+ *
345
+ * Block keys are linear block indices `bx + by*nbx + bz*nbx*nby` in the
346
+ * producer's grid coordinate system. Producers and consumers must agree on
347
+ * the grid dimensions; the buffer itself is dimension-agnostic.
348
+ */
349
+ export class BlockMaskBuffer {
350
+ constructor() {
351
+ this.solidIdx = new Float64Array(0);
352
+ this.solidCountValue = 0;
353
+ this.solidCap = 0;
354
+ this.mixedIdx = new Float64Array(0);
355
+ this.mixedCountValue = 0;
356
+ this.mixedCap = 0;
357
+ this.mixedMasks = new Uint32Array(0);
358
+ }
359
+ addBlock(blockIdx, lo, hi) {
360
+ if ((lo | hi) === 0) {
361
+ return;
362
+ }
363
+ if (lo >>> 0 === SOLID_MASK && hi >>> 0 === SOLID_MASK) {
364
+ if (this.solidCountValue === this.solidCap) {
365
+ this.solidCap = this.solidCap === 0 ? INITIAL_BLOCK_BUFFER_CAPACITY : this.solidCap * 2;
366
+ this.solidIdx = growFloat64(this.solidIdx, this.solidCap);
367
+ }
368
+ this.solidIdx[this.solidCountValue++] = blockIdx;
369
+ return;
370
+ }
371
+ if (this.mixedCountValue === this.mixedCap) {
372
+ this.mixedCap = this.mixedCap === 0 ? INITIAL_BLOCK_BUFFER_CAPACITY : this.mixedCap * 2;
373
+ this.mixedIdx = growFloat64(this.mixedIdx, this.mixedCap);
374
+ this.mixedMasks = growUint32(this.mixedMasks, this.mixedCap * 2);
375
+ }
376
+ this.mixedIdx[this.mixedCountValue] = blockIdx;
377
+ this.mixedMasks[this.mixedCountValue * 2] = lo >>> 0;
378
+ this.mixedMasks[this.mixedCountValue * 2 + 1] = hi >>> 0;
379
+ this.mixedCountValue++;
380
+ }
381
+ getMixedBlocks() {
382
+ return {
383
+ blockIdx: this.mixedIdx.subarray(0, this.mixedCountValue),
384
+ masks: this.mixedMasks.subarray(0, this.mixedCountValue * 2),
385
+ };
386
+ }
387
+ getSolidBlocks() {
388
+ return this.solidIdx.subarray(0, this.solidCountValue);
389
+ }
390
+ get count() {
391
+ return this.mixedCountValue + this.solidCountValue;
392
+ }
393
+ get mixedCount() {
394
+ return this.mixedCountValue;
395
+ }
396
+ get solidCount() {
397
+ return this.solidCountValue;
398
+ }
399
+ clear() {
400
+ this.solidIdx = new Float64Array(0);
401
+ this.solidCountValue = 0;
402
+ this.solidCap = 0;
403
+ this.mixedIdx = new Float64Array(0);
404
+ this.mixedMasks = new Uint32Array(0);
405
+ this.mixedCountValue = 0;
406
+ this.mixedCap = 0;
407
+ }
408
+ }
409
+ const BLOCK_EMPTY = 0;
410
+ const BLOCK_SOLID = 1;
411
+ const BLOCK_MIXED = 2;
412
+ const TYPE_MASK = 0x3;
413
+ const BLOCKS_PER_WORD = 16;
414
+ const EVEN_BITS = 0x55555555 >>> 0;
415
+ function getBlockTypeWordIndex(blockIdx) {
416
+ return Math.floor(blockIdx / BLOCKS_PER_WORD);
417
+ }
418
+ function getBlockTypeBitShift(blockIdx) {
419
+ return (blockIdx % BLOCKS_PER_WORD) << 1;
420
+ }
421
+ function getBlockTypeWordCount(totalBlocks) {
422
+ return Math.ceil(totalBlocks / BLOCKS_PER_WORD);
423
+ }
424
+ function readBlockType(types, blockIdx) {
425
+ const word = getBlockTypeWordIndex(blockIdx);
426
+ const shift = getBlockTypeBitShift(blockIdx);
427
+ return (types[word] >>> shift) & TYPE_MASK;
428
+ }
429
+ function writeBlockType(types, blockIdx, blockType) {
430
+ const word = getBlockTypeWordIndex(blockIdx);
431
+ const shift = getBlockTypeBitShift(blockIdx);
432
+ const mask = TYPE_MASK << shift;
433
+ types[word] = (types[word] & ~mask) | ((blockType & TYPE_MASK) << shift);
434
+ }
435
+ const EMPTY = -1;
436
+ class BlockMaskMap {
437
+ constructor(initialCapacity = 4096) {
438
+ const cap = 1 << (32 - Math.clz32(Math.max(15, initialCapacity - 1)));
439
+ this._capacity = cap;
440
+ this._mask = cap - 1;
441
+ this._size = 0;
442
+ this.keys = new Int32Array(cap).fill(EMPTY);
443
+ this.lo = new Uint32Array(cap);
444
+ this.hi = new Uint32Array(cap);
445
+ }
446
+ slot(key) {
447
+ const mask = this._mask;
448
+ let i = (Math.imul(key, 0x9e3779b9) >>> 0) & mask;
449
+ while (true) {
450
+ const k = this.keys[i];
451
+ if (k === key || k === EMPTY) {
452
+ return i;
453
+ }
454
+ i = (i + 1) & mask;
455
+ }
456
+ }
457
+ set(key, loVal, hiVal) {
458
+ let s = this.slot(key);
459
+ if (this.keys[s] === EMPTY) {
460
+ this.keys[s] = key;
461
+ this._size++;
462
+ if (this._size > ((this._capacity * 0.7) | 0)) {
463
+ this._grow();
464
+ s = this.slot(key);
465
+ }
466
+ }
467
+ this.lo[s] = loVal;
468
+ this.hi[s] = hiVal;
469
+ }
470
+ removeAt(slot) {
471
+ this._size--;
472
+ const mask = this._mask;
473
+ let i = slot;
474
+ let j = slot;
475
+ while (true) {
476
+ j = (j + 1) & mask;
477
+ if (this.keys[j] === EMPTY) {
478
+ break;
479
+ }
480
+ const k = (Math.imul(this.keys[j], 0x9e3779b9) >>> 0) & mask;
481
+ if (i < j ? k <= i || k > j : k <= i && k > j) {
482
+ this.keys[i] = this.keys[j];
483
+ this.lo[i] = this.lo[j];
484
+ this.hi[i] = this.hi[j];
485
+ i = j;
486
+ }
487
+ }
488
+ this.keys[i] = EMPTY;
489
+ }
490
+ clear() {
491
+ this.keys.fill(EMPTY);
492
+ this._size = 0;
493
+ }
494
+ get size() {
495
+ return this._size;
496
+ }
497
+ releaseStorage() {
498
+ this.keys = new Int32Array(0);
499
+ this.lo = new Uint32Array(0);
500
+ this.hi = new Uint32Array(0);
501
+ this._size = 0;
502
+ this._capacity = 0;
503
+ this._mask = 0;
504
+ }
505
+ clone() {
506
+ const c = new BlockMaskMap(this._capacity);
507
+ c.keys.set(this.keys);
508
+ c.lo.set(this.lo);
509
+ c.hi.set(this.hi);
510
+ c._size = this._size;
511
+ return c;
512
+ }
513
+ _grow() {
514
+ const oldKeys = this.keys;
515
+ const oldLo = this.lo;
516
+ const oldHi = this.hi;
517
+ const oldCap = this._capacity;
518
+ this._capacity *= 2;
519
+ this._mask = this._capacity - 1;
520
+ this.keys = new Int32Array(this._capacity).fill(EMPTY);
521
+ this.lo = new Uint32Array(this._capacity);
522
+ this.hi = new Uint32Array(this._capacity);
523
+ this._size = 0;
524
+ for (let i = 0; i < oldCap; i++) {
525
+ if (oldKeys[i] !== EMPTY) {
526
+ const s = this.slot(oldKeys[i]);
527
+ this.keys[s] = oldKeys[i];
528
+ this.lo[s] = oldLo[i];
529
+ this.hi[s] = oldHi[i];
530
+ this._size++;
531
+ }
532
+ }
533
+ }
534
+ }
535
+ class SparseVoxelGrid {
536
+ constructor(nx, ny, nz) {
537
+ this.nx = nx;
538
+ this.ny = ny;
539
+ this.nz = nz;
540
+ this.nbx = nx >> 2;
541
+ this.nby = ny >> 2;
542
+ this.nbz = nz >> 2;
543
+ this.bStride = this.nbx * this.nby;
544
+ const totalBlocks = this.nbx * this.nby * this.nbz;
545
+ this.types = new Uint32Array(getBlockTypeWordCount(totalBlocks));
546
+ this.masks = new BlockMaskMap();
547
+ }
548
+ getVoxel(ix, iy, iz) {
549
+ const blockIdx = (ix >> 2) + (iy >> 2) * this.nbx + (iz >> 2) * this.bStride;
550
+ const bt = readBlockType(this.types, blockIdx);
551
+ if (bt === BLOCK_EMPTY) {
552
+ return 0;
553
+ }
554
+ if (bt === BLOCK_SOLID) {
555
+ return 1;
556
+ }
557
+ const s = this.masks.slot(blockIdx);
558
+ const bitIdx = (ix & 3) + ((iy & 3) << 2) + ((iz & 3) << 4);
559
+ return bitIdx < 32 ? (this.masks.lo[s] >>> bitIdx) & 1 : (this.masks.hi[s] >>> (bitIdx - 32)) & 1;
560
+ }
561
+ setVoxel(ix, iy, iz) {
562
+ const blockIdx = (ix >> 2) + (iy >> 2) * this.nbx + (iz >> 2) * this.bStride;
563
+ const bt = readBlockType(this.types, blockIdx);
564
+ if (bt === BLOCK_SOLID) {
565
+ return;
566
+ }
567
+ const bitIdx = (ix & 3) + ((iy & 3) << 2) + ((iz & 3) << 4);
568
+ if (bt === BLOCK_MIXED) {
569
+ const s = this.masks.slot(blockIdx);
570
+ if (bitIdx < 32) {
571
+ this.masks.lo[s] = (this.masks.lo[s] | (1 << bitIdx)) >>> 0;
572
+ }
573
+ else {
574
+ this.masks.hi[s] = (this.masks.hi[s] | (1 << (bitIdx - 32))) >>> 0;
575
+ }
576
+ if (this.masks.lo[s] === SOLID_LO && this.masks.hi[s] === SOLID_HI) {
577
+ this.masks.removeAt(s);
578
+ writeBlockType(this.types, blockIdx, BLOCK_SOLID);
579
+ }
580
+ }
581
+ else {
582
+ writeBlockType(this.types, blockIdx, BLOCK_MIXED);
583
+ this.masks.set(blockIdx, bitIdx < 32 ? (1 << bitIdx) >>> 0 : 0, bitIdx >= 32 ? (1 << (bitIdx - 32)) >>> 0 : 0);
584
+ }
585
+ }
586
+ orBlock(blockIdx, lo, hi) {
587
+ if (lo === 0 && hi === 0) {
588
+ return;
589
+ }
590
+ const bt = readBlockType(this.types, blockIdx);
591
+ if (bt === BLOCK_SOLID) {
592
+ return;
593
+ }
594
+ if (bt === BLOCK_MIXED) {
595
+ const s = this.masks.slot(blockIdx);
596
+ this.masks.lo[s] = (this.masks.lo[s] | lo) >>> 0;
597
+ this.masks.hi[s] = (this.masks.hi[s] | hi) >>> 0;
598
+ if (this.masks.lo[s] === SOLID_LO && this.masks.hi[s] === SOLID_HI) {
599
+ this.masks.removeAt(s);
600
+ writeBlockType(this.types, blockIdx, BLOCK_SOLID);
601
+ }
602
+ }
603
+ else {
604
+ if (lo >>> 0 === SOLID_LO && hi >>> 0 === SOLID_HI) {
605
+ writeBlockType(this.types, blockIdx, BLOCK_SOLID);
606
+ }
607
+ else {
608
+ writeBlockType(this.types, blockIdx, BLOCK_MIXED);
609
+ this.masks.set(blockIdx, lo >>> 0, hi >>> 0);
610
+ }
611
+ }
612
+ }
613
+ clear() {
614
+ this.types.fill(0);
615
+ this.masks.clear();
616
+ }
617
+ releaseStorage() {
618
+ this.types = new Uint32Array(0);
619
+ this.masks.releaseStorage();
620
+ }
621
+ clone() {
622
+ const g = new SparseVoxelGrid(this.nx, this.ny, this.nz);
623
+ g.types.set(this.types);
624
+ g.masks = this.masks.clone();
625
+ return g;
626
+ }
627
+ cropTo(cropMinBx, cropMinBy, cropMinBz, cropMaxBx, cropMaxBy, cropMaxBz, onProgress) {
628
+ const outNbx = cropMaxBx - cropMinBx;
629
+ const outNby = cropMaxBy - cropMinBy;
630
+ const outNbz = cropMaxBz - cropMinBz;
631
+ const out = new SparseVoxelGrid(outNbx * 4, outNby * 4, outNbz * 4);
632
+ const outBStride = outNbx * outNby;
633
+ const { nbx, nby } = this;
634
+ const totalBlocks = nbx * nby * this.nbz;
635
+ const types = this.types;
636
+ const masks = this.masks;
637
+ const outTypes = out.types;
638
+ const outMasks = out.masks;
639
+ if (out.nbx * out.nby * out.nbz === 0) {
640
+ if (onProgress) {
641
+ onProgress(0, 0);
642
+ }
643
+ return out;
644
+ }
645
+ const PROGRESS_INTERVAL = 1 << 13;
646
+ let nextTick = PROGRESS_INTERVAL;
647
+ for (let w = 0; w < types.length; w++) {
648
+ if (onProgress && w >= nextTick) {
649
+ onProgress(w, types.length);
650
+ nextTick = w + PROGRESS_INTERVAL;
651
+ }
652
+ const word = types[w];
653
+ if (word === 0) {
654
+ continue;
655
+ }
656
+ let nonEmpty = ((word & EVEN_BITS) | ((word >>> 1) & EVEN_BITS)) >>> 0;
657
+ const baseIdx = w * BLOCKS_PER_WORD;
658
+ let bx = baseIdx % nbx;
659
+ const byBz = (baseIdx / nbx) | 0;
660
+ let by = byBz % nby;
661
+ let bz = (byBz / nby) | 0;
662
+ let coordLane = 0;
663
+ while (nonEmpty) {
664
+ const bp = 31 - Math.clz32(nonEmpty & -nonEmpty);
665
+ const lane = bp >>> 1;
666
+ nonEmpty &= nonEmpty - 1;
667
+ const blockIdx = baseIdx + lane;
668
+ if (blockIdx >= totalBlocks) {
669
+ break;
670
+ }
671
+ bx += lane - coordLane;
672
+ coordLane = lane;
673
+ while (bx >= nbx) {
674
+ bx -= nbx;
675
+ by++;
676
+ if (by >= nby) {
677
+ by = 0;
678
+ bz++;
679
+ }
680
+ }
681
+ if (bx < cropMinBx ||
682
+ bx >= cropMaxBx ||
683
+ by < cropMinBy ||
684
+ by >= cropMaxBy ||
685
+ bz < cropMinBz ||
686
+ bz >= cropMaxBz) {
687
+ continue;
688
+ }
689
+ const outIdx = bx - cropMinBx + (by - cropMinBy) * outNbx + (bz - cropMinBz) * outBStride;
690
+ const bt = (word >>> (lane << 1)) & TYPE_MASK;
691
+ writeBlockType(outTypes, outIdx, bt);
692
+ if (bt === BLOCK_MIXED) {
693
+ const s = masks.slot(blockIdx);
694
+ outMasks.set(outIdx, masks.lo[s], masks.hi[s]);
695
+ }
696
+ }
697
+ }
698
+ if (onProgress) {
699
+ onProgress(types.length, types.length);
700
+ }
701
+ return out;
702
+ }
703
+ cropToInverted(cropMinBx, cropMinBy, cropMinBz, cropMaxBx, cropMaxBy, cropMaxBz, onProgress) {
704
+ const outNbx = cropMaxBx - cropMinBx;
705
+ const outNby = cropMaxBy - cropMinBy;
706
+ const outNbz = cropMaxBz - cropMinBz;
707
+ const out = new SparseVoxelGrid(outNbx * 4, outNby * 4, outNbz * 4);
708
+ const outBStride = outNbx * outNby;
709
+ const outTotalBlocks = outNbx * outNby * outNbz;
710
+ const outTypes = out.types;
711
+ const outMasks = out.masks;
712
+ if (outTotalBlocks === 0) {
713
+ if (onProgress) {
714
+ onProgress(0, 0);
715
+ }
716
+ return out;
717
+ }
718
+ const SOLID_WORD = 0x55555555 >>> 0;
719
+ outTypes.fill(SOLID_WORD);
720
+ const lastWord = outTypes.length - 1;
721
+ const lastLanes = outTotalBlocks - lastWord * BLOCKS_PER_WORD;
722
+ if (lastLanes < BLOCKS_PER_WORD) {
723
+ const validBits = (1 << (lastLanes * 2)) - 1;
724
+ outTypes[lastWord] = (outTypes[lastWord] & validBits) >>> 0;
725
+ }
726
+ const { nbx, nby } = this;
727
+ const types = this.types;
728
+ const masks = this.masks;
729
+ const totalBlocks = nbx * nby * this.nbz;
730
+ const PROGRESS_INTERVAL = 1 << 13;
731
+ let nextTick = PROGRESS_INTERVAL;
732
+ for (let w = 0; w < types.length; w++) {
733
+ if (onProgress && w >= nextTick) {
734
+ onProgress(w, types.length);
735
+ nextTick = w + PROGRESS_INTERVAL;
736
+ }
737
+ const word = types[w];
738
+ if (word === 0) {
739
+ continue;
740
+ }
741
+ let nonEmpty = ((word & EVEN_BITS) | ((word >>> 1) & EVEN_BITS)) >>> 0;
742
+ const baseIdx = w * BLOCKS_PER_WORD;
743
+ let bx = baseIdx % nbx;
744
+ const byBz = (baseIdx / nbx) | 0;
745
+ let by = byBz % nby;
746
+ let bz = (byBz / nby) | 0;
747
+ let coordLane = 0;
748
+ while (nonEmpty) {
749
+ const bp = 31 - Math.clz32(nonEmpty & -nonEmpty);
750
+ const lane = bp >>> 1;
751
+ nonEmpty &= nonEmpty - 1;
752
+ const blockIdx = baseIdx + lane;
753
+ if (blockIdx >= totalBlocks) {
754
+ break;
755
+ }
756
+ bx += lane - coordLane;
757
+ coordLane = lane;
758
+ while (bx >= nbx) {
759
+ bx -= nbx;
760
+ by++;
761
+ if (by >= nby) {
762
+ by = 0;
763
+ bz++;
764
+ }
765
+ }
766
+ if (bx < cropMinBx ||
767
+ bx >= cropMaxBx ||
768
+ by < cropMinBy ||
769
+ by >= cropMaxBy ||
770
+ bz < cropMinBz ||
771
+ bz >= cropMaxBz) {
772
+ continue;
773
+ }
774
+ const outIdx = bx - cropMinBx + (by - cropMinBy) * outNbx + (bz - cropMinBz) * outBStride;
775
+ const bt = (word >>> (lane << 1)) & TYPE_MASK;
776
+ if (bt === BLOCK_SOLID) {
777
+ writeBlockType(outTypes, outIdx, BLOCK_EMPTY);
778
+ }
779
+ else {
780
+ writeBlockType(outTypes, outIdx, BLOCK_MIXED);
781
+ const s = masks.slot(blockIdx);
782
+ outMasks.set(outIdx, ~masks.lo[s] >>> 0, ~masks.hi[s] >>> 0);
783
+ }
784
+ }
785
+ }
786
+ if (onProgress) {
787
+ onProgress(types.length, types.length);
788
+ }
789
+ return out;
790
+ }
791
+ static fromBuffer(acc, nx, ny, nz) {
792
+ const g = new SparseVoxelGrid(nx, ny, nz);
793
+ const solidBlocks = acc.getSolidBlocks();
794
+ const totalBlocks = g.nbx * g.nby * g.nbz;
795
+ for (let i = 0; i < solidBlocks.length; i++) {
796
+ const blockIdx = solidBlocks[i];
797
+ if (blockIdx < 0 || blockIdx >= totalBlocks) {
798
+ continue;
799
+ }
800
+ writeBlockType(g.types, blockIdx, BLOCK_SOLID);
801
+ }
802
+ const mixed = acc.getMixedBlocks();
803
+ for (let i = 0; i < mixed.blockIdx.length; i++) {
804
+ const blockIdx = mixed.blockIdx[i];
805
+ if (blockIdx < 0 || blockIdx >= totalBlocks) {
806
+ continue;
807
+ }
808
+ if (readBlockType(g.types, blockIdx) === BLOCK_SOLID) {
809
+ continue;
810
+ }
811
+ writeBlockType(g.types, blockIdx, BLOCK_MIXED);
812
+ g.masks.set(blockIdx, mixed.masks[i * 2], mixed.masks[i * 2 + 1]);
813
+ }
814
+ return g;
815
+ }
816
+ toBuffer(cropMinBx, cropMinBy, cropMinBz, cropMaxBx, cropMaxBy, cropMaxBz, defaultSolid = false) {
817
+ const out = new BlockMaskBuffer();
818
+ for (let bz = cropMinBz; bz < cropMaxBz; bz++) {
819
+ for (let by = cropMinBy; by < cropMaxBy; by++) {
820
+ for (let bx = cropMinBx; bx < cropMaxBx; bx++) {
821
+ const blockIdx = bx + by * this.nbx + bz * this.bStride;
822
+ const bt = readBlockType(this.types, blockIdx);
823
+ let lo;
824
+ let hi;
825
+ if (bt === BLOCK_SOLID) {
826
+ lo = SOLID_LO;
827
+ hi = SOLID_HI;
828
+ }
829
+ else if (bt === BLOCK_MIXED) {
830
+ const s = this.masks.slot(blockIdx);
831
+ lo = this.masks.lo[s];
832
+ hi = this.masks.hi[s];
833
+ }
834
+ else if (defaultSolid) {
835
+ lo = SOLID_LO;
836
+ hi = SOLID_HI;
837
+ }
838
+ else {
839
+ continue;
840
+ }
841
+ if ((lo | hi) !== 0) {
842
+ const outNbx = cropMaxBx - cropMinBx;
843
+ const outNby = cropMaxBy - cropMinBy;
844
+ out.addBlock(bx - cropMinBx + (by - cropMinBy) * outNbx + (bz - cropMinBz) * outNbx * outNby, lo, hi);
845
+ }
846
+ }
847
+ }
848
+ }
849
+ return out;
850
+ }
851
+ toBufferInverted(cropMinBx, cropMinBy, cropMinBz, cropMaxBx, cropMaxBy, cropMaxBz) {
852
+ const out = new BlockMaskBuffer();
853
+ for (let bz = cropMinBz; bz < cropMaxBz; bz++) {
854
+ for (let by = cropMinBy; by < cropMaxBy; by++) {
855
+ for (let bx = cropMinBx; bx < cropMaxBx; bx++) {
856
+ const blockIdx = bx + by * this.nbx + bz * this.bStride;
857
+ const bt = readBlockType(this.types, blockIdx);
858
+ let lo;
859
+ let hi;
860
+ if (bt === BLOCK_SOLID) {
861
+ continue;
862
+ }
863
+ if (bt === BLOCK_MIXED) {
864
+ const s = this.masks.slot(blockIdx);
865
+ lo = ~this.masks.lo[s] >>> 0;
866
+ hi = ~this.masks.hi[s] >>> 0;
867
+ }
868
+ else {
869
+ lo = SOLID_LO;
870
+ hi = SOLID_HI;
871
+ }
872
+ if ((lo | hi) !== 0) {
873
+ const outNbx = cropMaxBx - cropMinBx;
874
+ const outNby = cropMaxBy - cropMinBy;
875
+ out.addBlock(bx - cropMinBx + (by - cropMinBy) * outNbx + (bz - cropMinBz) * outNbx * outNby, lo, hi);
876
+ }
877
+ }
878
+ }
879
+ }
880
+ return out;
881
+ }
882
+ getOccupiedBlockBounds(onProgress) {
883
+ const { nbx, nby } = this;
884
+ const totalBlocks = nbx * nby * this.nbz;
885
+ let minBx = nbx, minBy = nby, minBz = this.nbz;
886
+ let maxBx = 0, maxBy = 0, maxBz = 0;
887
+ let found = false;
888
+ const PROGRESS_INTERVAL = 1 << 13;
889
+ let nextTick = PROGRESS_INTERVAL;
890
+ for (let w = 0; w < this.types.length; w++) {
891
+ if (onProgress && w >= nextTick) {
892
+ onProgress(w, this.types.length);
893
+ nextTick = w + PROGRESS_INTERVAL;
894
+ }
895
+ const word = this.types[w];
896
+ if (word === 0) {
897
+ continue;
898
+ }
899
+ let nonEmpty = ((word & EVEN_BITS) | ((word >>> 1) & EVEN_BITS)) >>> 0;
900
+ const baseIdx = w * BLOCKS_PER_WORD;
901
+ let bx = baseIdx % nbx;
902
+ const byBz = (baseIdx / nbx) | 0;
903
+ let by = byBz % nby;
904
+ let bz = (byBz / nby) | 0;
905
+ let coordLane = 0;
906
+ while (nonEmpty) {
907
+ const bitPos = 31 - Math.clz32(nonEmpty & -nonEmpty);
908
+ const lane = bitPos >>> 1;
909
+ const blockIdx = baseIdx + lane;
910
+ if (blockIdx >= totalBlocks) {
911
+ nonEmpty = 0;
912
+ break;
913
+ }
914
+ bx += lane - coordLane;
915
+ coordLane = lane;
916
+ while (bx >= nbx) {
917
+ bx -= nbx;
918
+ by++;
919
+ if (by >= nby) {
920
+ by = 0;
921
+ bz++;
922
+ }
923
+ }
924
+ if (bx < minBx) {
925
+ minBx = bx;
926
+ }
927
+ if (bx > maxBx) {
928
+ maxBx = bx;
929
+ }
930
+ if (by < minBy) {
931
+ minBy = by;
932
+ }
933
+ if (by > maxBy) {
934
+ maxBy = by;
935
+ }
936
+ if (bz < minBz) {
937
+ minBz = bz;
938
+ }
939
+ if (bz > maxBz) {
940
+ maxBz = bz;
941
+ }
942
+ found = true;
943
+ nonEmpty &= nonEmpty - 1;
944
+ }
945
+ }
946
+ if (onProgress) {
947
+ onProgress(this.types.length, this.types.length);
948
+ }
949
+ return found ? { minBx, minBy, minBz, maxBx, maxBy, maxBz } : null;
950
+ }
951
+ getNavigableBlockBounds(onProgress) {
952
+ const { nbx, nby } = this;
953
+ const totalBlocks = nbx * nby * this.nbz;
954
+ if (totalBlocks === 0) {
955
+ if (onProgress) {
956
+ onProgress(0, 0);
957
+ }
958
+ return null;
959
+ }
960
+ const SOLID_WORD = 0x55555555 >>> 0;
961
+ const lastWordIdx = this.types.length - 1;
962
+ const lastLanes = totalBlocks - lastWordIdx * BLOCKS_PER_WORD;
963
+ const lastNonEmptyMask = lastLanes >= BLOCKS_PER_WORD ? EVEN_BITS : (((1 << (lastLanes * 2)) - 1) >>> 0) & EVEN_BITS;
964
+ let minBx = nbx, minBy = nby, minBz = this.nbz;
965
+ let maxBx = -1, maxBy = 0, maxBz = 0;
966
+ const PROGRESS_INTERVAL = 1 << 13;
967
+ let nextTick = PROGRESS_INTERVAL;
968
+ for (let w = 0; w < this.types.length; w++) {
969
+ if (onProgress && w >= nextTick) {
970
+ onProgress(w, this.types.length);
971
+ nextTick = w + PROGRESS_INTERVAL;
972
+ }
973
+ const baseIdx = w * BLOCKS_PER_WORD;
974
+ const word = this.types[w];
975
+ const flipped = (word ^ SOLID_WORD) >>> 0;
976
+ let navMask = ((flipped & EVEN_BITS) | ((flipped >>> 1) & EVEN_BITS)) >>> 0;
977
+ if (w === lastWordIdx) {
978
+ navMask &= lastNonEmptyMask;
979
+ }
980
+ let bx = baseIdx % nbx;
981
+ const byBz = (baseIdx / nbx) | 0;
982
+ let by = byBz % nby;
983
+ let bz = (byBz / nby) | 0;
984
+ let coordLane = 0;
985
+ while (navMask) {
986
+ const bp = 31 - Math.clz32(navMask & -navMask);
987
+ const lane = bp >>> 1;
988
+ bx += lane - coordLane;
989
+ coordLane = lane;
990
+ while (bx >= nbx) {
991
+ bx -= nbx;
992
+ by++;
993
+ if (by >= nby) {
994
+ by = 0;
995
+ bz++;
996
+ }
997
+ }
998
+ if (bx < minBx) {
999
+ minBx = bx;
1000
+ }
1001
+ if (bx > maxBx) {
1002
+ maxBx = bx;
1003
+ }
1004
+ if (by < minBy) {
1005
+ minBy = by;
1006
+ }
1007
+ if (by > maxBy) {
1008
+ maxBy = by;
1009
+ }
1010
+ if (bz < minBz) {
1011
+ minBz = bz;
1012
+ }
1013
+ if (bz > maxBz) {
1014
+ maxBz = bz;
1015
+ }
1016
+ navMask &= navMask - 1;
1017
+ }
1018
+ }
1019
+ if (onProgress) {
1020
+ onProgress(this.types.length, this.types.length);
1021
+ }
1022
+ return maxBx >= 0 ? { minBx, minBy, minBz, maxBx, maxBy, maxBz } : null;
1023
+ }
1024
+ static findNearestFreeCell(blocked, seedIx, seedIy, seedIz, maxRadius) {
1025
+ const { nx, ny, nz } = blocked;
1026
+ for (let r = 1; r <= maxRadius; r++) {
1027
+ for (let dz = -r; dz <= r; dz++) {
1028
+ for (let dy = -r; dy <= r; dy++) {
1029
+ for (let dx = -r; dx <= r; dx++) {
1030
+ if (Math.abs(dx) !== r && Math.abs(dy) !== r && Math.abs(dz) !== r) {
1031
+ continue;
1032
+ }
1033
+ const ix = seedIx + dx;
1034
+ const iy = seedIy + dy;
1035
+ const iz = seedIz + dz;
1036
+ if (ix < 0 || ix >= nx || iy < 0 || iy >= ny || iz < 0 || iz >= nz) {
1037
+ continue;
1038
+ }
1039
+ if (!blocked.getVoxel(ix, iy, iz)) {
1040
+ return { ix, iy, iz };
1041
+ }
1042
+ }
1043
+ }
1044
+ }
1045
+ }
1046
+ return null;
1047
+ }
1048
+ }
1049
+ export const SOLID_LEAF_MARKER = 0xff000000 >>> 0;
1050
+ export const MAX_24BIT_OFFSET = 0x00ffffff;
1051
+ const DENSE_SOLID_STREAM_THRESHOLD = 8_000_000;
1052
+ export class SparseOctree24BitOverflowError extends Error {
1053
+ constructor(kind, actual, limit) {
1054
+ super(`Sparse octree ${kind} count (${actual}) exceeds the Laine-Karras 24-bit baseOffset limit (${limit}). Reduce the grid size or split the scene.`);
1055
+ this.name = 'SparseOctree24BitOverflowError';
1056
+ this.kind = kind;
1057
+ this.actual = actual;
1058
+ this.limit = limit;
1059
+ }
1060
+ }
1061
+ export function getChildOffset(mask, octant) {
1062
+ const prefix = mask & ((1 << octant) - 1);
1063
+ let n = prefix >>> 0;
1064
+ n -= (n >>> 1) & 0x55555555;
1065
+ n = (n & 0x33333333) + ((n >>> 2) & 0x33333333);
1066
+ return (((n + (n >>> 4)) & 0x0f0f0f0f) * 0x01010101) >>> 24;
1067
+ }
1068
+ function bitCount(n) {
1069
+ let v = n >>> 0;
1070
+ v -= (v >>> 1) & 0x55555555;
1071
+ v = (v & 0x33333333) + ((v >>> 2) & 0x33333333);
1072
+ return (((v + (v >>> 4)) & 0x0f0f0f0f) * 0x01010101) >>> 24;
1073
+ }
1074
+ function sortMixedByMorton(mortons, masks, n = mortons.length) {
1075
+ if (n <= 1) {
1076
+ return;
1077
+ }
1078
+ const stackLo = [0];
1079
+ const stackHi = [n - 1];
1080
+ function swap(a, b) {
1081
+ const km = mortons[a];
1082
+ mortons[a] = mortons[b];
1083
+ mortons[b] = km;
1084
+ const alo = masks[a * 2];
1085
+ const ahi = masks[a * 2 + 1];
1086
+ masks[a * 2] = masks[b * 2];
1087
+ masks[a * 2 + 1] = masks[b * 2 + 1];
1088
+ masks[b * 2] = alo;
1089
+ masks[b * 2 + 1] = ahi;
1090
+ }
1091
+ while (stackLo.length > 0) {
1092
+ const lo = stackLo.pop();
1093
+ const hi = stackHi.pop();
1094
+ if (hi - lo < 16) {
1095
+ for (let i = lo + 1; i <= hi; i++) {
1096
+ const km = mortons[i];
1097
+ const m0 = masks[i * 2];
1098
+ const m1 = masks[i * 2 + 1];
1099
+ let j = i - 1;
1100
+ while (j >= lo && mortons[j] > km) {
1101
+ mortons[j + 1] = mortons[j];
1102
+ masks[(j + 1) * 2] = masks[j * 2];
1103
+ masks[(j + 1) * 2 + 1] = masks[j * 2 + 1];
1104
+ j--;
1105
+ }
1106
+ mortons[j + 1] = km;
1107
+ masks[(j + 1) * 2] = m0;
1108
+ masks[(j + 1) * 2 + 1] = m1;
1109
+ }
1110
+ continue;
1111
+ }
1112
+ const mid = (lo + hi) >>> 1;
1113
+ if (mortons[mid] < mortons[lo]) {
1114
+ swap(mid, lo);
1115
+ }
1116
+ if (mortons[hi] < mortons[lo]) {
1117
+ swap(hi, lo);
1118
+ }
1119
+ if (mortons[hi] < mortons[mid]) {
1120
+ swap(hi, mid);
1121
+ }
1122
+ const pivot = mortons[mid];
1123
+ let i = lo;
1124
+ let j = hi;
1125
+ while (i <= j) {
1126
+ while (mortons[i] < pivot) {
1127
+ i++;
1128
+ }
1129
+ while (mortons[j] > pivot) {
1130
+ j--;
1131
+ }
1132
+ if (i <= j) {
1133
+ if (i !== j) {
1134
+ swap(i, j);
1135
+ }
1136
+ i++;
1137
+ j--;
1138
+ }
1139
+ }
1140
+ if (j - lo > hi - i) {
1141
+ if (lo < j) {
1142
+ stackLo.push(lo);
1143
+ stackHi.push(j);
1144
+ }
1145
+ if (i < hi) {
1146
+ stackLo.push(i);
1147
+ stackHi.push(hi);
1148
+ }
1149
+ }
1150
+ else {
1151
+ if (i < hi) {
1152
+ stackLo.push(i);
1153
+ stackHi.push(hi);
1154
+ }
1155
+ if (lo < j) {
1156
+ stackLo.push(lo);
1157
+ stackHi.push(j);
1158
+ }
1159
+ }
1160
+ }
1161
+ }
1162
+ var OctreeNodeType;
1163
+ (function (OctreeNodeType) {
1164
+ OctreeNodeType[OctreeNodeType["Empty"] = 0] = "Empty";
1165
+ OctreeNodeType[OctreeNodeType["Solid"] = 1] = "Solid";
1166
+ OctreeNodeType[OctreeNodeType["Mixed"] = 2] = "Mixed";
1167
+ })(OctreeNodeType || (OctreeNodeType = {}));
1168
+ function createInteriorWave(initialCapacity) {
1169
+ const cap = Math.max(16, initialCapacity);
1170
+ return { pos: new Uint32Array(cap), li: new Uint32Array(cap), ii: new Uint32Array(cap), length: 0 };
1171
+ }
1172
+ function pushInteriorWave(wave, pos, li, ii) {
1173
+ if (wave.length === wave.pos.length) {
1174
+ const cap = wave.pos.length * 2;
1175
+ const grownPos = new Uint32Array(cap);
1176
+ const grownLi = new Uint32Array(cap);
1177
+ const grownIi = new Uint32Array(cap);
1178
+ grownPos.set(wave.pos);
1179
+ grownLi.set(wave.li);
1180
+ grownIi.set(wave.ii);
1181
+ wave.pos = grownPos;
1182
+ wave.li = grownLi;
1183
+ wave.ii = grownIi;
1184
+ }
1185
+ const i = wave.length++;
1186
+ wave.pos[i] = pos;
1187
+ wave.li[i] = li;
1188
+ wave.ii[i] = ii;
1189
+ }
1190
+ function shouldUseDenseMipBuild(totalBlocks, nSolid, nMixed) {
1191
+ return nSolid >= DENSE_SOLID_STREAM_THRESHOLD && nSolid > nMixed * 4 && nSolid > totalBlocks * 0.25;
1192
+ }
1193
+ function buildDenseTypeLevels(grid, maxDepth) {
1194
+ const levels = [
1195
+ {
1196
+ types: grid.types,
1197
+ nbx: grid.nbx,
1198
+ nby: grid.nby,
1199
+ nbz: grid.nbz,
1200
+ nonEmptyCount: 0,
1201
+ },
1202
+ ];
1203
+ for (let li = 1; li <= maxDepth; li++) {
1204
+ const prev = levels[li - 1];
1205
+ const nbx = Math.max(1, Math.ceil(prev.nbx / 2));
1206
+ const nby = Math.max(1, Math.ceil(prev.nby / 2));
1207
+ const nbz = Math.max(1, Math.ceil(prev.nbz / 2));
1208
+ const total = nbx * nby * nbz;
1209
+ const types = new Uint32Array(getBlockTypeWordCount(total));
1210
+ const prevStride = prev.nbx * prev.nby;
1211
+ const stride = nbx * nby;
1212
+ let nonEmptyCount = 0;
1213
+ for (let pz = 0; pz < nbz; pz++) {
1214
+ const childZ0 = pz << 1;
1215
+ for (let py = 0; py < nby; py++) {
1216
+ const childY0 = py << 1;
1217
+ for (let px = 0; px < nbx; px++) {
1218
+ const childX0 = px << 1;
1219
+ let childMask = 0;
1220
+ let allSolid = true;
1221
+ let childCount = 0;
1222
+ for (let oct = 0; oct < 8; oct++) {
1223
+ const cx = childX0 + (oct & 1);
1224
+ const cy = childY0 + ((oct >> 1) & 1);
1225
+ const cz = childZ0 + ((oct >> 2) & 1);
1226
+ if (cx >= prev.nbx || cy >= prev.nby || cz >= prev.nbz) {
1227
+ continue;
1228
+ }
1229
+ const childIdx = cx + cy * prev.nbx + cz * prevStride;
1230
+ const bt = readBlockType(prev.types, childIdx);
1231
+ if (bt === BLOCK_EMPTY) {
1232
+ continue;
1233
+ }
1234
+ childMask |= 1 << oct;
1235
+ childCount++;
1236
+ if (bt !== BLOCK_SOLID) {
1237
+ allSolid = false;
1238
+ }
1239
+ }
1240
+ if (childMask !== 0) {
1241
+ const parentIdx = px + py * nbx + pz * stride;
1242
+ writeBlockType(types, parentIdx, allSolid && childCount === 8 ? BLOCK_SOLID : BLOCK_MIXED);
1243
+ nonEmptyCount++;
1244
+ }
1245
+ }
1246
+ }
1247
+ }
1248
+ levels.push({ types, nbx, nby, nbz, nonEmptyCount });
1249
+ if (nonEmptyCount === 0) {
1250
+ break;
1251
+ }
1252
+ if (nonEmptyCount === 1 && readBlockType(types, 0) !== BLOCK_EMPTY) {
1253
+ break;
1254
+ }
1255
+ }
1256
+ return levels;
1257
+ }
1258
+ function lowerBoundF64(arr, target, n) {
1259
+ let lo = 0;
1260
+ let hi = n;
1261
+ while (lo < hi) {
1262
+ const mid = (lo + hi) >>> 1;
1263
+ if (arr[mid] < target) {
1264
+ lo = mid + 1;
1265
+ }
1266
+ else {
1267
+ hi = mid;
1268
+ }
1269
+ }
1270
+ return lo;
1271
+ }
1272
+ function flattenTreeFromLevels(interiorLevels, solidStream, mixedStream, mixedMasks, nSolid, nMixed, gridBounds, sceneBounds, voxelResolution, treeDepth) {
1273
+ if (interiorLevels.length === 0) {
1274
+ return {
1275
+ gridBounds,
1276
+ sceneBounds,
1277
+ voxelResolution,
1278
+ leafSize: LEAF_SIZE,
1279
+ treeDepth,
1280
+ numInteriorNodes: 0,
1281
+ numMixedLeaves: 0,
1282
+ nodes: new Uint32Array(0),
1283
+ leafData: new Uint32Array(0),
1284
+ };
1285
+ }
1286
+ const rootLevel = interiorLevels[interiorLevels.length - 1];
1287
+ let maxNodes = nSolid + nMixed;
1288
+ for (let l = 0; l < interiorLevels.length; l++) {
1289
+ maxNodes += interiorLevels[l].mortons.length;
1290
+ }
1291
+ const nodes = new Uint32Array(maxNodes);
1292
+ const leafData = new Uint32Array(nMixed * 2);
1293
+ let leafDataLen = 0;
1294
+ let numInteriorNodes = 0;
1295
+ let numMixedLeaves = 0;
1296
+ let emitPos = 0;
1297
+ let waveLi = [];
1298
+ let waveIi = [];
1299
+ const rootLi = interiorLevels.length - 1;
1300
+ for (let i = 0; i < rootLevel.mortons.length; i++) {
1301
+ waveLi.push(rootLi);
1302
+ waveIi.push(i);
1303
+ }
1304
+ const intPos = [];
1305
+ const intLi = [];
1306
+ const intIi = [];
1307
+ const intMask = [];
1308
+ while (waveLi.length > 0) {
1309
+ intPos.length = 0;
1310
+ intLi.length = 0;
1311
+ intIi.length = 0;
1312
+ intMask.length = 0;
1313
+ for (let w = 0; w < waveLi.length; w++) {
1314
+ const li = waveLi[w];
1315
+ const ii = waveIi[w];
1316
+ if (li === -1) {
1317
+ if (ii < nMixed) {
1318
+ const leafDataIndex = leafDataLen >> 1;
1319
+ if (leafDataIndex > MAX_24BIT_OFFSET) {
1320
+ throw new SparseOctree24BitOverflowError('mixed-leaf', leafDataIndex + 1, MAX_24BIT_OFFSET + 1);
1321
+ }
1322
+ leafData[leafDataLen++] = mixedMasks[ii * 2];
1323
+ leafData[leafDataLen++] = mixedMasks[ii * 2 + 1];
1324
+ nodes[emitPos] = leafDataIndex;
1325
+ numMixedLeaves++;
1326
+ }
1327
+ else {
1328
+ nodes[emitPos] = SOLID_LEAF_MARKER;
1329
+ }
1330
+ emitPos++;
1331
+ continue;
1332
+ }
1333
+ const level = interiorLevels[li];
1334
+ const type = level.types[ii];
1335
+ if (type === OctreeNodeType.Solid) {
1336
+ nodes[emitPos] = SOLID_LEAF_MARKER;
1337
+ }
1338
+ else {
1339
+ intPos.push(emitPos);
1340
+ intLi.push(li);
1341
+ intIi.push(ii);
1342
+ intMask.push(level.childMasks[ii]);
1343
+ numInteriorNodes++;
1344
+ nodes[emitPos] = 0;
1345
+ }
1346
+ emitPos++;
1347
+ }
1348
+ const nextWaveLi = [];
1349
+ const nextWaveIi = [];
1350
+ let nextChildStart = emitPos;
1351
+ for (let j = 0; j < intPos.length; j++) {
1352
+ const childMask = intMask[j];
1353
+ const childCount = bitCount(childMask);
1354
+ if (nextChildStart > MAX_24BIT_OFFSET) {
1355
+ throw new SparseOctree24BitOverflowError('node', nextChildStart + 1, MAX_24BIT_OFFSET + 1);
1356
+ }
1357
+ nodes[intPos[j]] = ((childMask & 0xff) << 24) | nextChildStart;
1358
+ const myLi = intLi[j];
1359
+ const myMorton = interiorLevels[myLi].mortons[intIi[j]];
1360
+ const childMortonBase = myMorton * 8;
1361
+ const childMortonEnd = childMortonBase + 8;
1362
+ if (myLi === 0) {
1363
+ let sIdx = lowerBoundF64(solidStream, childMortonBase, nSolid);
1364
+ let mIdx = lowerBoundF64(mixedStream, childMortonBase, nMixed);
1365
+ while (true) {
1366
+ const sM = sIdx < nSolid && solidStream[sIdx] < childMortonEnd
1367
+ ? solidStream[sIdx]
1368
+ : Number.POSITIVE_INFINITY;
1369
+ const mM = mIdx < nMixed && mixedStream[mIdx] < childMortonEnd
1370
+ ? mixedStream[mIdx]
1371
+ : Number.POSITIVE_INFINITY;
1372
+ if (!isFinite(sM) && !isFinite(mM)) {
1373
+ break;
1374
+ }
1375
+ if (sM < mM) {
1376
+ nextWaveLi.push(-1);
1377
+ nextWaveIi.push(nMixed + sIdx);
1378
+ sIdx++;
1379
+ }
1380
+ else {
1381
+ nextWaveLi.push(-1);
1382
+ nextWaveIi.push(mIdx);
1383
+ mIdx++;
1384
+ }
1385
+ }
1386
+ }
1387
+ else {
1388
+ const childLi = myLi - 1;
1389
+ const childLevel = interiorLevels[childLi];
1390
+ const childMortons = childLevel.mortons;
1391
+ let lo = 0;
1392
+ let hi = childMortons.length;
1393
+ while (lo < hi) {
1394
+ const mid = (lo + hi) >> 1;
1395
+ if (childMortons[mid] < childMortonBase) {
1396
+ lo = mid + 1;
1397
+ }
1398
+ else {
1399
+ hi = mid;
1400
+ }
1401
+ }
1402
+ while (lo < childMortons.length && childMortons[lo] < childMortonEnd) {
1403
+ nextWaveLi.push(childLi);
1404
+ nextWaveIi.push(lo);
1405
+ lo++;
1406
+ }
1407
+ }
1408
+ nextChildStart += childCount;
1409
+ }
1410
+ waveLi = nextWaveLi;
1411
+ waveIi = nextWaveIi;
1412
+ }
1413
+ return {
1414
+ gridBounds,
1415
+ sceneBounds,
1416
+ voxelResolution,
1417
+ leafSize: LEAF_SIZE,
1418
+ treeDepth,
1419
+ numInteriorNodes,
1420
+ numMixedLeaves,
1421
+ nodes: emitPos === maxNodes ? nodes : nodes.slice(0, emitPos),
1422
+ leafData: leafDataLen === leafData.length ? leafData : leafData.slice(0, leafDataLen),
1423
+ };
1424
+ }
1425
+ function flattenDenseLevels(levels, grid, gridBounds, sceneBounds, voxelResolution) {
1426
+ const treeDepth = Math.max(1, levels.length - 1);
1427
+ const rootLi = levels.length - 1;
1428
+ const rootLevel = levels[rootLi];
1429
+ const rootType = readBlockType(rootLevel.types, 0);
1430
+ if (rootType === BLOCK_EMPTY) {
1431
+ return {
1432
+ gridBounds,
1433
+ sceneBounds,
1434
+ voxelResolution,
1435
+ leafSize: LEAF_SIZE,
1436
+ treeDepth,
1437
+ numInteriorNodes: 0,
1438
+ numMixedLeaves: 0,
1439
+ nodes: new Uint32Array(0),
1440
+ leafData: new Uint32Array(0),
1441
+ };
1442
+ }
1443
+ let nodes = new Uint32Array(Math.max(1024, Math.min(MAX_24BIT_OFFSET + 1, grid.masks.size * 3)));
1444
+ let nodeLen = 0;
1445
+ let leafData = new Uint32Array(Math.max(1024, grid.masks.size * 2));
1446
+ let leafDataLen = 0;
1447
+ let numInteriorNodes = 0;
1448
+ let numMixedLeaves = 0;
1449
+ function appendNode(value) {
1450
+ if (nodeLen === nodes.length) {
1451
+ const grown = new Uint32Array(nodes.length * 2);
1452
+ grown.set(nodes);
1453
+ nodes = grown;
1454
+ }
1455
+ nodes[nodeLen] = value >>> 0;
1456
+ return nodeLen++;
1457
+ }
1458
+ function appendMixedLeaf(blockIdx) {
1459
+ const leafDataIndex = leafDataLen >> 1;
1460
+ if (leafDataIndex > MAX_24BIT_OFFSET) {
1461
+ throw new SparseOctree24BitOverflowError('mixed-leaf', leafDataIndex + 1, MAX_24BIT_OFFSET + 1);
1462
+ }
1463
+ if (leafDataLen + 2 > leafData.length) {
1464
+ const grown = new Uint32Array(leafData.length * 2);
1465
+ grown.set(leafData);
1466
+ leafData = grown;
1467
+ }
1468
+ const s = grid.masks.slot(blockIdx);
1469
+ leafData[leafDataLen++] = grid.masks.lo[s];
1470
+ leafData[leafDataLen++] = grid.masks.hi[s];
1471
+ appendNode(leafDataIndex);
1472
+ numMixedLeaves++;
1473
+ }
1474
+ let curWave = createInteriorWave(1);
1475
+ let nextWave = createInteriorWave(1024);
1476
+ function appendDenseNode(li, idx, wave) {
1477
+ const level = levels[li];
1478
+ const bt = readBlockType(level.types, idx);
1479
+ if (bt === BLOCK_SOLID) {
1480
+ appendNode(SOLID_LEAF_MARKER);
1481
+ }
1482
+ else if (bt === BLOCK_MIXED) {
1483
+ const pos = appendNode(0);
1484
+ pushInteriorWave(wave, pos, li, idx);
1485
+ numInteriorNodes++;
1486
+ }
1487
+ }
1488
+ appendDenseNode(rootLi, 0, curWave);
1489
+ while (curWave.length > 0) {
1490
+ nextWave.length = 0;
1491
+ const currentLi = curWave.li[0];
1492
+ for (let w = 0; w < curWave.length; w++) {
1493
+ const li = curWave.li[w];
1494
+ const parentLevel = levels[li];
1495
+ const childLevel = levels[li - 1];
1496
+ const parentIdx = curWave.ii[w];
1497
+ const px = parentIdx % parentLevel.nbx;
1498
+ const pyBz = (parentIdx / parentLevel.nbx) | 0;
1499
+ const py = pyBz % parentLevel.nby;
1500
+ const pz = (pyBz / parentLevel.nby) | 0;
1501
+ const childX0 = px << 1;
1502
+ const childY0 = py << 1;
1503
+ const childZ0 = pz << 1;
1504
+ const childStride = childLevel.nbx * childLevel.nby;
1505
+ const childStart = nodeLen;
1506
+ let childMask = 0;
1507
+ if (childStart > MAX_24BIT_OFFSET) {
1508
+ throw new SparseOctree24BitOverflowError('node', childStart + 1, MAX_24BIT_OFFSET + 1);
1509
+ }
1510
+ for (let oct = 0; oct < 8; oct++) {
1511
+ const cx = childX0 + (oct & 1);
1512
+ const cy = childY0 + ((oct >> 1) & 1);
1513
+ const cz = childZ0 + ((oct >> 2) & 1);
1514
+ if (cx >= childLevel.nbx || cy >= childLevel.nby || cz >= childLevel.nbz) {
1515
+ continue;
1516
+ }
1517
+ const childIdx = cx + cy * childLevel.nbx + cz * childStride;
1518
+ const bt = readBlockType(childLevel.types, childIdx);
1519
+ if (bt === BLOCK_EMPTY) {
1520
+ continue;
1521
+ }
1522
+ childMask |= 1 << oct;
1523
+ if (li === 1) {
1524
+ if (bt === BLOCK_SOLID) {
1525
+ appendNode(SOLID_LEAF_MARKER);
1526
+ }
1527
+ else {
1528
+ appendMixedLeaf(childIdx);
1529
+ }
1530
+ }
1531
+ else {
1532
+ appendDenseNode(li - 1, childIdx, nextWave);
1533
+ }
1534
+ }
1535
+ nodes[curWave.pos[w]] = ((childMask & 0xff) << 24) | childStart;
1536
+ }
1537
+ levels[currentLi] = null;
1538
+ const tmp = curWave;
1539
+ curWave = nextWave;
1540
+ nextWave = tmp;
1541
+ }
1542
+ return {
1543
+ gridBounds,
1544
+ sceneBounds,
1545
+ voxelResolution,
1546
+ leafSize: LEAF_SIZE,
1547
+ treeDepth,
1548
+ numInteriorNodes,
1549
+ numMixedLeaves,
1550
+ nodes: nodes.slice(0, nodeLen),
1551
+ leafData: leafData.slice(0, leafDataLen),
1552
+ };
1553
+ }
1554
+ function buildSparseOctreeDense(grid, gridBounds, sceneBounds, voxelResolution, maxDepth, consumeGrid) {
1555
+ const levels = buildDenseTypeLevels(grid, maxDepth);
1556
+ const result = flattenDenseLevels(levels, grid, gridBounds, sceneBounds, voxelResolution);
1557
+ if (consumeGrid) {
1558
+ grid.releaseStorage();
1559
+ }
1560
+ return result;
1561
+ }
1562
+ /**
1563
+ * Build a sparse octree from block masks using:
1564
+ * 1) mixed+solid SoA merge and Morton sort
1565
+ * 2) bottom-up level construction by parent Morton grouping
1566
+ * 3) BFS flatten to node/leafData arrays.
1567
+ */
1568
+ export function buildSparseOctree(grid, gridBounds, sceneBounds, voxelResolution, options = {}) {
1569
+ const { nbx, nby, nbz, types: gridTypes, masks: gridMasks } = grid;
1570
+ const totalBlocks = nbx * nby * nbz;
1571
+ const blocksPerAxis = Math.max(nbx, nby, nbz);
1572
+ const treeDepth = Math.max(1, Math.ceil(Math.log2(blocksPerAxis)));
1573
+ const lastWordIdx = gridTypes.length - 1;
1574
+ const lastLanes = totalBlocks - lastWordIdx * BLOCKS_PER_WORD;
1575
+ const lastValidWordMask = lastLanes >= BLOCKS_PER_WORD ? 0xffffffff >>> 0 : ((1 << (lastLanes * 2)) - 1) >>> 0;
1576
+ let nSolid = 0;
1577
+ let nMixed = 0;
1578
+ for (let w = 0; w < gridTypes.length; w++) {
1579
+ let word = gridTypes[w];
1580
+ if (w === lastWordIdx) {
1581
+ word = (word & lastValidWordMask) >>> 0;
1582
+ }
1583
+ if (word === 0) {
1584
+ continue;
1585
+ }
1586
+ const solidMask = word & EVEN_BITS & ~((word >>> 1) & EVEN_BITS);
1587
+ const mixedMask = (word >>> 1) & EVEN_BITS & ~(word & EVEN_BITS);
1588
+ nSolid += bitCount(solidMask >>> 0);
1589
+ nMixed += bitCount(mixedMask >>> 0);
1590
+ }
1591
+ if (nSolid + nMixed === 0) {
1592
+ return {
1593
+ gridBounds,
1594
+ sceneBounds,
1595
+ voxelResolution,
1596
+ leafSize: LEAF_SIZE,
1597
+ treeDepth: 1,
1598
+ numInteriorNodes: 0,
1599
+ numMixedLeaves: 0,
1600
+ nodes: new Uint32Array(0),
1601
+ leafData: new Uint32Array(0),
1602
+ };
1603
+ }
1604
+ if (options.dense || shouldUseDenseMipBuild(totalBlocks, nSolid, nMixed)) {
1605
+ return buildSparseOctreeDense(grid, gridBounds, sceneBounds, voxelResolution, treeDepth, !!options.consumeGrid);
1606
+ }
1607
+ const solidStream = new Float64Array(nSolid);
1608
+ const mixedStream = new Float64Array(nMixed);
1609
+ const mixedMasks = new Uint32Array(nMixed * 2);
1610
+ let solidWriteIdx = 0;
1611
+ let mixedWriteIdx = 0;
1612
+ for (let w = 0; w < gridTypes.length; w++) {
1613
+ let word = gridTypes[w];
1614
+ if (w === lastWordIdx) {
1615
+ word = (word & lastValidWordMask) >>> 0;
1616
+ }
1617
+ if (word === 0) {
1618
+ continue;
1619
+ }
1620
+ let nonEmpty = ((word & EVEN_BITS) | ((word >>> 1) & EVEN_BITS)) >>> 0;
1621
+ const baseIdx = w * BLOCKS_PER_WORD;
1622
+ while (nonEmpty) {
1623
+ const bp = 31 - Math.clz32(nonEmpty & -nonEmpty);
1624
+ const lane = bp >>> 1;
1625
+ nonEmpty &= nonEmpty - 1;
1626
+ const blockIdx = baseIdx + lane;
1627
+ if (blockIdx >= totalBlocks) {
1628
+ break;
1629
+ }
1630
+ const bx = blockIdx % nbx;
1631
+ const byBz = (blockIdx / nbx) | 0;
1632
+ const by = byBz % nby;
1633
+ const bz = (byBz / nby) | 0;
1634
+ const morton = encodeMorton3(bx, by, bz);
1635
+ const bt = (word >>> (lane << 1)) & TYPE_MASK;
1636
+ if (bt === OctreeNodeType.Solid) {
1637
+ solidStream[solidWriteIdx++] = morton;
1638
+ }
1639
+ else if (bt === OctreeNodeType.Mixed) {
1640
+ mixedStream[mixedWriteIdx] = morton;
1641
+ const s = gridMasks.slot(blockIdx);
1642
+ mixedMasks[mixedWriteIdx * 2] = gridMasks.lo[s];
1643
+ mixedMasks[mixedWriteIdx * 2 + 1] = gridMasks.hi[s];
1644
+ mixedWriteIdx++;
1645
+ }
1646
+ }
1647
+ }
1648
+ if (options.consumeGrid) {
1649
+ grid.releaseStorage();
1650
+ }
1651
+ if (nSolid > 1) {
1652
+ solidStream.sort();
1653
+ }
1654
+ if (nMixed > 1) {
1655
+ sortMixedByMorton(mixedStream, mixedMasks, nMixed);
1656
+ }
1657
+ const interiorLevels = [];
1658
+ let curMortons = [];
1659
+ let curTypes = [];
1660
+ let curChildMasks = [];
1661
+ {
1662
+ let sI = 0;
1663
+ let mI = 0;
1664
+ while (sI < nSolid || mI < nMixed) {
1665
+ const sM0 = sI < nSolid ? solidStream[sI] : Number.POSITIVE_INFINITY;
1666
+ const mM0 = mI < nMixed ? mixedStream[mI] : Number.POSITIVE_INFINITY;
1667
+ const minMorton = sM0 < mM0 ? sM0 : mM0;
1668
+ const parentMorton = Math.floor(minMorton / 8);
1669
+ let childMask = 0;
1670
+ let allSolid = true;
1671
+ let childCount = 0;
1672
+ while (true) {
1673
+ const sM = sI < nSolid ? solidStream[sI] : Number.POSITIVE_INFINITY;
1674
+ const mM = mI < nMixed ? mixedStream[mI] : Number.POSITIVE_INFINITY;
1675
+ const cur = sM < mM ? sM : mM;
1676
+ if (!isFinite(cur) || Math.floor(cur / 8) !== parentMorton) {
1677
+ break;
1678
+ }
1679
+ childMask |= 1 << (cur % 8);
1680
+ childCount++;
1681
+ if (sM < mM) {
1682
+ sI++;
1683
+ }
1684
+ else {
1685
+ allSolid = false;
1686
+ mI++;
1687
+ }
1688
+ }
1689
+ curMortons.push(parentMorton);
1690
+ if (allSolid && childCount === 8) {
1691
+ curTypes.push(OctreeNodeType.Solid);
1692
+ curChildMasks.push(0);
1693
+ }
1694
+ else {
1695
+ curTypes.push(OctreeNodeType.Mixed);
1696
+ curChildMasks.push(childMask);
1697
+ }
1698
+ }
1699
+ }
1700
+ let actualDepth = treeDepth;
1701
+ if (curMortons.length === 0) {
1702
+ actualDepth = 1;
1703
+ }
1704
+ else if (curMortons.length === 1 && curMortons[0] === 0) {
1705
+ actualDepth = 1;
1706
+ interiorLevels.push({ mortons: curMortons, types: curTypes, childMasks: curChildMasks });
1707
+ }
1708
+ else {
1709
+ for (let level = 1; level < treeDepth; level++) {
1710
+ interiorLevels.push({ mortons: curMortons, types: curTypes, childMasks: curChildMasks });
1711
+ const n = curMortons.length;
1712
+ const nextMortons = [];
1713
+ const nextTypes = [];
1714
+ const nextChildMasks = [];
1715
+ let i = 0;
1716
+ while (i < n) {
1717
+ const parentMorton = Math.floor(curMortons[i] / 8);
1718
+ let childMask = 0;
1719
+ let allSolid = true;
1720
+ let childCount = 0;
1721
+ while (i < n && Math.floor(curMortons[i] / 8) === parentMorton) {
1722
+ const octant = curMortons[i] % 8;
1723
+ childMask |= 1 << octant;
1724
+ if (curTypes[i] !== OctreeNodeType.Solid) {
1725
+ allSolid = false;
1726
+ }
1727
+ childCount++;
1728
+ i++;
1729
+ }
1730
+ nextMortons.push(parentMorton);
1731
+ if (allSolid && childCount === 8) {
1732
+ nextTypes.push(OctreeNodeType.Solid);
1733
+ nextChildMasks.push(0);
1734
+ }
1735
+ else {
1736
+ nextTypes.push(OctreeNodeType.Mixed);
1737
+ nextChildMasks.push(childMask);
1738
+ }
1739
+ }
1740
+ curMortons = nextMortons;
1741
+ curTypes = nextTypes;
1742
+ curChildMasks = nextChildMasks;
1743
+ if (curMortons.length === 1 && curMortons[0] === 0) {
1744
+ actualDepth = level + 1;
1745
+ break;
1746
+ }
1747
+ }
1748
+ interiorLevels.push({ mortons: curMortons, types: curTypes, childMasks: curChildMasks });
1749
+ }
1750
+ return flattenTreeFromLevels(interiorLevels, solidStream, mixedStream, mixedMasks, nSolid, nMixed, gridBounds, sceneBounds, voxelResolution, actualDepth);
1751
+ }
1752
+ export { BLOCK_EMPTY, BLOCK_SOLID, BLOCK_MIXED, BLOCKS_PER_WORD, TYPE_MASK, EVEN_BITS, readBlockType, writeBlockType, SOLID_LO, SOLID_HI, SparseVoxelGrid, };