@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
package/dist/file/esz.js CHANGED
@@ -1,322 +1,337 @@
1
- import { unzipSync, zipSync } from 'fflate';
2
- import { clamp, extractFromRootDir, isUrl, logger, mortonSort } from '../utils/index.js';
3
- import { decodeWebP, encodeWebP, WebPLosslessProfile } from '../native/index.js';
4
- import { SH_C0, SH_MAPS } from '../constant.js';
5
- const TEMP_ROT = new Array(4);
6
- const PERM_TABLE = [
7
- [0, 1, 2, 3],
8
- [3, 1, 2, 0],
9
- [1, 3, 2, 0],
10
- [1, 2, 3, 0],
11
- ];
12
- const COLOR_SCALE = SH_C0 / 0.15;
13
- const SH_SCALE1 = 1 << 3;
14
- const SH_SCALE2 = 1 << 4;
15
- function logTransform(value) {
16
- return Math.sign(value) * Math.log(Math.abs(value) + 1);
17
- }
18
- ;
19
- export class EszFile {
20
- constructor() {
21
- this.counts = 0;
22
- this.shDegree = 0;
23
- /**
24
- * @internal
25
- */
26
- this.refs = {};
27
- }
28
- async load(stream, contentLength) {
29
- const buffer = new Uint8Array(contentLength);
30
- const reader = stream.getReader();
31
- let offset = 0;
32
- while (true) {
33
- const { done, value } = await reader.read();
34
- if (done) {
35
- break;
36
- }
37
- buffer.set(value, offset);
38
- offset += value.length;
39
- }
40
- this.refs = extractFromRootDir(unzipSync(buffer));
41
- const metaBuffer = this.refs['meta.json'];
42
- if (!metaBuffer) {
43
- throw new Error('SOG meta.json not found in the zip archive.');
44
- }
45
- const meta = this.meta = JSON.parse(new TextDecoder().decode(metaBuffer));
46
- this.version = meta.version;
47
- this.counts = meta.counts;
48
- this.shDegree = meta.shDegree;
49
- }
50
- async loadTexture(path) {
51
- let buffer = this.refs[path];
52
- if (!buffer) {
53
- if (isUrl(path)) {
54
- buffer = await fetch(path)
55
- .then(res => res.arrayBuffer())
56
- .then(buf => new Uint8Array(buf));
57
- }
58
- }
59
- if (!buffer) {
60
- throw new Error(`Cannot load texture: ${path}`);
61
- }
62
- const { data, width, height } = decodeWebP(buffer);
63
- return {
64
- data: new Uint8Array(data),
65
- width,
66
- height,
67
- };
68
- }
69
- async read(stream, contentLength, data) {
70
- await this.load(stream, contentLength);
71
- const offset = await data.initBlock(this.counts, this.shDegree);
72
- const { resources } = this.meta;
73
- this.cached = await Promise.all([
74
- resources.means_l, resources.means_u,
75
- resources.scales, resources.quats,
76
- resources.sh0, resources.shN,
77
- ].filter(path => !!path).map(path => this.loadTexture(path)));
78
- const setFn = data.set.bind(data);
79
- const setShFn = data.setShN.bind(data);
80
- const SCALE_LUT = new Float32Array(256);
81
- for (let i = 0; i < 256; i++) {
82
- SCALE_LUT[i] = Math.exp(i / 16 - 10);
83
- }
84
- const COLOR_LUT = new Float32Array(256);
85
- for (let i = 0; i < 256; i++) {
86
- COLOR_LUT[i] = (i / 255 - 0.5) * COLOR_SCALE + 0.5;
87
- }
88
- const { meta: { box }, counts, shDegree, cached } = this;
89
- const [means_l, means_u, scales, quats, color, shN] = cached.map(v => v.data);
90
- const { min: [centerMinX, centerMinY, centerMinZ], max: [centerMaxX, centerMaxY, centerMaxZ] } = box;
91
- const rangeX = (centerMaxX - centerMinX) / 65535;
92
- const rangeY = (centerMaxY - centerMinY) / 65535;
93
- const rangeZ = (centerMaxZ - centerMinZ) / 65535;
94
- const single = {
95
- x: 0, y: 0, z: 0,
96
- sx: 0, sy: 0, sz: 0,
97
- qx: 0, qy: 0, qz: 0, qw: 0,
98
- r: 0, g: 0, b: 0, a: 0,
99
- shN: [],
100
- };
101
- for (let i = 0; i < counts; i++) {
102
- const i4 = i * 4;
103
- const x = centerMinX + rangeX * (means_l[i4 + 0] + (means_u[i4 + 0] << 8));
104
- const y = centerMinY + rangeY * (means_l[i4 + 1] + (means_u[i4 + 1] << 8));
105
- const z = centerMinZ + rangeZ * (means_l[i4 + 2] + (means_u[i4 + 2] << 8));
106
- single.x = Math.sign(x) * (Math.exp(Math.abs(x)) - 1);
107
- single.y = Math.sign(y) * (Math.exp(Math.abs(y)) - 1);
108
- single.z = Math.sign(z) * (Math.exp(Math.abs(z)) - 1);
109
- single.sx = SCALE_LUT[scales[i4 + 0]];
110
- single.sy = SCALE_LUT[scales[i4 + 1]];
111
- single.sz = SCALE_LUT[scales[i4 + 2]];
112
- TEMP_ROT[0] = (quats[i4 + 0] / 255 - 0.5) * Math.SQRT2;
113
- TEMP_ROT[1] = (quats[i4 + 1] / 255 - 0.5) * Math.SQRT2;
114
- TEMP_ROT[2] = (quats[i4 + 2] / 255 - 0.5) * Math.SQRT2;
115
- 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]));
116
- const PERM = PERM_TABLE[quats[i4 + 3] - 252];
117
- single.qx = TEMP_ROT[PERM[0]];
118
- single.qy = TEMP_ROT[PERM[1]];
119
- single.qz = TEMP_ROT[PERM[2]];
120
- single.qw = TEMP_ROT[PERM[3]];
121
- single.r = COLOR_LUT[color[i4 + 0]];
122
- single.g = COLOR_LUT[color[i4 + 1]];
123
- single.b = COLOR_LUT[color[i4 + 2]];
124
- single.a = color[i4 + 3] / 255;
125
- setFn(offset + i, single);
126
- }
127
- if (shN) {
128
- const shCounts = SH_MAPS[shDegree];
129
- const shCoeffs = shCounts / 3;
130
- const sh = new Array(shCounts);
131
- for (let i = 0; i < counts; i++) {
132
- const o = i * shCounts;
133
- for (let j = 0; j < shCoeffs; j++) {
134
- sh[o + j * 3 + 0] = (shN[(i * shCoeffs + j) * 4 + 0] - 128) / 128;
135
- sh[o + j * 3 + 1] = (shN[(i * shCoeffs + j) * 4 + 1] - 128) / 128;
136
- sh[o + j * 3 + 2] = (shN[(i * shCoeffs + j) * 4 + 2] - 128) / 128;
137
- }
138
- setShFn(offset + i, sh);
139
- }
140
- }
141
- data.finishBlock();
142
- }
143
- async write(stream, data, indices = mortonSort(data)) {
144
- const { counts, shDegree, shCounts, table } = data;
145
- const width = Math.ceil(Math.sqrt(counts) / 4) * 4;
146
- const height = Math.ceil(counts / width / 4) * 4;
147
- const webPProfile = new WebPLosslessProfile();
148
- const output = {};
149
- const meta = {
150
- version: 1,
151
- counts,
152
- shDegree,
153
- box: {
154
- min: [Infinity, Infinity, Infinity],
155
- max: [-Infinity, -Infinity, -Infinity],
156
- },
157
- resources: {
158
- means_l: 'means_l.webp', means_u: 'means_u.webp',
159
- scales: 'scales.webp', quats: 'quats.webp', sh0: 'sh0.webp',
160
- },
161
- };
162
- const xCol = table[0 /* ColIdx.x */];
163
- const yCol = table[1 /* ColIdx.y */];
164
- const zCol = table[2 /* ColIdx.z */];
165
- // calculate minmax & transform
166
- let minX = Infinity;
167
- let minY = Infinity;
168
- let minZ = Infinity;
169
- let maxX = -Infinity;
170
- let maxY = -Infinity;
171
- let maxZ = -Infinity;
172
- for (let i = 0; i < counts; i++) {
173
- const idx = indices[i];
174
- const x = xCol[idx];
175
- const y = yCol[idx];
176
- const z = zCol[idx];
177
- if (x < minX) {
178
- minX = x;
179
- }
180
- if (x > maxX) {
181
- maxX = x;
182
- }
183
- if (y < minY) {
184
- minY = y;
185
- }
186
- if (y > maxY) {
187
- maxY = y;
188
- }
189
- if (z < minZ) {
190
- minZ = z;
191
- }
192
- if (z > maxZ) {
193
- maxZ = z;
194
- }
195
- }
196
- meta.box.min = [minX, minY, minZ];
197
- meta.box.max = [maxX, maxY, maxZ];
198
- {
199
- logger.time('ESZ encoding means');
200
- minX = logTransform(minX);
201
- minY = logTransform(minY);
202
- minZ = logTransform(minZ);
203
- maxX = logTransform(maxX);
204
- maxY = logTransform(maxY);
205
- maxZ = logTransform(maxZ);
206
- const scaleX = 65535 / Math.max(maxX - minX, 1e-9);
207
- const scaleY = 65535 / Math.max(maxY - minY, 1e-9);
208
- const scaleZ = 65535 / Math.max(maxZ - minZ, 1e-9);
209
- const meansL = new Uint8Array(width * height * 4).fill(0xff);
210
- const meansU = new Uint8Array(width * height * 4).fill(0xff);
211
- for (let i = 0; i < indices.length; i++) {
212
- const idx = indices[i];
213
- const x = (logTransform(xCol[idx]) - minX) * scaleX;
214
- const y = (logTransform(yCol[idx]) - minY) * scaleY;
215
- const z = (logTransform(zCol[idx]) - minZ) * scaleZ;
216
- meansL[i * 4 + 0] = x & 0xff;
217
- meansL[i * 4 + 1] = y & 0xff;
218
- meansL[i * 4 + 2] = z & 0xff;
219
- meansU[i * 4 + 0] = (x >> 8) & 0xff;
220
- meansU[i * 4 + 1] = (y >> 8) & 0xff;
221
- meansU[i * 4 + 2] = (z >> 8) & 0xff;
222
- }
223
- output['means_l.webp'] = encodeWebP(meansL, width, height, webPProfile);
224
- output['means_u.webp'] = encodeWebP(meansU, width, height, webPProfile);
225
- logger.timeEnd('ESZ encoding means');
226
- }
227
- {
228
- logger.time('ESZ encoding scales');
229
- const sxCol = table[3 /* ColIdx.sx */];
230
- const syCol = table[4 /* ColIdx.sy */];
231
- const szCol = table[5 /* ColIdx.sz */];
232
- const scales = new Uint8Array(width * height * 4).fill(0xff);
233
- for (let i = 0; i < counts; i++) {
234
- const idx = indices[i];
235
- scales[i * 4 + 0] = clamp(Math.round((Math.log(sxCol[idx]) + 10) * 16), 0, 255);
236
- scales[i * 4 + 1] = clamp(Math.round((Math.log(syCol[idx]) + 10) * 16), 0, 255);
237
- scales[i * 4 + 2] = clamp(Math.round((Math.log(szCol[idx]) + 10) * 16), 0, 255);
238
- }
239
- output['scales.webp'] = encodeWebP(scales, width, height, webPProfile);
240
- logger.timeEnd('ESZ encoding scales');
241
- }
242
- {
243
- logger.time('ESZ encoding quats');
244
- const qxCol = table[6 /* ColIdx.qx */];
245
- const qyCol = table[7 /* ColIdx.qy */];
246
- const qzCol = table[8 /* ColIdx.qz */];
247
- const qwCol = table[9 /* ColIdx.qw */];
248
- const quats = new Uint8Array(width * height * 4);
249
- for (let i = 0; i < counts; i++) {
250
- const idx = indices[i];
251
- TEMP_ROT[0] = qwCol[idx];
252
- TEMP_ROT[1] = qxCol[idx];
253
- TEMP_ROT[2] = qyCol[idx];
254
- TEMP_ROT[3] = qzCol[idx];
255
- const l = Math.sqrt(TEMP_ROT[0] * TEMP_ROT[0] + TEMP_ROT[1] * TEMP_ROT[1] + TEMP_ROT[2] * TEMP_ROT[2] + TEMP_ROT[3] * TEMP_ROT[3]);
256
- TEMP_ROT.forEach((v, j) => {
257
- TEMP_ROT[j] = v / l;
258
- });
259
- const maxComp = TEMP_ROT.reduce((v, _, i) => (Math.abs(TEMP_ROT[i]) > Math.abs(TEMP_ROT[v]) ? i : v), 0);
260
- if (TEMP_ROT[maxComp] < 0) {
261
- TEMP_ROT.forEach((_, j) => {
262
- TEMP_ROT[j] *= -1;
263
- });
264
- }
265
- TEMP_ROT.forEach((_, j) => TEMP_ROT[j] *= Math.SQRT2);
266
- const PERM = [
267
- [1, 2, 3],
268
- [0, 2, 3],
269
- [0, 1, 3],
270
- [0, 1, 2]
271
- ][maxComp];
272
- quats[i * 4] = (TEMP_ROT[PERM[0]] * 0.5 + 0.5) * 255;
273
- quats[i * 4 + 1] = (TEMP_ROT[PERM[1]] * 0.5 + 0.5) * 255;
274
- quats[i * 4 + 2] = (TEMP_ROT[PERM[2]] * 0.5 + 0.5) * 255;
275
- quats[i * 4 + 3] = 252 + maxComp;
276
- }
277
- output['quats.webp'] = encodeWebP(quats, width, height, webPProfile);
278
- logger.timeEnd('ESZ encoding quats');
279
- }
280
- {
281
- logger.time('ESZ encoding sh0');
282
- const rCol = table[10 /* ColIdx.r */];
283
- const gCol = table[11 /* ColIdx.g */];
284
- const bCol = table[12 /* ColIdx.b */];
285
- const aCol = table[13 /* ColIdx.a */];
286
- const sh0 = new Uint8Array(width * height * 4).fill(0xff);
287
- for (let i = 0; i < counts; i++) {
288
- const idx = indices[i];
289
- sh0[i * 4 + 0] = clamp(Math.round(((rCol[idx] - 0.5) / COLOR_SCALE + 0.5) * 255), 0, 255);
290
- sh0[i * 4 + 1] = clamp(Math.round(((gCol[idx] - 0.5) / COLOR_SCALE + 0.5) * 255), 0, 255);
291
- sh0[i * 4 + 2] = clamp(Math.round(((bCol[idx] - 0.5) / COLOR_SCALE + 0.5) * 255), 0, 255);
292
- sh0[i * 4 + 3] = clamp(Math.round(aCol[idx] * 255), 0, 255);
293
- }
294
- output['sh0.webp'] = encodeWebP(sh0, width, height, webPProfile);
295
- logger.timeEnd('ESZ encoding sh0');
296
- }
297
- if (shDegree > 0) {
298
- logger.time('ESZ encoding shN');
299
- const shCoeffs = shCounts / 3;
300
- const pixels = counts * shCoeffs;
301
- const shNWidth = Math.ceil(Math.sqrt(pixels) / 4) * 4;
302
- const shNHeight = Math.ceil(pixels / shNWidth / 4) * 4;
303
- const shN = new Uint8Array(shNWidth * shNHeight * 4).fill(0xff);
304
- for (let i = 0; i < counts; i++) {
305
- const idx = indices[i];
306
- const o = i * shCoeffs;
307
- for (let j = 0; j < shCoeffs; j++) {
308
- const scale = j < 3 ? SH_SCALE1 : SH_SCALE2;
309
- shN[(o + j) * 4 + 0] = clamp(Math.floor((Math.round(table[14 /* ColIdx.shOffset */ + (j * 3 + 0)][idx] * 128) + 128 + scale / 2) / scale) * scale, 0, 255);
310
- shN[(o + j) * 4 + 1] = clamp(Math.floor((Math.round(table[14 /* ColIdx.shOffset */ + (j * 3 + 1)][idx] * 128) + 128 + scale / 2) / scale) * scale, 0, 255);
311
- shN[(o + j) * 4 + 2] = clamp(Math.floor((Math.round(table[14 /* ColIdx.shOffset */ + (j * 3 + 2)][idx] * 128) + 128 + scale / 2) / scale) * scale, 0, 255);
312
- }
313
- }
314
- output['shN.webp'] = encodeWebP(shN, shNWidth, shNHeight, webPProfile);
315
- meta.resources.shN = 'shN.webp';
316
- logger.timeEnd('ESZ encoding shN');
317
- }
318
- output['meta.json'] = Buffer.from(JSON.stringify(meta), 'utf-8');
319
- const result = zipSync(output);
320
- await stream.getWriter().write(result);
321
- }
322
- }
1
+ import { unzipSync, zipSync } from 'fflate';
2
+ import { ColIdx } from '../SplatData.js';
3
+ import { clamp, extractFromRootDir, isUrl, logger, mortonSort } from '../utils/index.js';
4
+ import { decodeWebP, encodeWebP, WebPLosslessProfile } from '../native/index.js';
5
+ import { SH_C0, SH_MAPS } from '../constant.js';
6
+ const TEMP_ROT = new Array(4);
7
+ const PERM_TABLE = [
8
+ // original quat idx ---> actual storage idx
9
+ [0, 1, 2, 3],
10
+ [3, 1, 2, 0],
11
+ [1, 3, 2, 0],
12
+ [1, 2, 3, 0],
13
+ ];
14
+ const COLOR_SCALE = SH_C0 / 0.15;
15
+ const SH_SCALE1 = 1 << 3;
16
+ const SH_SCALE2 = 1 << 4;
17
+ function logTransform(value) {
18
+ return Math.sign(value) * Math.log(Math.abs(value) + 1);
19
+ }
20
+ export class EszFile {
21
+ constructor() {
22
+ this.counts = 0;
23
+ this.shDegree = 0;
24
+ /**
25
+ * @internal
26
+ */
27
+ this.refs = {};
28
+ }
29
+ async load(stream, contentLength) {
30
+ const buffer = new Uint8Array(contentLength);
31
+ const reader = stream.getReader();
32
+ let offset = 0;
33
+ while (true) {
34
+ const { done, value } = await reader.read();
35
+ if (done) {
36
+ break;
37
+ }
38
+ buffer.set(value, offset);
39
+ offset += value.length;
40
+ }
41
+ this.refs = extractFromRootDir(unzipSync(buffer));
42
+ const metaBuffer = this.refs['meta.json'];
43
+ if (!metaBuffer) {
44
+ throw new Error('SOG meta.json not found in the zip archive.');
45
+ }
46
+ const meta = (this.meta = JSON.parse(new TextDecoder().decode(metaBuffer)));
47
+ this.version = meta.version;
48
+ this.counts = meta.counts;
49
+ this.shDegree = meta.shDegree;
50
+ }
51
+ async loadTexture(path) {
52
+ let buffer = this.refs[path];
53
+ if (!buffer) {
54
+ if (isUrl(path)) {
55
+ buffer = await fetch(path)
56
+ .then(res => res.arrayBuffer())
57
+ .then(buf => new Uint8Array(buf));
58
+ }
59
+ }
60
+ if (!buffer) {
61
+ throw new Error(`Cannot load texture: ${path}`);
62
+ }
63
+ const { data, width, height } = decodeWebP(buffer);
64
+ return {
65
+ data: new Uint8Array(data),
66
+ width,
67
+ height,
68
+ };
69
+ }
70
+ async read(stream, contentLength, data) {
71
+ await this.load(stream, contentLength);
72
+ const offset = await data.initBlock(this.counts, this.shDegree);
73
+ const { resources } = this.meta;
74
+ this.cached = await Promise.all([resources.means_l, resources.means_u, resources.scales, resources.quats, resources.sh0, resources.shN]
75
+ .filter(path => !!path)
76
+ .map(path => this.loadTexture(path)));
77
+ const setFn = data.set.bind(data);
78
+ const setShFn = data.setShN.bind(data);
79
+ const SCALE_LUT = new Float32Array(256);
80
+ for (let i = 0; i < 256; i++) {
81
+ SCALE_LUT[i] = Math.exp(i / 16 - 10);
82
+ }
83
+ const COLOR_LUT = new Float32Array(256);
84
+ for (let i = 0; i < 256; i++) {
85
+ COLOR_LUT[i] = (i / 255 - 0.5) * COLOR_SCALE + 0.5;
86
+ }
87
+ const { meta: { box }, counts, shDegree, cached, } = this;
88
+ const [means_l, means_u, scales, quats, color, shN] = cached.map(v => v.data);
89
+ const { min: [centerMinX, centerMinY, centerMinZ], max: [centerMaxX, centerMaxY, centerMaxZ], } = box;
90
+ const rangeX = (centerMaxX - centerMinX) / 65535;
91
+ const rangeY = (centerMaxY - centerMinY) / 65535;
92
+ const rangeZ = (centerMaxZ - centerMinZ) / 65535;
93
+ const single = {
94
+ x: 0,
95
+ y: 0,
96
+ z: 0,
97
+ sx: 0,
98
+ sy: 0,
99
+ sz: 0,
100
+ qx: 0,
101
+ qy: 0,
102
+ qz: 0,
103
+ qw: 0,
104
+ r: 0,
105
+ g: 0,
106
+ b: 0,
107
+ a: 0,
108
+ shN: [],
109
+ };
110
+ for (let i = 0; i < counts; i++) {
111
+ const i4 = i * 4;
112
+ const x = centerMinX + rangeX * (means_l[i4 + 0] + (means_u[i4 + 0] << 8));
113
+ const y = centerMinY + rangeY * (means_l[i4 + 1] + (means_u[i4 + 1] << 8));
114
+ const z = centerMinZ + rangeZ * (means_l[i4 + 2] + (means_u[i4 + 2] << 8));
115
+ single.x = Math.sign(x) * (Math.exp(Math.abs(x)) - 1);
116
+ single.y = Math.sign(y) * (Math.exp(Math.abs(y)) - 1);
117
+ single.z = Math.sign(z) * (Math.exp(Math.abs(z)) - 1);
118
+ single.sx = SCALE_LUT[scales[i4 + 0]];
119
+ single.sy = SCALE_LUT[scales[i4 + 1]];
120
+ single.sz = SCALE_LUT[scales[i4 + 2]];
121
+ TEMP_ROT[0] = (quats[i4 + 0] / 255 - 0.5) * Math.SQRT2;
122
+ TEMP_ROT[1] = (quats[i4 + 1] / 255 - 0.5) * Math.SQRT2;
123
+ TEMP_ROT[2] = (quats[i4 + 2] / 255 - 0.5) * Math.SQRT2;
124
+ 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]));
125
+ const PERM = PERM_TABLE[quats[i4 + 3] - 252];
126
+ single.qx = TEMP_ROT[PERM[0]];
127
+ single.qy = TEMP_ROT[PERM[1]];
128
+ single.qz = TEMP_ROT[PERM[2]];
129
+ single.qw = TEMP_ROT[PERM[3]];
130
+ single.r = COLOR_LUT[color[i4 + 0]];
131
+ single.g = COLOR_LUT[color[i4 + 1]];
132
+ single.b = COLOR_LUT[color[i4 + 2]];
133
+ single.a = color[i4 + 3] / 255;
134
+ setFn(offset + i, single);
135
+ }
136
+ if (shN) {
137
+ const shCounts = SH_MAPS[shDegree];
138
+ const shCoeffs = shCounts / 3;
139
+ const sh = new Array(shCounts);
140
+ for (let i = 0; i < counts; i++) {
141
+ const o = i * shCounts;
142
+ for (let j = 0; j < shCoeffs; j++) {
143
+ sh[o + j * 3 + 0] = (shN[(i * shCoeffs + j) * 4 + 0] - 128) / 128;
144
+ sh[o + j * 3 + 1] = (shN[(i * shCoeffs + j) * 4 + 1] - 128) / 128;
145
+ sh[o + j * 3 + 2] = (shN[(i * shCoeffs + j) * 4 + 2] - 128) / 128;
146
+ }
147
+ setShFn(offset + i, sh);
148
+ }
149
+ }
150
+ data.finishBlock();
151
+ }
152
+ async write(stream, data, indices = mortonSort(data)) {
153
+ const { counts, shDegree, shCounts, table } = data;
154
+ const width = Math.ceil(Math.sqrt(counts) / 4) * 4;
155
+ const height = Math.ceil(counts / width / 4) * 4;
156
+ const webPProfile = new WebPLosslessProfile();
157
+ const output = {};
158
+ const meta = {
159
+ version: 1,
160
+ counts,
161
+ shDegree,
162
+ box: {
163
+ min: [Infinity, Infinity, Infinity],
164
+ max: [-Infinity, -Infinity, -Infinity],
165
+ },
166
+ resources: {
167
+ means_l: 'means_l.webp',
168
+ means_u: 'means_u.webp',
169
+ scales: 'scales.webp',
170
+ quats: 'quats.webp',
171
+ sh0: 'sh0.webp',
172
+ },
173
+ };
174
+ const xCol = table[ColIdx.x];
175
+ const yCol = table[ColIdx.y];
176
+ const zCol = table[ColIdx.z];
177
+ // calculate minmax & transform
178
+ let minX = Infinity;
179
+ let minY = Infinity;
180
+ let minZ = Infinity;
181
+ let maxX = -Infinity;
182
+ let maxY = -Infinity;
183
+ let maxZ = -Infinity;
184
+ for (let i = 0; i < counts; i++) {
185
+ const idx = indices[i];
186
+ const x = xCol[idx];
187
+ const y = yCol[idx];
188
+ const z = zCol[idx];
189
+ if (x < minX) {
190
+ minX = x;
191
+ }
192
+ if (x > maxX) {
193
+ maxX = x;
194
+ }
195
+ if (y < minY) {
196
+ minY = y;
197
+ }
198
+ if (y > maxY) {
199
+ maxY = y;
200
+ }
201
+ if (z < minZ) {
202
+ minZ = z;
203
+ }
204
+ if (z > maxZ) {
205
+ maxZ = z;
206
+ }
207
+ }
208
+ meta.box.min = [minX, minY, minZ];
209
+ meta.box.max = [maxX, maxY, maxZ];
210
+ {
211
+ logger.time('ESZ encoding means');
212
+ minX = logTransform(minX);
213
+ minY = logTransform(minY);
214
+ minZ = logTransform(minZ);
215
+ maxX = logTransform(maxX);
216
+ maxY = logTransform(maxY);
217
+ maxZ = logTransform(maxZ);
218
+ const scaleX = 65535 / Math.max(maxX - minX, 1e-9);
219
+ const scaleY = 65535 / Math.max(maxY - minY, 1e-9);
220
+ const scaleZ = 65535 / Math.max(maxZ - minZ, 1e-9);
221
+ const meansL = new Uint8Array(width * height * 4).fill(0xff);
222
+ const meansU = new Uint8Array(width * height * 4).fill(0xff);
223
+ for (let i = 0; i < indices.length; i++) {
224
+ const idx = indices[i];
225
+ const x = (logTransform(xCol[idx]) - minX) * scaleX;
226
+ const y = (logTransform(yCol[idx]) - minY) * scaleY;
227
+ const z = (logTransform(zCol[idx]) - minZ) * scaleZ;
228
+ meansL[i * 4 + 0] = x & 0xff;
229
+ meansL[i * 4 + 1] = y & 0xff;
230
+ meansL[i * 4 + 2] = z & 0xff;
231
+ meansU[i * 4 + 0] = (x >> 8) & 0xff;
232
+ meansU[i * 4 + 1] = (y >> 8) & 0xff;
233
+ meansU[i * 4 + 2] = (z >> 8) & 0xff;
234
+ }
235
+ output['means_l.webp'] = encodeWebP(meansL, width, height, webPProfile);
236
+ output['means_u.webp'] = encodeWebP(meansU, width, height, webPProfile);
237
+ logger.timeEnd('ESZ encoding means');
238
+ }
239
+ {
240
+ logger.time('ESZ encoding scales');
241
+ const sxCol = table[ColIdx.sx];
242
+ const syCol = table[ColIdx.sy];
243
+ const szCol = table[ColIdx.sz];
244
+ const scales = new Uint8Array(width * height * 4).fill(0xff);
245
+ for (let i = 0; i < counts; i++) {
246
+ const idx = indices[i];
247
+ scales[i * 4 + 0] = clamp(Math.round((Math.log(sxCol[idx]) + 10) * 16), 0, 255);
248
+ scales[i * 4 + 1] = clamp(Math.round((Math.log(syCol[idx]) + 10) * 16), 0, 255);
249
+ scales[i * 4 + 2] = clamp(Math.round((Math.log(szCol[idx]) + 10) * 16), 0, 255);
250
+ }
251
+ output['scales.webp'] = encodeWebP(scales, width, height, webPProfile);
252
+ logger.timeEnd('ESZ encoding scales');
253
+ }
254
+ {
255
+ logger.time('ESZ encoding quats');
256
+ const qxCol = table[ColIdx.qx];
257
+ const qyCol = table[ColIdx.qy];
258
+ const qzCol = table[ColIdx.qz];
259
+ const qwCol = table[ColIdx.qw];
260
+ const quats = new Uint8Array(width * height * 4);
261
+ for (let i = 0; i < counts; i++) {
262
+ const idx = indices[i];
263
+ TEMP_ROT[0] = qwCol[idx];
264
+ TEMP_ROT[1] = qxCol[idx];
265
+ TEMP_ROT[2] = qyCol[idx];
266
+ TEMP_ROT[3] = qzCol[idx];
267
+ const l = Math.sqrt(TEMP_ROT[0] * TEMP_ROT[0] +
268
+ TEMP_ROT[1] * TEMP_ROT[1] +
269
+ TEMP_ROT[2] * TEMP_ROT[2] +
270
+ TEMP_ROT[3] * TEMP_ROT[3]);
271
+ TEMP_ROT.forEach((v, j) => {
272
+ TEMP_ROT[j] = v / l;
273
+ });
274
+ const maxComp = TEMP_ROT.reduce((v, _, i) => (Math.abs(TEMP_ROT[i]) > Math.abs(TEMP_ROT[v]) ? i : v), 0);
275
+ if (TEMP_ROT[maxComp] < 0) {
276
+ TEMP_ROT.forEach((_, j) => {
277
+ TEMP_ROT[j] *= -1;
278
+ });
279
+ }
280
+ TEMP_ROT.forEach((_, j) => (TEMP_ROT[j] *= Math.SQRT2));
281
+ const PERM = [
282
+ [1, 2, 3],
283
+ [0, 2, 3],
284
+ [0, 1, 3],
285
+ [0, 1, 2],
286
+ ][maxComp];
287
+ quats[i * 4] = (TEMP_ROT[PERM[0]] * 0.5 + 0.5) * 255;
288
+ quats[i * 4 + 1] = (TEMP_ROT[PERM[1]] * 0.5 + 0.5) * 255;
289
+ quats[i * 4 + 2] = (TEMP_ROT[PERM[2]] * 0.5 + 0.5) * 255;
290
+ quats[i * 4 + 3] = 252 + maxComp;
291
+ }
292
+ output['quats.webp'] = encodeWebP(quats, width, height, webPProfile);
293
+ logger.timeEnd('ESZ encoding quats');
294
+ }
295
+ {
296
+ logger.time('ESZ encoding sh0');
297
+ const rCol = table[ColIdx.r];
298
+ const gCol = table[ColIdx.g];
299
+ const bCol = table[ColIdx.b];
300
+ const aCol = table[ColIdx.a];
301
+ const sh0 = new Uint8Array(width * height * 4).fill(0xff);
302
+ for (let i = 0; i < counts; i++) {
303
+ const idx = indices[i];
304
+ sh0[i * 4 + 0] = clamp(Math.round(((rCol[idx] - 0.5) / COLOR_SCALE + 0.5) * 255), 0, 255);
305
+ sh0[i * 4 + 1] = clamp(Math.round(((gCol[idx] - 0.5) / COLOR_SCALE + 0.5) * 255), 0, 255);
306
+ sh0[i * 4 + 2] = clamp(Math.round(((bCol[idx] - 0.5) / COLOR_SCALE + 0.5) * 255), 0, 255);
307
+ sh0[i * 4 + 3] = clamp(Math.round(aCol[idx] * 255), 0, 255);
308
+ }
309
+ output['sh0.webp'] = encodeWebP(sh0, width, height, webPProfile);
310
+ logger.timeEnd('ESZ encoding sh0');
311
+ }
312
+ if (shDegree > 0) {
313
+ logger.time('ESZ encoding shN');
314
+ const shCoeffs = shCounts / 3;
315
+ const pixels = counts * shCoeffs;
316
+ const shNWidth = Math.ceil(Math.sqrt(pixels) / 4) * 4;
317
+ const shNHeight = Math.ceil(pixels / shNWidth / 4) * 4;
318
+ const shN = new Uint8Array(shNWidth * shNHeight * 4).fill(0xff);
319
+ for (let i = 0; i < counts; i++) {
320
+ const idx = indices[i];
321
+ const o = i * shCoeffs;
322
+ for (let j = 0; j < shCoeffs; j++) {
323
+ const scale = j < 3 ? SH_SCALE1 : SH_SCALE2;
324
+ shN[(o + j) * 4 + 0] = clamp(Math.floor((Math.round(table[ColIdx.shOffset + (j * 3 + 0)][idx] * 128) + 128 + scale / 2) / scale) * scale, 0, 255);
325
+ shN[(o + j) * 4 + 1] = clamp(Math.floor((Math.round(table[ColIdx.shOffset + (j * 3 + 1)][idx] * 128) + 128 + scale / 2) / scale) * scale, 0, 255);
326
+ shN[(o + j) * 4 + 2] = clamp(Math.floor((Math.round(table[ColIdx.shOffset + (j * 3 + 2)][idx] * 128) + 128 + scale / 2) / scale) * scale, 0, 255);
327
+ }
328
+ }
329
+ output['shN.webp'] = encodeWebP(shN, shNWidth, shNHeight, webPProfile);
330
+ meta.resources.shN = 'shN.webp';
331
+ logger.timeEnd('ESZ encoding shN');
332
+ }
333
+ output['meta.json'] = Buffer.from(JSON.stringify(meta), 'utf-8');
334
+ const result = zipSync(output);
335
+ await stream.getWriter().write(result);
336
+ }
337
+ }