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