@manycore/aholo-splat-transform 1.2.8 → 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 (97) hide show
  1. package/CHANGELOG.md +120 -113
  2. package/README.md +39 -39
  3. package/THIRD_PARTY_LICENSES.txt +1373 -1373
  4. package/bin/cli.js +125 -118
  5. package/dist/SplatData.d.ts +67 -67
  6. package/dist/SplatData.js +167 -150
  7. package/dist/constant.d.ts +3 -3
  8. package/dist/constant.js +13 -13
  9. package/dist/file/IFile.d.ts +5 -5
  10. package/dist/file/IFile.js +1 -1
  11. package/dist/file/esz.d.ts +11 -11
  12. package/dist/file/esz.js +337 -322
  13. package/dist/file/index.d.ts +8 -8
  14. package/dist/file/index.js +7 -7
  15. package/dist/file/ksplat.d.ts +12 -12
  16. package/dist/file/ksplat.js +293 -231
  17. package/dist/file/lcc.d.ts +11 -11
  18. package/dist/file/lcc.js +161 -158
  19. package/dist/file/ply.d.ts +13 -13
  20. package/dist/file/ply.js +439 -390
  21. package/dist/file/sog.d.ts +80 -80
  22. package/dist/file/sog.js +525 -494
  23. package/dist/file/splat.d.ts +6 -6
  24. package/dist/file/splat.js +119 -99
  25. package/dist/file/spz.d.ts +11 -11
  26. package/dist/file/spz.js +597 -583
  27. package/dist/file/voxel.d.ts +43 -37
  28. package/dist/file/voxel.js +411 -280
  29. package/dist/index.d.ts +33 -33
  30. package/dist/index.js +54 -54
  31. package/dist/native/index.d.ts +54 -54
  32. package/dist/native/index.js +122 -129
  33. package/dist/native/utils.d.ts +1 -0
  34. package/dist/native/utils.js +54 -0
  35. package/dist/tasks/AutoChunkLodTask.d.ts +13 -13
  36. package/dist/tasks/AutoChunkLodTask.js +117 -117
  37. package/dist/tasks/AutoLodTask.d.ts +10 -10
  38. package/dist/tasks/AutoLodTask.js +20 -20
  39. package/dist/tasks/BaseTask.d.ts +15 -15
  40. package/dist/tasks/BaseTask.js +5 -5
  41. package/dist/tasks/FlexLodTask.d.ts +12 -12
  42. package/dist/tasks/FlexLodTask.js +54 -44
  43. package/dist/tasks/ModifyTask.d.ts +9 -9
  44. package/dist/tasks/ModifyTask.js +166 -156
  45. package/dist/tasks/ReadTask.d.ts +9 -9
  46. package/dist/tasks/ReadTask.js +29 -29
  47. package/dist/tasks/SkeletonLodTask.d.ts +10 -10
  48. package/dist/tasks/SkeletonLodTask.js +176 -156
  49. package/dist/tasks/VoxelTask.d.ts +35 -30
  50. package/dist/tasks/VoxelTask.js +40 -37
  51. package/dist/tasks/WriteTask.d.ts +12 -12
  52. package/dist/tasks/WriteTask.js +70 -70
  53. package/dist/utils/BufferReader.d.ts +12 -12
  54. package/dist/utils/BufferReader.js +45 -45
  55. package/dist/utils/Logger.d.ts +11 -11
  56. package/dist/utils/Logger.js +40 -40
  57. package/dist/utils/StreamChunkDecoder.d.ts +16 -16
  58. package/dist/utils/StreamChunkDecoder.js +31 -31
  59. package/dist/utils/index.d.ts +27 -27
  60. package/dist/utils/index.js +101 -101
  61. package/dist/utils/k-means.d.ts +4 -4
  62. package/dist/utils/k-means.js +340 -341
  63. package/dist/utils/math.d.ts +46 -46
  64. package/dist/utils/math.js +350 -346
  65. package/dist/utils/quantize-1d.d.ts +4 -4
  66. package/dist/utils/quantize-1d.js +164 -164
  67. package/dist/utils/sh-rotate.d.ts +2 -2
  68. package/dist/utils/sh-rotate.js +236 -175
  69. package/dist/utils/splat.d.ts +21 -21
  70. package/dist/utils/splat.js +397 -387
  71. package/dist/utils/voxel/binary.d.ts +8 -0
  72. package/dist/utils/voxel/binary.js +176 -0
  73. package/dist/utils/voxel/common.d.ts +178 -162
  74. package/dist/utils/voxel/common.js +1752 -1682
  75. package/dist/utils/voxel/coplanar-merge.d.ts +63 -63
  76. package/dist/utils/voxel/coplanar-merge.js +818 -819
  77. package/dist/utils/voxel/filter-cluster.d.ts +20 -0
  78. package/dist/utils/voxel/filter-cluster.js +628 -0
  79. package/dist/utils/voxel/gpu-dilation.d.ts +2 -2
  80. package/dist/utils/voxel/gpu-dilation.js +677 -656
  81. package/dist/utils/voxel/marching-cubes.d.ts +42 -42
  82. package/dist/utils/voxel/marching-cubes.js +1645 -1657
  83. package/dist/utils/voxel/mesh.d.ts +3 -3
  84. package/dist/utils/voxel/mesh.js +130 -130
  85. package/dist/utils/voxel/nav.d.ts +29 -29
  86. package/dist/utils/voxel/nav.js +1068 -1043
  87. package/dist/utils/voxel/postprocess.d.ts +23 -23
  88. package/dist/utils/voxel/postprocess.js +408 -375
  89. package/dist/utils/voxel/voxel-faces.d.ts +18 -18
  90. package/dist/utils/voxel/voxel-faces.js +662 -663
  91. package/dist/utils/voxel/voxelize.d.ts +34 -33
  92. package/dist/utils/voxel/voxelize.js +1208 -1193
  93. package/dist/utils/webgpu.d.ts +8 -8
  94. package/dist/utils/webgpu.js +122 -122
  95. package/package.json +37 -39
  96. package/dist/native/cpp/bin/linux/binding.node +0 -0
  97. package/dist/native/cpp/bin/windows/binding.node +0 -0
@@ -1,387 +1,397 @@
1
- import { unzipSync } from 'fflate';
2
- import { PlyFile, SpzFile, KsplatFile, SplatFile, SogFile, LccFile, EszFile } from '../file/index.js';
3
- import { SplatData } from '../SplatData.js';
4
- import { SH_MAPS } from '../constant.js';
5
- export var SplatFileType;
6
- (function (SplatFileType) {
7
- SplatFileType[SplatFileType["PLY"] = 0] = "PLY";
8
- SplatFileType[SplatFileType["SPZ"] = 1] = "SPZ";
9
- SplatFileType[SplatFileType["USPZ"] = 2] = "USPZ";
10
- SplatFileType[SplatFileType["SPLAT"] = 3] = "SPLAT";
11
- SplatFileType[SplatFileType["KSPLAT"] = 4] = "KSPLAT";
12
- SplatFileType[SplatFileType["SOG"] = 5] = "SOG";
13
- SplatFileType[SplatFileType["LCC"] = 6] = "LCC";
14
- SplatFileType[SplatFileType["ESZ"] = 7] = "ESZ";
15
- })(SplatFileType || (SplatFileType = {}));
16
- export function detectSplatFileType(filename, buffer = new Uint8Array()) {
17
- let ext = filename.split('.').pop();
18
- if (ext === 'zip') {
19
- unzipSync(buffer, {
20
- filter: file => {
21
- const { name } = file;
22
- if (name.endsWith('meta.json')) {
23
- ext = 'sog';
24
- }
25
- else if (name.endsWith('meta.lcc')) {
26
- ext = 'lcc';
27
- }
28
- return false;
29
- },
30
- });
31
- }
32
- else if (ext === 'json') {
33
- // fast check sog json
34
- const json = JSON.parse(new TextDecoder().decode(buffer));
35
- const isSogMetadata = ['means', 'scales', 'quats', 'sh0'].every(k => !!json[k]);
36
- if (isSogMetadata) {
37
- ext = 'sog';
38
- }
39
- }
40
- let type;
41
- switch (ext) {
42
- case 'ply': {
43
- type = SplatFileType.PLY;
44
- break;
45
- }
46
- case 'spz': {
47
- type = SplatFileType.SPZ;
48
- break;
49
- }
50
- case 'uspz': {
51
- type = SplatFileType.USPZ;
52
- break;
53
- }
54
- case 'splat': {
55
- type = SplatFileType.SPLAT;
56
- break;
57
- }
58
- case 'ksplat': {
59
- type = SplatFileType.KSPLAT;
60
- break;
61
- }
62
- case 'sog': {
63
- type = SplatFileType.SOG;
64
- break;
65
- }
66
- case 'lcc': {
67
- type = SplatFileType.LCC;
68
- break;
69
- }
70
- case 'esz': {
71
- type = SplatFileType.ESZ;
72
- break;
73
- }
74
- default: {
75
- break;
76
- }
77
- }
78
- return type;
79
- }
80
- export function createSplatFile(path, buffer = new Uint8Array(), compressLevel = 6, spzVersion = 3) {
81
- const type = detectSplatFileType(path, buffer);
82
- if (type === undefined) {
83
- throw new Error(`Unsupported file format: ${path}`);
84
- }
85
- let file;
86
- switch (type) {
87
- case SplatFileType.PLY: {
88
- file = new PlyFile();
89
- break;
90
- }
91
- case SplatFileType.SPZ: {
92
- file = new SpzFile(compressLevel, spzVersion);
93
- break;
94
- }
95
- case SplatFileType.USPZ: {
96
- file = new SpzFile(-1);
97
- break;
98
- }
99
- case SplatFileType.KSPLAT: {
100
- file = new KsplatFile();
101
- break;
102
- }
103
- case SplatFileType.SPLAT: {
104
- file = new SplatFile();
105
- break;
106
- }
107
- case SplatFileType.SOG: {
108
- file = new SogFile();
109
- break;
110
- }
111
- case SplatFileType.LCC: {
112
- file = new LccFile();
113
- break;
114
- }
115
- case SplatFileType.ESZ: {
116
- file = new EszFile();
117
- break;
118
- }
119
- }
120
- return file;
121
- }
122
- export function combineSplatData(source) {
123
- const target = new SplatData().init(source.reduce((p, c) => p + c.counts, 0), Math.max(...source.map(v => v.shDegree)));
124
- const single = {
125
- x: 0, y: 0, z: 0,
126
- sx: 0, sy: 0, sz: 0,
127
- qx: 0, qy: 0, qz: 0, qw: 0,
128
- r: 0, g: 0, b: 0, a: 0,
129
- shN: new Array(SH_MAPS[target.shDegree]),
130
- };
131
- const shN = single.shN;
132
- let index = 0;
133
- for (let i = 0; i < source.length; i++) {
134
- const splat = source[i];
135
- const { counts } = splat;
136
- for (let j = 0; j < counts; j++) {
137
- splat.get(j, single);
138
- splat.getShN(j, shN);
139
- target.set(index, single);
140
- target.setShN(index, shN);
141
- index++;
142
- }
143
- }
144
- return target;
145
- }
146
- const VOXEL_COUNTS = 65535;
147
- export function computeDenseBox(data, ratio = 0.98) {
148
- if (data.counts === 0) {
149
- return { min: [0, 0, 0], max: [0, 0, 0] };
150
- }
151
- const xCol = data.table[0 /* ColIdx.x */];
152
- const yCol = data.table[1 /* ColIdx.y */];
153
- const zCol = data.table[2 /* ColIdx.z */];
154
- let minX = Infinity;
155
- let minY = Infinity;
156
- let minZ = Infinity;
157
- let maxX = -Infinity;
158
- let maxY = -Infinity;
159
- let maxZ = -Infinity;
160
- for (let i = 0; i < data.counts; i++) {
161
- const x = xCol[i];
162
- const y = yCol[i];
163
- const z = zCol[i];
164
- if (x < minX) {
165
- minX = x;
166
- }
167
- if (x > maxX) {
168
- maxX = x;
169
- }
170
- if (y < minY) {
171
- minY = y;
172
- }
173
- if (y > maxY) {
174
- maxY = y;
175
- }
176
- if (z < minZ) {
177
- minZ = z;
178
- }
179
- if (z > maxZ) {
180
- maxZ = z;
181
- }
182
- }
183
- const scaleX = VOXEL_COUNTS / Math.max(maxX - minX, 1e-9);
184
- const scaleY = VOXEL_COUNTS / Math.max(maxY - minY, 1e-9);
185
- const scaleZ = VOXEL_COUNTS / Math.max(maxZ - minZ, 1e-9);
186
- const xChunks = new Uint32Array(VOXEL_COUNTS);
187
- const yChunks = new Uint32Array(VOXEL_COUNTS);
188
- const zChunks = new Uint32Array(VOXEL_COUNTS);
189
- for (let i = 0; i < data.counts; i++) {
190
- xChunks[((xCol[i] - minX) * scaleX) | 0]++;
191
- yChunks[((yCol[i] - minY) * scaleY) | 0]++;
192
- zChunks[((zCol[i] - minZ) * scaleZ) | 0]++;
193
- }
194
- const K = Math.ceil(data.counts * (1 - ratio));
195
- let startX = 0;
196
- let endX = VOXEL_COUNTS - 1;
197
- let startY = 0;
198
- let endY = VOXEL_COUNTS - 1;
199
- let startZ = 0;
200
- let endZ = VOXEL_COUNTS - 1;
201
- let count = data.counts;
202
- while (count > K) {
203
- const xs = xChunks[startX];
204
- const xe = xChunks[endX];
205
- const ys = yChunks[startY];
206
- const ye = yChunks[endY];
207
- const zs = zChunks[startZ];
208
- const ze = zChunks[endZ];
209
- let min = xs;
210
- let minKey = 'startX';
211
- if (xe < min) {
212
- min = xe;
213
- minKey = 'endX';
214
- }
215
- if (ys < min) {
216
- min = ys;
217
- minKey = 'startY';
218
- }
219
- if (ye < min) {
220
- min = ye;
221
- minKey = 'endY';
222
- }
223
- if (zs < min) {
224
- min = zs;
225
- minKey = 'startZ';
226
- }
227
- if (ze < min) {
228
- min = ze;
229
- minKey = 'endZ';
230
- }
231
- switch (minKey) {
232
- case 'startX':
233
- startX++;
234
- break;
235
- case 'endX':
236
- endX--;
237
- break;
238
- case 'startY':
239
- startY++;
240
- break;
241
- case 'endY':
242
- endY--;
243
- break;
244
- case 'startZ':
245
- startZ++;
246
- break;
247
- case 'endZ':
248
- endZ--;
249
- break;
250
- }
251
- count -= min;
252
- }
253
- return {
254
- min: [(startX / scaleX) + minX, (startY / scaleY) + minY, (startZ / scaleZ) + minZ],
255
- max: [(endX / scaleX) + minX, (endY / scaleY) + minY, (endZ / scaleZ) + minZ],
256
- };
257
- }
258
- // https://github.com/playcanvas/splat-transform/blob/main/src/lib/data-table/data-table.ts
259
- export function mortonSort(splat) {
260
- const result = new Uint32Array(splat.counts);
261
- const xCol = splat.table[0 /* ColIdx.x */];
262
- const yCol = splat.table[1 /* ColIdx.y */];
263
- const zCol = splat.table[2 /* ColIdx.z */];
264
- for (let i = 0; i < result.length; ++i) {
265
- result[i] = i;
266
- }
267
- const generate = (indices) => {
268
- if (indices.length === 0) {
269
- return;
270
- }
271
- // https://fgiesen.wordpress.com/2009/12/13/decoding-morton-codes/
272
- const encodeMorton3 = (x, y, z) => {
273
- const Part1By2 = (x) => {
274
- x &= 0x000003ff;
275
- x = (x ^ (x << 16)) & 0xff0000ff;
276
- x = (x ^ (x << 8)) & 0x0300f00f;
277
- x = (x ^ (x << 4)) & 0x030c30c3;
278
- x = (x ^ (x << 2)) & 0x09249249;
279
- return x;
280
- };
281
- return (Part1By2(z) << 2) + (Part1By2(y) << 1) + Part1By2(x);
282
- };
283
- let mx = Infinity;
284
- let my = Infinity;
285
- let mz = Infinity;
286
- let Mx = -Infinity;
287
- let My = -Infinity;
288
- let Mz = -Infinity;
289
- // calculate scene extents across all splats (using sort centers, because they're in world space)
290
- for (let i = 0; i < indices.length; ++i) {
291
- const ri = indices[i];
292
- const x = xCol[ri];
293
- const y = yCol[ri];
294
- const z = zCol[ri];
295
- if (x < mx) {
296
- mx = x;
297
- }
298
- if (x > Mx) {
299
- Mx = x;
300
- }
301
- if (y < my) {
302
- my = y;
303
- }
304
- if (y > My) {
305
- My = y;
306
- }
307
- if (z < mz) {
308
- mz = z;
309
- }
310
- if (z > Mz) {
311
- Mz = z;
312
- }
313
- }
314
- const xlen = Mx - mx;
315
- const ylen = My - my;
316
- const zlen = Mz - mz;
317
- if (!isFinite(xlen) || !isFinite(ylen) || !isFinite(zlen)) {
318
- console.debug('invalid extents', xlen, ylen, zlen);
319
- return;
320
- }
321
- // all points are identical
322
- if (xlen === 0 && ylen === 0 && zlen === 0) {
323
- return;
324
- }
325
- const xmul = (xlen === 0) ? 0 : 1024 / xlen;
326
- const ymul = (ylen === 0) ? 0 : 1024 / ylen;
327
- const zmul = (zlen === 0) ? 0 : 1024 / zlen;
328
- const morton = new Uint32Array(indices.length);
329
- for (let i = 0; i < indices.length; ++i) {
330
- const ri = indices[i];
331
- const x = xCol[ri];
332
- const y = yCol[ri];
333
- const z = zCol[ri];
334
- const ix = Math.min(1023, (x - mx) * xmul) >>> 0;
335
- const iy = Math.min(1023, (y - my) * ymul) >>> 0;
336
- const iz = Math.min(1023, (z - mz) * zmul) >>> 0;
337
- morton[i] = encodeMorton3(ix, iy, iz);
338
- }
339
- // sort indices by morton code
340
- const order = new Uint32Array(indices.length);
341
- for (let i = 0; i < order.length; i++) {
342
- order[i] = i;
343
- }
344
- order.sort((a, b) => morton[a] - morton[b]);
345
- const tmpIndices = indices.slice();
346
- for (let i = 0; i < indices.length; ++i) {
347
- indices[i] = tmpIndices[order[i]];
348
- }
349
- // sort the largest buckets recursively
350
- let start = 0;
351
- let end = 1;
352
- while (start < indices.length) {
353
- while (end < indices.length && morton[order[end]] === morton[order[start]]) {
354
- ++end;
355
- }
356
- if (end - start > 256) {
357
- generate(indices.subarray(start, end));
358
- }
359
- start = end;
360
- }
361
- };
362
- generate(result);
363
- return result;
364
- }
365
- export function fastDeleteSplat(splat, indices) {
366
- const { counts, table } = splat;
367
- const map = new Uint32Array(counts - indices.length);
368
- let write = 0;
369
- let removeIdx = 0;
370
- for (let read = 0; read < counts; read++) {
371
- if (removeIdx < indices.length && read === indices[removeIdx]) {
372
- removeIdx++;
373
- continue;
374
- }
375
- map[write++] = read;
376
- }
377
- for (let i = 0; i < table.length; i++) {
378
- const col = table[i];
379
- for (let j = 0; j < map.length; j++) {
380
- col[j] = col[map[j]];
381
- }
382
- }
383
- splat.counts -= indices.length;
384
- for (let i = 0; i < table.length; i++) {
385
- table[i] = table[i].subarray(0, splat.counts);
386
- }
387
- }
1
+ import { unzipSync } from 'fflate';
2
+ import { PlyFile, SpzFile, KsplatFile, SplatFile, SogFile, LccFile, EszFile } from '../file/index.js';
3
+ import { ColIdx, SplatData } from '../SplatData.js';
4
+ import { SH_MAPS } from '../constant.js';
5
+ export var SplatFileType;
6
+ (function (SplatFileType) {
7
+ SplatFileType[SplatFileType["PLY"] = 0] = "PLY";
8
+ SplatFileType[SplatFileType["SPZ"] = 1] = "SPZ";
9
+ SplatFileType[SplatFileType["USPZ"] = 2] = "USPZ";
10
+ SplatFileType[SplatFileType["SPLAT"] = 3] = "SPLAT";
11
+ SplatFileType[SplatFileType["KSPLAT"] = 4] = "KSPLAT";
12
+ SplatFileType[SplatFileType["SOG"] = 5] = "SOG";
13
+ SplatFileType[SplatFileType["LCC"] = 6] = "LCC";
14
+ SplatFileType[SplatFileType["ESZ"] = 7] = "ESZ";
15
+ })(SplatFileType || (SplatFileType = {}));
16
+ export function detectSplatFileType(filename, buffer = new Uint8Array()) {
17
+ let ext = filename.split('.').pop();
18
+ if (ext === 'zip') {
19
+ unzipSync(buffer, {
20
+ filter: file => {
21
+ const { name } = file;
22
+ if (name.endsWith('meta.json')) {
23
+ ext = 'sog';
24
+ }
25
+ else if (name.endsWith('meta.lcc')) {
26
+ ext = 'lcc';
27
+ }
28
+ return false;
29
+ },
30
+ });
31
+ }
32
+ else if (ext === 'json') {
33
+ // fast check sog json
34
+ const json = JSON.parse(new TextDecoder().decode(buffer));
35
+ const isSogMetadata = ['means', 'scales', 'quats', 'sh0'].every(k => !!json[k]);
36
+ if (isSogMetadata) {
37
+ ext = 'sog';
38
+ }
39
+ }
40
+ let type;
41
+ switch (ext) {
42
+ case 'ply': {
43
+ type = SplatFileType.PLY;
44
+ break;
45
+ }
46
+ case 'spz': {
47
+ type = SplatFileType.SPZ;
48
+ break;
49
+ }
50
+ case 'uspz': {
51
+ type = SplatFileType.USPZ;
52
+ break;
53
+ }
54
+ case 'splat': {
55
+ type = SplatFileType.SPLAT;
56
+ break;
57
+ }
58
+ case 'ksplat': {
59
+ type = SplatFileType.KSPLAT;
60
+ break;
61
+ }
62
+ case 'sog': {
63
+ type = SplatFileType.SOG;
64
+ break;
65
+ }
66
+ case 'lcc': {
67
+ type = SplatFileType.LCC;
68
+ break;
69
+ }
70
+ case 'esz': {
71
+ type = SplatFileType.ESZ;
72
+ break;
73
+ }
74
+ default: {
75
+ break;
76
+ }
77
+ }
78
+ return type;
79
+ }
80
+ export function createSplatFile(path, buffer = new Uint8Array(), compressLevel = 6, spzVersion = 3) {
81
+ const type = detectSplatFileType(path, buffer);
82
+ if (type === undefined) {
83
+ throw new Error(`Unsupported file format: ${path}`);
84
+ }
85
+ let file;
86
+ switch (type) {
87
+ case SplatFileType.PLY: {
88
+ file = new PlyFile();
89
+ break;
90
+ }
91
+ case SplatFileType.SPZ: {
92
+ file = new SpzFile(compressLevel, spzVersion);
93
+ break;
94
+ }
95
+ case SplatFileType.USPZ: {
96
+ file = new SpzFile(-1);
97
+ break;
98
+ }
99
+ case SplatFileType.KSPLAT: {
100
+ file = new KsplatFile();
101
+ break;
102
+ }
103
+ case SplatFileType.SPLAT: {
104
+ file = new SplatFile();
105
+ break;
106
+ }
107
+ case SplatFileType.SOG: {
108
+ file = new SogFile();
109
+ break;
110
+ }
111
+ case SplatFileType.LCC: {
112
+ file = new LccFile();
113
+ break;
114
+ }
115
+ case SplatFileType.ESZ: {
116
+ file = new EszFile();
117
+ break;
118
+ }
119
+ }
120
+ return file;
121
+ }
122
+ export function combineSplatData(source) {
123
+ const target = new SplatData().init(source.reduce((p, c) => p + c.counts, 0), Math.max(...source.map(v => v.shDegree)));
124
+ const single = {
125
+ x: 0,
126
+ y: 0,
127
+ z: 0,
128
+ sx: 0,
129
+ sy: 0,
130
+ sz: 0,
131
+ qx: 0,
132
+ qy: 0,
133
+ qz: 0,
134
+ qw: 0,
135
+ r: 0,
136
+ g: 0,
137
+ b: 0,
138
+ a: 0,
139
+ shN: new Array(SH_MAPS[target.shDegree]),
140
+ };
141
+ const shN = single.shN;
142
+ let index = 0;
143
+ for (let i = 0; i < source.length; i++) {
144
+ const splat = source[i];
145
+ const { counts } = splat;
146
+ for (let j = 0; j < counts; j++) {
147
+ splat.get(j, single);
148
+ splat.getShN(j, shN);
149
+ target.set(index, single);
150
+ target.setShN(index, shN);
151
+ index++;
152
+ }
153
+ }
154
+ return target;
155
+ }
156
+ const VOXEL_COUNTS = 65535;
157
+ export function computeDenseBox(data, ratio = 0.98) {
158
+ if (data.counts === 0) {
159
+ return { min: [0, 0, 0], max: [0, 0, 0] };
160
+ }
161
+ const xCol = data.table[ColIdx.x];
162
+ const yCol = data.table[ColIdx.y];
163
+ const zCol = data.table[ColIdx.z];
164
+ let minX = Infinity;
165
+ let minY = Infinity;
166
+ let minZ = Infinity;
167
+ let maxX = -Infinity;
168
+ let maxY = -Infinity;
169
+ let maxZ = -Infinity;
170
+ for (let i = 0; i < data.counts; i++) {
171
+ const x = xCol[i];
172
+ const y = yCol[i];
173
+ const z = zCol[i];
174
+ if (x < minX) {
175
+ minX = x;
176
+ }
177
+ if (x > maxX) {
178
+ maxX = x;
179
+ }
180
+ if (y < minY) {
181
+ minY = y;
182
+ }
183
+ if (y > maxY) {
184
+ maxY = y;
185
+ }
186
+ if (z < minZ) {
187
+ minZ = z;
188
+ }
189
+ if (z > maxZ) {
190
+ maxZ = z;
191
+ }
192
+ }
193
+ const scaleX = VOXEL_COUNTS / Math.max(maxX - minX, 1e-9);
194
+ const scaleY = VOXEL_COUNTS / Math.max(maxY - minY, 1e-9);
195
+ const scaleZ = VOXEL_COUNTS / Math.max(maxZ - minZ, 1e-9);
196
+ const xChunks = new Uint32Array(VOXEL_COUNTS);
197
+ const yChunks = new Uint32Array(VOXEL_COUNTS);
198
+ const zChunks = new Uint32Array(VOXEL_COUNTS);
199
+ for (let i = 0; i < data.counts; i++) {
200
+ xChunks[((xCol[i] - minX) * scaleX) | 0]++;
201
+ yChunks[((yCol[i] - minY) * scaleY) | 0]++;
202
+ zChunks[((zCol[i] - minZ) * scaleZ) | 0]++;
203
+ }
204
+ const K = Math.ceil(data.counts * (1 - ratio));
205
+ let startX = 0;
206
+ let endX = VOXEL_COUNTS - 1;
207
+ let startY = 0;
208
+ let endY = VOXEL_COUNTS - 1;
209
+ let startZ = 0;
210
+ let endZ = VOXEL_COUNTS - 1;
211
+ let count = data.counts;
212
+ while (count > K) {
213
+ const xs = xChunks[startX];
214
+ const xe = xChunks[endX];
215
+ const ys = yChunks[startY];
216
+ const ye = yChunks[endY];
217
+ const zs = zChunks[startZ];
218
+ const ze = zChunks[endZ];
219
+ let min = xs;
220
+ let minKey = 'startX';
221
+ if (xe < min) {
222
+ min = xe;
223
+ minKey = 'endX';
224
+ }
225
+ if (ys < min) {
226
+ min = ys;
227
+ minKey = 'startY';
228
+ }
229
+ if (ye < min) {
230
+ min = ye;
231
+ minKey = 'endY';
232
+ }
233
+ if (zs < min) {
234
+ min = zs;
235
+ minKey = 'startZ';
236
+ }
237
+ if (ze < min) {
238
+ min = ze;
239
+ minKey = 'endZ';
240
+ }
241
+ switch (minKey) {
242
+ case 'startX':
243
+ startX++;
244
+ break;
245
+ case 'endX':
246
+ endX--;
247
+ break;
248
+ case 'startY':
249
+ startY++;
250
+ break;
251
+ case 'endY':
252
+ endY--;
253
+ break;
254
+ case 'startZ':
255
+ startZ++;
256
+ break;
257
+ case 'endZ':
258
+ endZ--;
259
+ break;
260
+ }
261
+ count -= min;
262
+ }
263
+ return {
264
+ min: [startX / scaleX + minX, startY / scaleY + minY, startZ / scaleZ + minZ],
265
+ max: [endX / scaleX + minX, endY / scaleY + minY, endZ / scaleZ + minZ],
266
+ };
267
+ }
268
+ // https://github.com/playcanvas/splat-transform/blob/main/src/lib/data-table/data-table.ts
269
+ export function mortonSort(splat) {
270
+ const result = new Uint32Array(splat.counts);
271
+ const xCol = splat.table[ColIdx.x];
272
+ const yCol = splat.table[ColIdx.y];
273
+ const zCol = splat.table[ColIdx.z];
274
+ for (let i = 0; i < result.length; ++i) {
275
+ result[i] = i;
276
+ }
277
+ const generate = (indices) => {
278
+ if (indices.length === 0) {
279
+ return;
280
+ }
281
+ // https://fgiesen.wordpress.com/2009/12/13/decoding-morton-codes/
282
+ const encodeMorton3 = (x, y, z) => {
283
+ const Part1By2 = (x) => {
284
+ x &= 0x000003ff;
285
+ x = (x ^ (x << 16)) & 0xff0000ff;
286
+ x = (x ^ (x << 8)) & 0x0300f00f;
287
+ x = (x ^ (x << 4)) & 0x030c30c3;
288
+ x = (x ^ (x << 2)) & 0x09249249;
289
+ return x;
290
+ };
291
+ return (Part1By2(z) << 2) + (Part1By2(y) << 1) + Part1By2(x);
292
+ };
293
+ let mx = Infinity;
294
+ let my = Infinity;
295
+ let mz = Infinity;
296
+ let Mx = -Infinity;
297
+ let My = -Infinity;
298
+ let Mz = -Infinity;
299
+ // calculate scene extents across all splats (using sort centers, because they're in world space)
300
+ for (let i = 0; i < indices.length; ++i) {
301
+ const ri = indices[i];
302
+ const x = xCol[ri];
303
+ const y = yCol[ri];
304
+ const z = zCol[ri];
305
+ if (x < mx) {
306
+ mx = x;
307
+ }
308
+ if (x > Mx) {
309
+ Mx = x;
310
+ }
311
+ if (y < my) {
312
+ my = y;
313
+ }
314
+ if (y > My) {
315
+ My = y;
316
+ }
317
+ if (z < mz) {
318
+ mz = z;
319
+ }
320
+ if (z > Mz) {
321
+ Mz = z;
322
+ }
323
+ }
324
+ const xlen = Mx - mx;
325
+ const ylen = My - my;
326
+ const zlen = Mz - mz;
327
+ if (!isFinite(xlen) || !isFinite(ylen) || !isFinite(zlen)) {
328
+ console.debug('invalid extents', xlen, ylen, zlen);
329
+ return;
330
+ }
331
+ // all points are identical
332
+ if (xlen === 0 && ylen === 0 && zlen === 0) {
333
+ return;
334
+ }
335
+ const xmul = xlen === 0 ? 0 : 1024 / xlen;
336
+ const ymul = ylen === 0 ? 0 : 1024 / ylen;
337
+ const zmul = zlen === 0 ? 0 : 1024 / zlen;
338
+ const morton = new Uint32Array(indices.length);
339
+ for (let i = 0; i < indices.length; ++i) {
340
+ const ri = indices[i];
341
+ const x = xCol[ri];
342
+ const y = yCol[ri];
343
+ const z = zCol[ri];
344
+ const ix = Math.min(1023, (x - mx) * xmul) >>> 0;
345
+ const iy = Math.min(1023, (y - my) * ymul) >>> 0;
346
+ const iz = Math.min(1023, (z - mz) * zmul) >>> 0;
347
+ morton[i] = encodeMorton3(ix, iy, iz);
348
+ }
349
+ // sort indices by morton code
350
+ const order = new Uint32Array(indices.length);
351
+ for (let i = 0; i < order.length; i++) {
352
+ order[i] = i;
353
+ }
354
+ order.sort((a, b) => morton[a] - morton[b]);
355
+ const tmpIndices = indices.slice();
356
+ for (let i = 0; i < indices.length; ++i) {
357
+ indices[i] = tmpIndices[order[i]];
358
+ }
359
+ // sort the largest buckets recursively
360
+ let start = 0;
361
+ let end = 1;
362
+ while (start < indices.length) {
363
+ while (end < indices.length && morton[order[end]] === morton[order[start]]) {
364
+ ++end;
365
+ }
366
+ if (end - start > 256) {
367
+ generate(indices.subarray(start, end));
368
+ }
369
+ start = end;
370
+ }
371
+ };
372
+ generate(result);
373
+ return result;
374
+ }
375
+ export function fastDeleteSplat(splat, indices) {
376
+ const { counts, table } = splat;
377
+ const map = new Uint32Array(counts - indices.length);
378
+ let write = 0;
379
+ let removeIdx = 0;
380
+ for (let read = 0; read < counts; read++) {
381
+ if (removeIdx < indices.length && read === indices[removeIdx]) {
382
+ removeIdx++;
383
+ continue;
384
+ }
385
+ map[write++] = read;
386
+ }
387
+ for (let i = 0; i < table.length; i++) {
388
+ const col = table[i];
389
+ for (let j = 0; j < map.length; j++) {
390
+ col[j] = col[map[j]];
391
+ }
392
+ }
393
+ splat.counts -= indices.length;
394
+ for (let i = 0; i < table.length; i++) {
395
+ table[i] = table[i].subarray(0, splat.counts);
396
+ }
397
+ }