@manycore/aholo-splat-transform 1.2.6

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 (88) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/README.md +33 -0
  3. package/bin/cli.js +118 -0
  4. package/dist/SplatData.d.ts +67 -0
  5. package/dist/SplatData.js +156 -0
  6. package/dist/constant.d.ts +3 -0
  7. package/dist/constant.js +13 -0
  8. package/dist/file/IFile.d.ts +5 -0
  9. package/dist/file/IFile.js +1 -0
  10. package/dist/file/index.d.ts +7 -0
  11. package/dist/file/index.js +6 -0
  12. package/dist/file/ksplat.d.ts +12 -0
  13. package/dist/file/ksplat.js +232 -0
  14. package/dist/file/lcc.d.ts +11 -0
  15. package/dist/file/lcc.js +157 -0
  16. package/dist/file/ply.d.ts +13 -0
  17. package/dist/file/ply.js +388 -0
  18. package/dist/file/sog.d.ts +80 -0
  19. package/dist/file/sog.js +504 -0
  20. package/dist/file/splat.d.ts +6 -0
  21. package/dist/file/splat.js +99 -0
  22. package/dist/file/spz.d.ts +8 -0
  23. package/dist/file/spz.js +400 -0
  24. package/dist/file/voxel.d.ts +37 -0
  25. package/dist/file/voxel.js +280 -0
  26. package/dist/index.d.ts +33 -0
  27. package/dist/index.js +54 -0
  28. package/dist/native/cpp/bin/linux/binding.node +0 -0
  29. package/dist/native/cpp/bin/windows/binding.node +0 -0
  30. package/dist/native/index.d.ts +54 -0
  31. package/dist/native/index.js +128 -0
  32. package/dist/tasks/AutoChunkLodTask.d.ts +13 -0
  33. package/dist/tasks/AutoChunkLodTask.js +117 -0
  34. package/dist/tasks/AutoLodTask.d.ts +10 -0
  35. package/dist/tasks/AutoLodTask.js +20 -0
  36. package/dist/tasks/BaseTask.d.ts +15 -0
  37. package/dist/tasks/BaseTask.js +5 -0
  38. package/dist/tasks/FlexLodTask.d.ts +12 -0
  39. package/dist/tasks/FlexLodTask.js +44 -0
  40. package/dist/tasks/ModifyTask.d.ts +9 -0
  41. package/dist/tasks/ModifyTask.js +156 -0
  42. package/dist/tasks/ReadTask.d.ts +8 -0
  43. package/dist/tasks/ReadTask.js +29 -0
  44. package/dist/tasks/SkeletonLodTask.d.ts +10 -0
  45. package/dist/tasks/SkeletonLodTask.js +156 -0
  46. package/dist/tasks/VoxelTask.d.ts +30 -0
  47. package/dist/tasks/VoxelTask.js +37 -0
  48. package/dist/tasks/WriteTask.d.ts +11 -0
  49. package/dist/tasks/WriteTask.js +70 -0
  50. package/dist/utils/BufferReader.d.ts +12 -0
  51. package/dist/utils/BufferReader.js +47 -0
  52. package/dist/utils/Logger.d.ts +11 -0
  53. package/dist/utils/Logger.js +38 -0
  54. package/dist/utils/StreamChunkDecoder.d.ts +16 -0
  55. package/dist/utils/StreamChunkDecoder.js +36 -0
  56. package/dist/utils/index.d.ts +27 -0
  57. package/dist/utils/index.js +101 -0
  58. package/dist/utils/k-means.d.ts +4 -0
  59. package/dist/utils/k-means.js +350 -0
  60. package/dist/utils/math.d.ts +46 -0
  61. package/dist/utils/math.js +351 -0
  62. package/dist/utils/quantize-1d.d.ts +4 -0
  63. package/dist/utils/quantize-1d.js +164 -0
  64. package/dist/utils/sh-rotate.d.ts +2 -0
  65. package/dist/utils/sh-rotate.js +175 -0
  66. package/dist/utils/splat.d.ts +20 -0
  67. package/dist/utils/splat.js +378 -0
  68. package/dist/utils/voxel/common.d.ts +162 -0
  69. package/dist/utils/voxel/common.js +1700 -0
  70. package/dist/utils/voxel/coplanar-merge.d.ts +63 -0
  71. package/dist/utils/voxel/coplanar-merge.js +819 -0
  72. package/dist/utils/voxel/gpu-dilation.d.ts +2 -0
  73. package/dist/utils/voxel/gpu-dilation.js +665 -0
  74. package/dist/utils/voxel/marching-cubes.d.ts +42 -0
  75. package/dist/utils/voxel/marching-cubes.js +1657 -0
  76. package/dist/utils/voxel/mesh.d.ts +3 -0
  77. package/dist/utils/voxel/mesh.js +130 -0
  78. package/dist/utils/voxel/nav.d.ts +29 -0
  79. package/dist/utils/voxel/nav.js +1043 -0
  80. package/dist/utils/voxel/postprocess.d.ts +23 -0
  81. package/dist/utils/voxel/postprocess.js +375 -0
  82. package/dist/utils/voxel/voxel-faces.d.ts +18 -0
  83. package/dist/utils/voxel/voxel-faces.js +663 -0
  84. package/dist/utils/voxel/voxelize.d.ts +33 -0
  85. package/dist/utils/voxel/voxelize.js +1193 -0
  86. package/dist/utils/webgpu.d.ts +8 -0
  87. package/dist/utils/webgpu.js +122 -0
  88. package/package.json +32 -0
@@ -0,0 +1,504 @@
1
+ import { unzipSync, zipSync } from 'fflate';
2
+ import { Buffer } from 'node:buffer';
3
+ import { decodeWebP, encodeWebP, WebPLosslessProfile } from '../native/index.js';
4
+ import { SH_C0, SH_MAPS, NUM_F_REST_TO_SH_DEGREE } from '../constant.js';
5
+ import { getOrCreateDevice, kmeans, logger, mortonSort, quantize1d, isUrl, extractFromRootDir, clamp } from '../utils/index.js';
6
+ const ZIP_MAGIC = 0x04034b50;
7
+ const PERM_TABLE = [
8
+ [0, 1, 2, 3],
9
+ [3, 1, 2, 0],
10
+ [1, 3, 2, 0],
11
+ [1, 2, 3, 0],
12
+ ];
13
+ const TEMP_ROT = new Float32Array(4);
14
+ function logTransform(value) {
15
+ return Math.sign(value) * Math.log(Math.abs(value) + 1);
16
+ }
17
+ ;
18
+ function writeTableData(table, indices, width, height, channels = 4) {
19
+ const data = new Uint8Array(width * height * channels);
20
+ const numColumns = table.length;
21
+ for (let i = 0; i < indices.length; ++i) {
22
+ const idx = indices[i];
23
+ data[i * channels + 0] = table[0][idx];
24
+ data[i * channels + 1] = numColumns > 1 ? table[1][idx] : 0;
25
+ data[i * channels + 2] = numColumns > 2 ? table[2][idx] : 0;
26
+ data[i * channels + 3] = numColumns > 3 ? table[3][idx] : 255;
27
+ }
28
+ return data;
29
+ }
30
+ function buildSHTableMap(shCoeffs) {
31
+ const result = [];
32
+ for (let i = 0; i < 3; i++) {
33
+ for (let j = 0; j < shCoeffs; j++) {
34
+ result.push(j * 3 + i);
35
+ }
36
+ }
37
+ return result;
38
+ }
39
+ export class SogFile {
40
+ iterations;
41
+ counts = 0;
42
+ shDegree = 0;
43
+ /**
44
+ * @internal
45
+ */
46
+ version;
47
+ /**
48
+ * @internal
49
+ */
50
+ meta;
51
+ /**
52
+ * @internal
53
+ */
54
+ refs = {};
55
+ cached;
56
+ constructor(iterations = 10) {
57
+ this.iterations = iterations;
58
+ }
59
+ async load(stream, contentLength) {
60
+ const buffer = new Uint8Array(contentLength);
61
+ const reader = stream.getReader();
62
+ let offset = 0;
63
+ while (true) {
64
+ const { done, value } = await reader.read();
65
+ if (done) {
66
+ break;
67
+ }
68
+ buffer.set(value, offset);
69
+ offset += value.length;
70
+ }
71
+ let metaBuffer = buffer;
72
+ const view = new DataView(buffer.buffer);
73
+ if (view.getUint32(0, true) === ZIP_MAGIC) {
74
+ this.refs = extractFromRootDir(unzipSync(buffer));
75
+ metaBuffer = this.refs['meta.json'];
76
+ if (!metaBuffer) {
77
+ throw new Error('SOG meta.json not found in the zip archive.');
78
+ }
79
+ }
80
+ this.meta = JSON.parse(new TextDecoder().decode(metaBuffer));
81
+ if (this.meta.version === undefined) {
82
+ const { means, quats, shN } = this.meta;
83
+ if (quats.encoding !== 'quaternion_packed') {
84
+ throw new Error('Unsupported quaternion encoding');
85
+ }
86
+ this.counts = means.shape[0];
87
+ this.shDegree = shN ? NUM_F_REST_TO_SH_DEGREE[shN.shape[1]] : 0;
88
+ this.version = 1;
89
+ }
90
+ else {
91
+ const { version, count, shN } = this.meta;
92
+ if (version !== 2) {
93
+ throw new Error(`Unsupported SOGS version: ${version}`);
94
+ }
95
+ this.counts = count;
96
+ this.shDegree = shN?.bands ?? 0;
97
+ this.version = version;
98
+ }
99
+ }
100
+ parse_v1(data, offset) {
101
+ const setFn = data.set.bind(data);
102
+ const setShFn = data.setShN.bind(data);
103
+ const { meta, counts, shDegree, cached } = this;
104
+ const [mean0, mean1, scale0, quat0, color0, centroids, labels] = cached.map(v => v.data);
105
+ const { means: { mins: [centerMinX, centerMinY, centerMinZ], maxs: [centerMaxX, centerMaxY, centerMaxZ] }, scales: { mins: [scaleMinX, scaleMinY, scaleMinZ], maxs: [scaleMaxX, scaleMaxY, scaleMaxZ] }, sh0: { mins: [colorMinR, colorMinG, colorMinB, colorMinA], maxs: [colorMaxR, colorMaxG, colorMaxB, colorMaxA], }, shN, } = meta;
106
+ const rangeX = (centerMaxX - centerMinX) / 65535;
107
+ const rangeY = (centerMaxY - centerMinY) / 65535;
108
+ const rangeZ = (centerMaxZ - centerMinZ) / 65535;
109
+ const SX_LUT = new Float32Array(256);
110
+ const SY_LUT = new Float32Array(256);
111
+ const SZ_LUT = new Float32Array(256);
112
+ const scaleRangeX = (scaleMaxX - scaleMinX) / 255;
113
+ const scaleRangeY = (scaleMaxY - scaleMinY) / 255;
114
+ const scaleRangeZ = (scaleMaxZ - scaleMinZ) / 255;
115
+ for (let i = 0; i < 256; i++) {
116
+ SX_LUT[i] = Math.exp(scaleMinX + scaleRangeX * i);
117
+ SY_LUT[i] = Math.exp(scaleMinY + scaleRangeY * i);
118
+ SZ_LUT[i] = Math.exp(scaleMinZ + scaleRangeZ * i);
119
+ }
120
+ const A_LUT = new Float32Array(256);
121
+ const colorRangeR = (colorMaxR - colorMinR) / 255;
122
+ const colorRangeG = (colorMaxG - colorMinG) / 255;
123
+ const colorRangeB = (colorMaxB - colorMinB) / 255;
124
+ const colorRangeA = (colorMaxA - colorMinA) / 255;
125
+ for (let i = 0; i < 256; i++) {
126
+ A_LUT[i] = 1.0 / (1.0 + Math.exp(-(colorMinA + colorRangeA * i)));
127
+ }
128
+ const single = {
129
+ x: 0, y: 0, z: 0,
130
+ sx: 0, sy: 0, sz: 0,
131
+ qx: 0, qy: 0, qz: 0, qw: 0,
132
+ r: 0, g: 0, b: 0, a: 0,
133
+ shN: [],
134
+ };
135
+ for (let i = 0; i < counts; i++) {
136
+ const i4 = i * 4;
137
+ const x = centerMinX + rangeX * (mean0[i4 + 0] + (mean1[i4 + 0] << 8));
138
+ const y = centerMinY + rangeY * (mean0[i4 + 1] + (mean1[i4 + 1] << 8));
139
+ const z = centerMinZ + rangeZ * (mean0[i4 + 2] + (mean1[i4 + 2] << 8));
140
+ single.x = Math.sign(x) * (Math.exp(Math.abs(x)) - 1);
141
+ single.y = Math.sign(y) * (Math.exp(Math.abs(y)) - 1);
142
+ single.z = Math.sign(z) * (Math.exp(Math.abs(z)) - 1);
143
+ single.sx = SX_LUT[scale0[i4 + 0]];
144
+ single.sy = SY_LUT[scale0[i4 + 1]];
145
+ single.sz = SZ_LUT[scale0[i4 + 2]];
146
+ TEMP_ROT[0] = (quat0[i4 + 0] / 255 - 0.5) * Math.SQRT2;
147
+ TEMP_ROT[1] = (quat0[i4 + 1] / 255 - 0.5) * Math.SQRT2;
148
+ TEMP_ROT[2] = (quat0[i4 + 2] / 255 - 0.5) * Math.SQRT2;
149
+ TEMP_ROT[3] = Math.sqrt(Math.max(0, 1.0 - TEMP_ROT[0] * TEMP_ROT[0] - TEMP_ROT[1] * TEMP_ROT[1] - TEMP_ROT[2] * TEMP_ROT[2]));
150
+ const PERM = PERM_TABLE[quat0[i4 + 3] - 252];
151
+ single.qx = TEMP_ROT[PERM[0]];
152
+ single.qy = TEMP_ROT[PERM[1]];
153
+ single.qz = TEMP_ROT[PERM[2]];
154
+ single.qw = TEMP_ROT[PERM[3]];
155
+ single.r = SH_C0 * (colorMinR + colorRangeR * color0[i4 + 0]) + 0.5;
156
+ single.g = SH_C0 * (colorMinG + colorRangeG * color0[i4 + 1]) + 0.5;
157
+ single.b = SH_C0 * (colorMinB + colorRangeB * color0[i4 + 2]) + 0.5;
158
+ single.a = A_LUT[color0[i4 + 3]];
159
+ setFn(offset + i, single);
160
+ }
161
+ if (shN) {
162
+ const centroidTexWidth = cached[5].width;
163
+ const { mins: min, maxs: max } = shN;
164
+ const range = (max - min) / 255;
165
+ const shCounts = SH_MAPS[shDegree];
166
+ const sh = new Array(shCounts);
167
+ const shCoeffs = shCounts / 3;
168
+ for (let i = 0; i < counts; i++) {
169
+ const i4 = i * 4;
170
+ const label = labels[i4] + (labels[i4 + 1] << 8);
171
+ const o = ((label >>> 6) * centroidTexWidth + (label & 63) * 15) * 4;
172
+ for (let j = 0; j < shCoeffs; j++) {
173
+ sh[j * 3 + 0] = min + range * centroids[o + j * 4 + 0];
174
+ sh[j * 3 + 1] = min + range * centroids[o + j * 4 + 1];
175
+ sh[j * 3 + 2] = min + range * centroids[o + j * 4 + 2];
176
+ }
177
+ setShFn(offset + i, sh);
178
+ }
179
+ }
180
+ }
181
+ parse_v2(data, offset) {
182
+ const setFn = data.set.bind(data);
183
+ const setShFn = data.setShN.bind(data);
184
+ const { meta, counts, shDegree, cached } = this;
185
+ const { means, scales, sh0, shN } = meta;
186
+ const { mins: [centerMinX, centerMinY, centerMinZ], maxs: [centerMaxX, centerMaxY, centerMaxZ], } = means;
187
+ const { codebook: scaleCodebook } = scales;
188
+ const { codebook: sh0Codebook } = sh0;
189
+ const [mean0, mean1, scale0, quat0, color0, centroids, labels] = cached.map(img => img.data);
190
+ const rangeX = (centerMaxX - centerMinX) / 65535;
191
+ const rangeY = (centerMaxY - centerMinY) / 65535;
192
+ const rangeZ = (centerMaxZ - centerMinZ) / 65535;
193
+ const SCALE_LUT = scaleCodebook.map(v => Math.exp(v));
194
+ const single = {
195
+ x: 0, y: 0, z: 0,
196
+ sx: 0, sy: 0, sz: 0,
197
+ qx: 0, qy: 0, qz: 0, qw: 0,
198
+ r: 0, g: 0, b: 0, a: 0,
199
+ shN: [],
200
+ };
201
+ for (let i = 0; i < counts; i++) {
202
+ const i4 = i * 4;
203
+ const x = centerMinX + rangeX * (mean0[i4 + 0] + (mean1[i4 + 0] << 8));
204
+ const y = centerMinY + rangeY * (mean0[i4 + 1] + (mean1[i4 + 1] << 8));
205
+ const z = centerMinZ + rangeZ * (mean0[i4 + 2] + (mean1[i4 + 2] << 8));
206
+ single.x = Math.sign(x) * (Math.exp(Math.abs(x)) - 1);
207
+ single.y = Math.sign(y) * (Math.exp(Math.abs(y)) - 1);
208
+ single.z = Math.sign(z) * (Math.exp(Math.abs(z)) - 1);
209
+ single.sx = SCALE_LUT[scale0[i4 + 0]];
210
+ single.sy = SCALE_LUT[scale0[i4 + 1]];
211
+ single.sz = SCALE_LUT[scale0[i4 + 2]];
212
+ TEMP_ROT[0] = (quat0[i4 + 0] / 255 - 0.5) * Math.SQRT2;
213
+ TEMP_ROT[1] = (quat0[i4 + 1] / 255 - 0.5) * Math.SQRT2;
214
+ TEMP_ROT[2] = (quat0[i4 + 2] / 255 - 0.5) * Math.SQRT2;
215
+ TEMP_ROT[3] = Math.sqrt(Math.max(0, 1.0 - TEMP_ROT[0] * TEMP_ROT[0] - TEMP_ROT[1] * TEMP_ROT[1] - TEMP_ROT[2] * TEMP_ROT[2]));
216
+ const PERM = PERM_TABLE[quat0[i4 + 3] - 252];
217
+ single.qx = TEMP_ROT[PERM[0]];
218
+ single.qy = TEMP_ROT[PERM[1]];
219
+ single.qz = TEMP_ROT[PERM[2]];
220
+ single.qw = TEMP_ROT[PERM[3]];
221
+ single.r = SH_C0 * sh0Codebook[color0[i4 + 0]] + 0.5;
222
+ single.g = SH_C0 * sh0Codebook[color0[i4 + 1]] + 0.5;
223
+ single.b = SH_C0 * sh0Codebook[color0[i4 + 2]] + 0.5;
224
+ single.a = color0[i4 + 3] / 255;
225
+ setFn(offset + i, single);
226
+ }
227
+ if (shN) {
228
+ const { codebook } = shN;
229
+ const shCounts = SH_MAPS[shDegree];
230
+ const shCoeffs = shCounts / 3;
231
+ const offsetItemSize = shCoeffs * 4;
232
+ const sh = new Array(shCounts);
233
+ for (let i = 0; i < counts; i++) {
234
+ const i4 = i * 4;
235
+ const o = (labels[i4 + 0] + (labels[i4 + 1] << 8)) * offsetItemSize;
236
+ for (let j = 0; j < shCoeffs; j++) {
237
+ sh[j * 3] = codebook[centroids[o + j * 4 + 0]];
238
+ sh[j * 3 + 1] = codebook[centroids[o + j * 4 + 1]];
239
+ sh[j * 3 + 2] = codebook[centroids[o + j * 4 + 2]];
240
+ }
241
+ setShFn(offset + i, sh);
242
+ }
243
+ }
244
+ }
245
+ async loadTexture(path) {
246
+ let buffer = this.refs[path];
247
+ if (!buffer) {
248
+ if (isUrl(path)) {
249
+ buffer = await fetch(path)
250
+ .then(res => res.arrayBuffer())
251
+ .then(buf => new Uint8Array(buf));
252
+ }
253
+ }
254
+ if (!buffer) {
255
+ throw new Error(`Cannot load texture: ${path}`);
256
+ }
257
+ const { data, width, height } = decodeWebP(buffer);
258
+ return {
259
+ data: new Uint8Array(data),
260
+ width,
261
+ height,
262
+ };
263
+ }
264
+ async read(stream, contentLength, data) {
265
+ await this.load(stream, contentLength);
266
+ const BlockOffset = await data.initBlock(this.counts, this.shDegree);
267
+ const { means, scales, quats, sh0, shN } = this.meta;
268
+ this.cached = await Promise.all([
269
+ means.files[0], means.files[1],
270
+ scales.files[0], quats.files[0],
271
+ sh0.files[0], shN?.files[0], shN?.files[1],
272
+ ].filter(path => !!path).map(path => this.loadTexture(path)));
273
+ if (this.version === 1) {
274
+ this.parse_v1(data, BlockOffset);
275
+ }
276
+ else if (this.version === 2) {
277
+ this.parse_v2(data, BlockOffset);
278
+ }
279
+ else {
280
+ throw new Error(`Unsupported SOG version: ${this.version}`);
281
+ }
282
+ data.finishBlock();
283
+ }
284
+ async write(stream, data, indices = mortonSort(data)) {
285
+ const { counts, shDegree, shCounts, table } = data;
286
+ const width = Math.ceil(Math.sqrt(counts) / 4) * 4;
287
+ const height = Math.ceil(counts / width / 4) * 4;
288
+ const channels = 4;
289
+ const single = {
290
+ x: 0, y: 0, z: 0,
291
+ sx: 0, sy: 0, sz: 0,
292
+ qx: 0, qy: 0, qz: 0, qw: 0,
293
+ r: 0, g: 0, b: 0, a: 0,
294
+ shN: new Array(shCounts),
295
+ };
296
+ const webPProfile = new WebPLosslessProfile();
297
+ const output = {};
298
+ const meta = {
299
+ version: 2,
300
+ count: counts,
301
+ means: {
302
+ mins: [],
303
+ maxs: [],
304
+ files: [
305
+ 'means_l.webp',
306
+ 'means_u.webp'
307
+ ]
308
+ },
309
+ scales: {
310
+ codebook: [],
311
+ files: ['scales.webp']
312
+ },
313
+ quats: {
314
+ files: ['quats.webp']
315
+ },
316
+ sh0: {
317
+ codebook: [],
318
+ files: ['sh0.webp']
319
+ }
320
+ };
321
+ // means
322
+ {
323
+ logger.time('SOG encoding means');
324
+ const xCol = table[0 /* ColIdx.x */];
325
+ const yCol = table[1 /* ColIdx.y */];
326
+ const zCol = table[2 /* ColIdx.z */];
327
+ // calculate minmax & transform
328
+ let minX = Infinity;
329
+ let minY = Infinity;
330
+ let minZ = Infinity;
331
+ let maxX = -Infinity;
332
+ let maxY = -Infinity;
333
+ let maxZ = -Infinity;
334
+ for (let i = 0; i < counts; i++) {
335
+ const idx = indices[i];
336
+ const x = xCol[idx];
337
+ const y = yCol[idx];
338
+ const z = zCol[idx];
339
+ if (x < minX) {
340
+ minX = x;
341
+ }
342
+ if (x > maxX) {
343
+ maxX = x;
344
+ }
345
+ if (y < minY) {
346
+ minY = y;
347
+ }
348
+ if (y > maxY) {
349
+ maxY = y;
350
+ }
351
+ if (z < minZ) {
352
+ minZ = z;
353
+ }
354
+ if (z > maxZ) {
355
+ maxZ = z;
356
+ }
357
+ }
358
+ minX = logTransform(minX);
359
+ minY = logTransform(minY);
360
+ minZ = logTransform(minZ);
361
+ maxX = logTransform(maxX);
362
+ maxY = logTransform(maxY);
363
+ maxZ = logTransform(maxZ);
364
+ const scaleX = 65535 / Math.max(maxX - minX, 1e-9);
365
+ const scaleY = 65535 / Math.max(maxY - minY, 1e-9);
366
+ const scaleZ = 65535 / Math.max(maxZ - minZ, 1e-9);
367
+ // encode means
368
+ const meansL = new Uint8Array(width * height * channels).fill(0xff);
369
+ const meansU = new Uint8Array(width * height * channels).fill(0xff);
370
+ for (let i = 0; i < indices.length; i++) {
371
+ const idx = indices[i];
372
+ const x = (logTransform(xCol[idx]) - minX) * scaleX;
373
+ const y = (logTransform(yCol[idx]) - minY) * scaleY;
374
+ const z = (logTransform(zCol[idx]) - minZ) * scaleZ;
375
+ meansL[i * 4 + 0] = x & 0xff;
376
+ meansL[i * 4 + 1] = y & 0xff;
377
+ meansL[i * 4 + 2] = z & 0xff;
378
+ meansU[i * 4 + 0] = (x >> 8) & 0xff;
379
+ meansU[i * 4 + 1] = (y >> 8) & 0xff;
380
+ meansU[i * 4 + 2] = (z >> 8) & 0xff;
381
+ }
382
+ output['means_l.webp'] = encodeWebP(meansL, width, height, webPProfile);
383
+ output['means_u.webp'] = encodeWebP(meansU, width, height, webPProfile);
384
+ meta.means.mins = [minX, minY, minZ];
385
+ meta.means.maxs = [maxX, maxY, maxZ];
386
+ logger.timeEnd('SOG encoding means');
387
+ }
388
+ // quaternions
389
+ {
390
+ logger.time('SOG encoding quaternions');
391
+ const quats = new Uint8Array(width * height * channels);
392
+ const q = [0, 0, 0, 0];
393
+ for (let i = 0; i < indices.length; ++i) {
394
+ data.getQuat(indices[i], single);
395
+ q[0] = single.qw;
396
+ q[1] = single.qx;
397
+ q[2] = single.qy;
398
+ q[3] = single.qz;
399
+ const l = Math.sqrt(q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]);
400
+ // normalize
401
+ q.forEach((v, j) => {
402
+ q[j] = v / l;
403
+ });
404
+ // find max component
405
+ const maxComp = q.reduce((v, _, i) => (Math.abs(q[i]) > Math.abs(q[v]) ? i : v), 0);
406
+ // invert if max component is negative
407
+ if (q[maxComp] < 0) {
408
+ q.forEach((v, j) => {
409
+ q[j] *= -1;
410
+ });
411
+ }
412
+ // scale by sqrt(2) to fit in [-1, 1] range
413
+ q.forEach((v, j) => q[j] *= Math.SQRT2);
414
+ const idx = [
415
+ [1, 2, 3],
416
+ [0, 2, 3],
417
+ [0, 1, 3],
418
+ [0, 1, 2]
419
+ ][maxComp];
420
+ quats[i * 4] = (q[idx[0]] * 0.5 + 0.5) * 255;
421
+ quats[i * 4 + 1] = (q[idx[1]] * 0.5 + 0.5) * 255;
422
+ quats[i * 4 + 2] = (q[idx[2]] * 0.5 + 0.5) * 255;
423
+ quats[i * 4 + 3] = 252 + maxComp;
424
+ }
425
+ output['quats.webp'] = encodeWebP(quats, width, height, webPProfile);
426
+ logger.timeEnd('SOG encoding quaternions');
427
+ }
428
+ // scales
429
+ {
430
+ logger.time('SOG encoding scales');
431
+ const scaleData = quantize1d([table[3 /* ColIdx.sx */], table[4 /* ColIdx.sy */], table[5 /* ColIdx.sz */]], undefined, undefined, Math.log);
432
+ const tableData = writeTableData(scaleData.labels, indices, width, height, channels);
433
+ output['scales.webp'] = encodeWebP(tableData, width, height, webPProfile);
434
+ meta.scales.codebook = Array.from(scaleData.centroids);
435
+ logger.timeEnd('SOG encoding scales');
436
+ }
437
+ // colors
438
+ {
439
+ logger.time('SOG encoding colors');
440
+ const colorData = quantize1d([table[10 /* ColIdx.r */], table[11 /* ColIdx.g */], table[12 /* ColIdx.b */]], undefined, undefined, v => (v - 0.5) / SH_C0);
441
+ const aCol = table[13 /* ColIdx.a */];
442
+ const opacityData = new Uint8Array(aCol.length);
443
+ for (let i = 0; i < counts; ++i) {
444
+ opacityData[i] = clamp(aCol[i] * 255, 0, 255);
445
+ }
446
+ colorData.labels.push(opacityData);
447
+ const tableData = writeTableData(colorData.labels, indices, width, height, channels);
448
+ output['sh0.webp'] = encodeWebP(tableData, width, height, webPProfile);
449
+ meta.sh0.codebook = Array.from(colorData.centroids);
450
+ logger.timeEnd('SOG encoding colors');
451
+ }
452
+ // SH
453
+ if (shDegree > 0) {
454
+ logger.time(`SOG encoding SH${shDegree}`);
455
+ const shCoeffs = shCounts / 3;
456
+ const shDataTable = [];
457
+ for (const i of buildSHTableMap(shCoeffs)) {
458
+ shDataTable.push(table[14 /* ColIdx.shOffset */ + i]);
459
+ }
460
+ const paletteSize = Math.min(64, 2 ** Math.floor(Math.log2(indices.length / 1024))) * 1024;
461
+ const device = await getOrCreateDevice();
462
+ logger.info(`SOG SH${shDegree} k-means with clusters=${paletteSize} iterations=${this.iterations}`);
463
+ logger.time(`SOG SH${shDegree} k-means`);
464
+ const { centroids, labels } = await kmeans(shDataTable, paletteSize, this.iterations, device);
465
+ logger.timeEnd(`SOG SH${shDegree} k-means`);
466
+ const codebook = quantize1d(centroids);
467
+ // write centroids
468
+ const centroidsBuf = new Uint8Array(64 * shCoeffs * Math.ceil(centroids[0].length / 64) * channels).fill(0xff);
469
+ const centroidsRow = [];
470
+ for (let i = 0; i < centroids[0].length; ++i) {
471
+ codebook.labels.forEach((column, index) => {
472
+ centroidsRow[index] = column[i];
473
+ });
474
+ for (let j = 0; j < shCoeffs; ++j) {
475
+ centroidsBuf[i * shCoeffs * 4 + j * 4 + 0] = centroidsRow[shCoeffs * 0 + j];
476
+ centroidsBuf[i * shCoeffs * 4 + j * 4 + 1] = centroidsRow[shCoeffs * 1 + j];
477
+ centroidsBuf[i * shCoeffs * 4 + j * 4 + 2] = centroidsRow[shCoeffs * 2 + j];
478
+ }
479
+ }
480
+ output['shN_centroids.webp'] = encodeWebP(centroidsBuf, 64 * shCoeffs, Math.ceil(centroids[0].length / 64), webPProfile);
481
+ // write labels
482
+ const labelsBuf = new Uint8Array(width * height * channels).fill(0xff);
483
+ for (let i = 0; i < indices.length; ++i) {
484
+ const label = labels[indices[i]];
485
+ labelsBuf[i * 4 + 0] = label & 0xff;
486
+ labelsBuf[i * 4 + 1] = (label >> 8) & 0xff;
487
+ }
488
+ output['shN_labels.webp'] = encodeWebP(labelsBuf, width, height, webPProfile);
489
+ meta.shN = {
490
+ count: paletteSize,
491
+ bands: shDegree,
492
+ codebook: Array.from(codebook.centroids),
493
+ files: [
494
+ 'shN_centroids.webp',
495
+ 'shN_labels.webp'
496
+ ]
497
+ };
498
+ logger.timeEnd(`SOG encoding SH${shDegree}`);
499
+ }
500
+ output['meta.json'] = Buffer.from(JSON.stringify(meta), 'utf-8');
501
+ const result = zipSync(output);
502
+ await stream.getWriter().write(result);
503
+ }
504
+ }
@@ -0,0 +1,6 @@
1
+ import { SplatData } from '../SplatData.js';
2
+ import { IFile } from './IFile.js';
3
+ export declare class SplatFile implements IFile {
4
+ read(stream: ReadableStream<Uint8Array>, contentLength: number, data: SplatData): Promise<void>;
5
+ write(stream: WritableStream<Uint8Array>, data: SplatData, indices?: Uint32Array): Promise<void>;
6
+ }
@@ -0,0 +1,99 @@
1
+ import { clamp, StreamChunkDecoder, BufferReader, mortonSort } from '../utils/index.js';
2
+ const ItemSize = 32;
3
+ export class SplatFile {
4
+ async read(stream, contentLength, data) {
5
+ const setFn = data.set.bind(data);
6
+ const counts = Math.floor(contentLength / ItemSize);
7
+ const BlockOffset = await data.initBlock(counts, 0);
8
+ const reader = new BufferReader();
9
+ const decoder = new StreamChunkDecoder(reader);
10
+ const single = {
11
+ x: 0, y: 0, z: 0,
12
+ sx: 0, sy: 0, sz: 0,
13
+ qx: 0, qy: 0, qz: 0, qw: 0,
14
+ r: 0, g: 0, b: 0, a: 0,
15
+ shN: [],
16
+ };
17
+ decoder.setDecoders([
18
+ {
19
+ init: () => [counts, ItemSize],
20
+ decode: (offset, counts, buffer) => {
21
+ offset += BlockOffset;
22
+ const f32Array = new Float32Array(buffer.buffer);
23
+ let o = 0;
24
+ for (let i = 0; i < counts; i++) {
25
+ o = i * 8;
26
+ single.x = f32Array[o];
27
+ single.y = f32Array[o + 1];
28
+ single.z = f32Array[o + 2];
29
+ single.sx = f32Array[o + 3];
30
+ single.sy = f32Array[o + 4];
31
+ single.sz = f32Array[o + 5];
32
+ o = i * 32;
33
+ single.r = buffer[o + 24] / 255;
34
+ single.g = buffer[o + 25] / 255;
35
+ single.b = buffer[o + 26] / 255;
36
+ single.a = buffer[o + 27] / 255;
37
+ single.qw = (buffer[o + 28] - 128) / 128;
38
+ single.qx = (buffer[o + 29] - 128) / 128;
39
+ single.qy = (buffer[o + 30] - 128) / 128;
40
+ single.qz = (buffer[o + 31] - 128) / 128;
41
+ setFn(offset + i, single);
42
+ }
43
+ },
44
+ },
45
+ ]);
46
+ const source = stream.getReader();
47
+ while (true) {
48
+ const { done, value } = await source.read();
49
+ if (done) {
50
+ break;
51
+ }
52
+ reader.write(value);
53
+ decoder.flush();
54
+ }
55
+ data.finishBlock();
56
+ }
57
+ async write(stream, data, indices = mortonSort(data)) {
58
+ const writer = stream.getWriter();
59
+ const chunkSize = 2048;
60
+ const chunkCounts = Math.ceil(data.counts / chunkSize);
61
+ const single = {
62
+ x: 0, y: 0, z: 0,
63
+ sx: 0, sy: 0, sz: 0,
64
+ qx: 0, qy: 0, qz: 0, qw: 0,
65
+ r: 0, g: 0, b: 0, a: 0,
66
+ shN: [],
67
+ };
68
+ for (let i = 0; i < chunkCounts; i++) {
69
+ if (writer.desiredSize <= 0) {
70
+ await writer.ready;
71
+ }
72
+ const currentChunkSize = Math.min(chunkSize, data.counts - i * chunkSize);
73
+ const chunk = new Uint8Array(currentChunkSize * ItemSize);
74
+ const dataView = new DataView(chunk.buffer);
75
+ const offset = i * chunkSize;
76
+ for (let j = 0; j < currentChunkSize; j++) {
77
+ data.get(indices[offset + j], single);
78
+ const o = j * ItemSize;
79
+ dataView.setFloat32(o, single.x, true);
80
+ dataView.setFloat32(o + 4, single.y, true);
81
+ dataView.setFloat32(o + 8, single.z, true);
82
+ dataView.setFloat32(o + 12, single.sx, true);
83
+ dataView.setFloat32(o + 16, single.sy, true);
84
+ dataView.setFloat32(o + 20, single.sz, true);
85
+ dataView.setUint8(o + 24, clamp(Math.round(single.r * 255), 0, 255));
86
+ dataView.setUint8(o + 25, clamp(Math.round(single.g * 255), 0, 255));
87
+ dataView.setUint8(o + 26, clamp(Math.round(single.b * 255), 0, 255));
88
+ dataView.setUint8(o + 27, clamp(Math.round(single.a * 255), 0, 255));
89
+ dataView.setUint8(o + 28, clamp(Math.round(single.qw * 128 + 128), 0, 255));
90
+ dataView.setUint8(o + 29, clamp(Math.round(single.qx * 128 + 128), 0, 255));
91
+ dataView.setUint8(o + 30, clamp(Math.round(single.qy * 128 + 128), 0, 255));
92
+ dataView.setUint8(o + 31, clamp(Math.round(single.qz * 128 + 128), 0, 255));
93
+ }
94
+ writer.write(chunk);
95
+ await Promise.resolve();
96
+ }
97
+ await writer.close();
98
+ }
99
+ }
@@ -0,0 +1,8 @@
1
+ import { SplatData } from '../SplatData.js';
2
+ import { IFile } from './IFile.js';
3
+ export declare class SpzFile implements IFile {
4
+ readonly compressLevel: number;
5
+ constructor(compressLevel: number);
6
+ read(stream: ReadableStream<Uint8Array>, _contentLength: number, data: SplatData): Promise<void>;
7
+ write(writeStream: WritableStream<Uint8Array>, data: SplatData, indices?: Uint32Array): Promise<void>;
8
+ }