@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.
- package/CHANGELOG.md +120 -106
- package/COPYRIGHT.md +17 -0
- package/README.md +39 -39
- package/THIRD_PARTY_LICENSES.txt +1373 -0
- package/bin/cli.js +125 -118
- package/dist/SplatData.d.ts +67 -67
- package/dist/SplatData.js +167 -156
- package/dist/constant.d.ts +3 -3
- package/dist/constant.js +13 -13
- package/dist/file/IFile.d.ts +5 -5
- package/dist/file/IFile.js +1 -1
- package/dist/file/esz.d.ts +11 -0
- package/dist/file/esz.js +337 -0
- package/dist/file/index.d.ts +8 -7
- package/dist/file/index.js +7 -6
- package/dist/file/ksplat.d.ts +12 -12
- package/dist/file/ksplat.js +293 -232
- package/dist/file/lcc.d.ts +11 -11
- package/dist/file/lcc.js +161 -157
- package/dist/file/ply.d.ts +13 -13
- package/dist/file/ply.js +439 -388
- package/dist/file/sog.d.ts +80 -80
- package/dist/file/sog.js +525 -504
- package/dist/file/splat.d.ts +6 -6
- package/dist/file/splat.js +119 -99
- package/dist/file/spz.d.ts +11 -8
- package/dist/file/spz.js +597 -400
- package/dist/file/voxel.d.ts +43 -37
- package/dist/file/voxel.js +411 -280
- package/dist/index.d.ts +33 -33
- package/dist/index.js +54 -54
- package/dist/native/index.d.ts +54 -54
- package/dist/native/index.js +122 -128
- package/dist/native/utils.d.ts +1 -0
- package/dist/native/utils.js +54 -0
- package/dist/tasks/AutoChunkLodTask.d.ts +13 -13
- package/dist/tasks/AutoChunkLodTask.js +117 -117
- package/dist/tasks/AutoLodTask.d.ts +10 -10
- package/dist/tasks/AutoLodTask.js +20 -20
- package/dist/tasks/BaseTask.d.ts +15 -15
- package/dist/tasks/BaseTask.js +5 -5
- package/dist/tasks/FlexLodTask.d.ts +12 -12
- package/dist/tasks/FlexLodTask.js +54 -44
- package/dist/tasks/ModifyTask.d.ts +9 -9
- package/dist/tasks/ModifyTask.js +166 -156
- package/dist/tasks/ReadTask.d.ts +9 -9
- package/dist/tasks/ReadTask.js +29 -29
- package/dist/tasks/SkeletonLodTask.d.ts +10 -10
- package/dist/tasks/SkeletonLodTask.js +176 -156
- package/dist/tasks/VoxelTask.d.ts +35 -30
- package/dist/tasks/VoxelTask.js +40 -37
- package/dist/tasks/WriteTask.d.ts +12 -11
- package/dist/tasks/WriteTask.js +70 -70
- package/dist/utils/BufferReader.d.ts +12 -12
- package/dist/utils/BufferReader.js +45 -47
- package/dist/utils/Logger.d.ts +11 -11
- package/dist/utils/Logger.js +40 -38
- package/dist/utils/StreamChunkDecoder.d.ts +16 -16
- package/dist/utils/StreamChunkDecoder.js +31 -36
- package/dist/utils/index.d.ts +27 -27
- package/dist/utils/index.js +101 -101
- package/dist/utils/k-means.d.ts +4 -4
- package/dist/utils/k-means.js +340 -350
- package/dist/utils/math.d.ts +46 -46
- package/dist/utils/math.js +350 -351
- package/dist/utils/quantize-1d.d.ts +4 -4
- package/dist/utils/quantize-1d.js +164 -164
- package/dist/utils/sh-rotate.d.ts +2 -2
- package/dist/utils/sh-rotate.js +236 -175
- package/dist/utils/splat.d.ts +21 -20
- package/dist/utils/splat.js +397 -378
- package/dist/utils/voxel/binary.d.ts +8 -0
- package/dist/utils/voxel/binary.js +176 -0
- package/dist/utils/voxel/common.d.ts +178 -162
- package/dist/utils/voxel/common.js +1752 -1700
- package/dist/utils/voxel/coplanar-merge.d.ts +63 -63
- package/dist/utils/voxel/coplanar-merge.js +818 -819
- package/dist/utils/voxel/filter-cluster.d.ts +20 -0
- package/dist/utils/voxel/filter-cluster.js +628 -0
- package/dist/utils/voxel/gpu-dilation.d.ts +2 -2
- package/dist/utils/voxel/gpu-dilation.js +677 -665
- package/dist/utils/voxel/marching-cubes.d.ts +42 -42
- package/dist/utils/voxel/marching-cubes.js +1645 -1657
- package/dist/utils/voxel/mesh.d.ts +3 -3
- package/dist/utils/voxel/mesh.js +130 -130
- package/dist/utils/voxel/nav.d.ts +29 -29
- package/dist/utils/voxel/nav.js +1068 -1043
- package/dist/utils/voxel/postprocess.d.ts +23 -23
- package/dist/utils/voxel/postprocess.js +408 -375
- package/dist/utils/voxel/voxel-faces.d.ts +18 -18
- package/dist/utils/voxel/voxel-faces.js +662 -663
- package/dist/utils/voxel/voxelize.d.ts +34 -33
- package/dist/utils/voxel/voxelize.js +1208 -1193
- package/dist/utils/webgpu.d.ts +8 -8
- package/dist/utils/webgpu.js +122 -122
- package/package.json +37 -30
- package/dist/native/cpp/bin/linux/binding.node +0 -0
- package/dist/native/cpp/bin/windows/binding.node +0 -0
package/dist/file/spz.js
CHANGED
|
@@ -1,400 +1,597 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
let
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
let
|
|
36
|
-
let
|
|
37
|
-
let
|
|
38
|
-
let
|
|
39
|
-
let
|
|
40
|
-
let
|
|
41
|
-
let
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
shDegree = header
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
isF16 = version < 2;
|
|
63
|
-
useSmallestThreeQuat = version >= 3;
|
|
64
|
-
fraction = 1 << fractionalBits;
|
|
65
|
-
fractionInv = 1 / fraction;
|
|
66
|
-
shCounts = SH_MAPS[shDegree];
|
|
67
|
-
BlockOffset = await data.initBlock(counts, shDegree);
|
|
68
|
-
if (flags || reserved) {
|
|
69
|
-
//
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
init: () => [counts, isF16 ? 6 : 9],
|
|
75
|
-
decode: (offset, counts, buf) => {
|
|
76
|
-
offset += BlockOffset;
|
|
77
|
-
let x, y, z;
|
|
78
|
-
for (let i = 0; i < counts; i++) {
|
|
79
|
-
if (isF16) {
|
|
80
|
-
const o = i * 6;
|
|
81
|
-
x = fromHalf((buf[o + 1] << 8) | buf[o]);
|
|
82
|
-
y = fromHalf((buf[o + 3] << 8) | buf[o + 2]);
|
|
83
|
-
z = fromHalf((buf[o + 5] << 8) | buf[o + 4]);
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
const o = i * 9;
|
|
87
|
-
x = (((buf[o + 2] << 24) | (buf[o + 1] << 16) | (buf[o] << 8)) >> 8) * fractionInv;
|
|
88
|
-
y = (((buf[o + 5] << 24) | (buf[o + 4] << 16) | (buf[o + 3] << 8)) >> 8) * fractionInv;
|
|
89
|
-
z = (((buf[o + 8] << 24) | (buf[o + 7] << 16) | (buf[o + 6] << 8)) >> 8) * fractionInv;
|
|
90
|
-
}
|
|
91
|
-
setCenter(offset + i, x, y, z);
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
init: () => [counts, 1],
|
|
97
|
-
decode: (offset, counts, buf) => {
|
|
98
|
-
offset += BlockOffset;
|
|
99
|
-
for (let i = 0; i < counts; i++) {
|
|
100
|
-
setAlpha(offset + i, buf[i] / 255);
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
init: () => [counts, 3],
|
|
106
|
-
decode: (offset, counts, buf) => {
|
|
107
|
-
offset += BlockOffset;
|
|
108
|
-
for (let i = 0; i < counts; i++) {
|
|
109
|
-
const o = i * 3;
|
|
110
|
-
setColor(offset + i, COLOR_LUT[buf[o]], COLOR_LUT[buf[o + 1]], COLOR_LUT[buf[o + 2]]);
|
|
111
|
-
}
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
init: () => [counts, 3],
|
|
116
|
-
decode: (offset, counts, buf) => {
|
|
117
|
-
offset += BlockOffset;
|
|
118
|
-
for (let i = 0; i < counts; i++) {
|
|
119
|
-
const o = i * 3;
|
|
120
|
-
setScale(offset + i, SCALE_LUT[buf[o]], SCALE_LUT[buf[o + 1]], SCALE_LUT[buf[o + 2]]);
|
|
121
|
-
}
|
|
122
|
-
},
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
init: () => [counts, useSmallestThreeQuat ? 4 : 3],
|
|
126
|
-
decode: (offset, counts, buf) => {
|
|
127
|
-
offset += BlockOffset;
|
|
128
|
-
let qx, qy, qz, qw;
|
|
129
|
-
for (let i = 0; i < counts; i++) {
|
|
130
|
-
if (!useSmallestThreeQuat) {
|
|
131
|
-
const o = i * 3;
|
|
132
|
-
qx = buf[o] / 127.5 - 1;
|
|
133
|
-
qy = buf[o + 1] / 127.5 - 1;
|
|
134
|
-
qz = buf[o + 2] / 127.5 - 1;
|
|
135
|
-
qw = Math.sqrt(Math.max(0, 1 - qx * qx - qy * qy - qz * qz));
|
|
136
|
-
}
|
|
137
|
-
else {
|
|
138
|
-
const o = i * 4;
|
|
139
|
-
const packed = buf[o] | (buf[o + 1] << 8) | (buf[o + 2] << 16) | (buf[o + 3] << 24);
|
|
140
|
-
const largest = packed >>> 30;
|
|
141
|
-
let temp = packed;
|
|
142
|
-
let sum = 0;
|
|
143
|
-
for (let j = 3; j >= 0; j--) {
|
|
144
|
-
if (j === largest) {
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
const mag = temp &
|
|
148
|
-
const sign = (temp >>> 9) & 1;
|
|
149
|
-
temp >>>= 10;
|
|
150
|
-
const v = Math.SQRT1_2 * (mag /
|
|
151
|
-
rotation[j] = v;
|
|
152
|
-
sum += v * v;
|
|
153
|
-
}
|
|
154
|
-
rotation[largest] = Math.sqrt(1 - sum);
|
|
155
|
-
qx = rotation[0];
|
|
156
|
-
qy = rotation[1];
|
|
157
|
-
qz = rotation[2];
|
|
158
|
-
qw = rotation[3];
|
|
159
|
-
}
|
|
160
|
-
setQuat(offset + i, qx, qy, qz, qw);
|
|
161
|
-
}
|
|
162
|
-
},
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
init: () => [counts, shCounts],
|
|
166
|
-
decode: (offset, counts, buf) => {
|
|
167
|
-
offset += BlockOffset;
|
|
168
|
-
for (let i = 0; i < counts; i++) {
|
|
169
|
-
const o = i * shCounts;
|
|
170
|
-
for (let j = 0; j < shCounts; j++) {
|
|
171
|
-
shN[j] = (buf[o + j] - 128) / 128;
|
|
172
|
-
}
|
|
173
|
-
setShN(offset + i, shN);
|
|
174
|
-
}
|
|
175
|
-
},
|
|
176
|
-
},
|
|
177
|
-
]);
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
for (let j =
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
1
|
+
import { constants as zlibConstants, zstdCompressSync, zstdDecompressSync } from 'node:zlib';
|
|
2
|
+
import { SH_C0, SH_MAPS } from '../constant.js';
|
|
3
|
+
import { BufferReader, fromHalf, clamp, StreamChunkDecoder, mortonSort } from '../utils/index.js';
|
|
4
|
+
const SPZ_MAGIC = 0x5053474e; // NGSP = Niantic gaussian splat
|
|
5
|
+
const SPZ_VERSION = 3;
|
|
6
|
+
const ZSTD_COMPRESSION_LEVEL = 12;
|
|
7
|
+
const FLAG_ANTIALIASED = 0x1;
|
|
8
|
+
const COLOR_SCALE = SH_C0 / 0.15;
|
|
9
|
+
const rotation = new Array(4);
|
|
10
|
+
const SH_SCALE1 = 1 << 3;
|
|
11
|
+
const SH_SCALE2 = 1 << 4;
|
|
12
|
+
export class SpzFile {
|
|
13
|
+
constructor(compressLevel, spzVersion = SPZ_VERSION) {
|
|
14
|
+
if (spzVersion !== 3 && spzVersion !== 4) {
|
|
15
|
+
throw new Error(`Unsupported SPZ version: ${spzVersion}`);
|
|
16
|
+
}
|
|
17
|
+
this.compressLevel = compressLevel;
|
|
18
|
+
this.spzVersion = spzVersion;
|
|
19
|
+
}
|
|
20
|
+
async read(stream, _contentLength, data) {
|
|
21
|
+
const setCenter = data.setCenter.bind(data);
|
|
22
|
+
const setAlpha = data.setAlpha.bind(data);
|
|
23
|
+
const setColor = data.setColor.bind(data);
|
|
24
|
+
const setScale = data.setScale.bind(data);
|
|
25
|
+
const setQuat = data.setQuat.bind(data);
|
|
26
|
+
const setShN = data.setShN.bind(data);
|
|
27
|
+
const SCALE_LUT = new Float32Array(256);
|
|
28
|
+
for (let i = 0; i < 256; i++) {
|
|
29
|
+
SCALE_LUT[i] = Math.exp(i / 16 - 10);
|
|
30
|
+
}
|
|
31
|
+
const COLOR_LUT = new Float32Array(256);
|
|
32
|
+
for (let i = 0; i < 256; i++) {
|
|
33
|
+
COLOR_LUT[i] = (i / 255 - 0.5) * COLOR_SCALE + 0.5;
|
|
34
|
+
}
|
|
35
|
+
let version = SPZ_VERSION;
|
|
36
|
+
let counts = 0;
|
|
37
|
+
let shDegree = 0;
|
|
38
|
+
let fractionalBits = 12;
|
|
39
|
+
let flags = FLAG_ANTIALIASED;
|
|
40
|
+
let reserved = 0;
|
|
41
|
+
let isF16 = false;
|
|
42
|
+
let useSmallestThreeQuat = true;
|
|
43
|
+
let fraction = 1;
|
|
44
|
+
let fractionInv = 1;
|
|
45
|
+
let shCounts = 0;
|
|
46
|
+
let BlockOffset = 0;
|
|
47
|
+
const shN = [];
|
|
48
|
+
const reader = new BufferReader();
|
|
49
|
+
const decoder = new StreamChunkDecoder(reader);
|
|
50
|
+
decoder.setDecoders([
|
|
51
|
+
{
|
|
52
|
+
init: () => [1, 16],
|
|
53
|
+
decode: async (_offset, _counts, buf) => {
|
|
54
|
+
const header = new DataView(buf.buffer);
|
|
55
|
+
if (header.getUint32(0, true) !== SPZ_MAGIC) {
|
|
56
|
+
throw new Error('Invalid SPZ file');
|
|
57
|
+
}
|
|
58
|
+
({ version, counts, shDegree, fractionalBits, flags, extra: reserved } = readSpzHeader(header));
|
|
59
|
+
if (version < 1 || version > 3) {
|
|
60
|
+
throw new Error(`Unsupported SPZ version: ${version}`);
|
|
61
|
+
}
|
|
62
|
+
isF16 = version < 2;
|
|
63
|
+
useSmallestThreeQuat = version >= 3;
|
|
64
|
+
fraction = 1 << fractionalBits;
|
|
65
|
+
fractionInv = 1 / fraction;
|
|
66
|
+
shCounts = SH_MAPS[shDegree];
|
|
67
|
+
BlockOffset = await data.initBlock(counts, shDegree);
|
|
68
|
+
if (flags || reserved) {
|
|
69
|
+
//
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
init: () => [counts, isF16 ? 6 : 9],
|
|
75
|
+
decode: (offset, counts, buf) => {
|
|
76
|
+
offset += BlockOffset;
|
|
77
|
+
let x, y, z;
|
|
78
|
+
for (let i = 0; i < counts; i++) {
|
|
79
|
+
if (isF16) {
|
|
80
|
+
const o = i * 6;
|
|
81
|
+
x = fromHalf((buf[o + 1] << 8) | buf[o]);
|
|
82
|
+
y = fromHalf((buf[o + 3] << 8) | buf[o + 2]);
|
|
83
|
+
z = fromHalf((buf[o + 5] << 8) | buf[o + 4]);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
const o = i * 9;
|
|
87
|
+
x = (((buf[o + 2] << 24) | (buf[o + 1] << 16) | (buf[o] << 8)) >> 8) * fractionInv;
|
|
88
|
+
y = (((buf[o + 5] << 24) | (buf[o + 4] << 16) | (buf[o + 3] << 8)) >> 8) * fractionInv;
|
|
89
|
+
z = (((buf[o + 8] << 24) | (buf[o + 7] << 16) | (buf[o + 6] << 8)) >> 8) * fractionInv;
|
|
90
|
+
}
|
|
91
|
+
setCenter(offset + i, x, y, z);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
init: () => [counts, 1],
|
|
97
|
+
decode: (offset, counts, buf) => {
|
|
98
|
+
offset += BlockOffset;
|
|
99
|
+
for (let i = 0; i < counts; i++) {
|
|
100
|
+
setAlpha(offset + i, buf[i] / 255);
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
init: () => [counts, 3],
|
|
106
|
+
decode: (offset, counts, buf) => {
|
|
107
|
+
offset += BlockOffset;
|
|
108
|
+
for (let i = 0; i < counts; i++) {
|
|
109
|
+
const o = i * 3;
|
|
110
|
+
setColor(offset + i, COLOR_LUT[buf[o]], COLOR_LUT[buf[o + 1]], COLOR_LUT[buf[o + 2]]);
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
init: () => [counts, 3],
|
|
116
|
+
decode: (offset, counts, buf) => {
|
|
117
|
+
offset += BlockOffset;
|
|
118
|
+
for (let i = 0; i < counts; i++) {
|
|
119
|
+
const o = i * 3;
|
|
120
|
+
setScale(offset + i, SCALE_LUT[buf[o]], SCALE_LUT[buf[o + 1]], SCALE_LUT[buf[o + 2]]);
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
init: () => [counts, useSmallestThreeQuat ? 4 : 3],
|
|
126
|
+
decode: (offset, counts, buf) => {
|
|
127
|
+
offset += BlockOffset;
|
|
128
|
+
let qx, qy, qz, qw;
|
|
129
|
+
for (let i = 0; i < counts; i++) {
|
|
130
|
+
if (!useSmallestThreeQuat) {
|
|
131
|
+
const o = i * 3;
|
|
132
|
+
qx = buf[o] / 127.5 - 1;
|
|
133
|
+
qy = buf[o + 1] / 127.5 - 1;
|
|
134
|
+
qz = buf[o + 2] / 127.5 - 1;
|
|
135
|
+
qw = Math.sqrt(Math.max(0, 1 - qx * qx - qy * qy - qz * qz));
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
const o = i * 4;
|
|
139
|
+
const packed = buf[o] | (buf[o + 1] << 8) | (buf[o + 2] << 16) | (buf[o + 3] << 24);
|
|
140
|
+
const largest = packed >>> 30;
|
|
141
|
+
let temp = packed;
|
|
142
|
+
let sum = 0;
|
|
143
|
+
for (let j = 3; j >= 0; j--) {
|
|
144
|
+
if (j === largest) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const mag = temp & 0x1ff;
|
|
148
|
+
const sign = (temp >>> 9) & 1;
|
|
149
|
+
temp >>>= 10;
|
|
150
|
+
const v = Math.SQRT1_2 * (mag / 0x1ff) * (sign ? -1 : 1);
|
|
151
|
+
rotation[j] = v;
|
|
152
|
+
sum += v * v;
|
|
153
|
+
}
|
|
154
|
+
rotation[largest] = Math.sqrt(1 - sum);
|
|
155
|
+
qx = rotation[0];
|
|
156
|
+
qy = rotation[1];
|
|
157
|
+
qz = rotation[2];
|
|
158
|
+
qw = rotation[3];
|
|
159
|
+
}
|
|
160
|
+
setQuat(offset + i, qx, qy, qz, qw);
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
init: () => [counts, shCounts],
|
|
166
|
+
decode: (offset, counts, buf) => {
|
|
167
|
+
offset += BlockOffset;
|
|
168
|
+
for (let i = 0; i < counts; i++) {
|
|
169
|
+
const o = i * shCounts;
|
|
170
|
+
for (let j = 0; j < shCounts; j++) {
|
|
171
|
+
shN[j] = (buf[o + j] - 128) / 128;
|
|
172
|
+
}
|
|
173
|
+
setShN(offset + i, shN);
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
]);
|
|
178
|
+
const peeked = await peekStream(stream, 8);
|
|
179
|
+
stream = peeked.stream;
|
|
180
|
+
if (isSpzV4(peeked.prefix)) {
|
|
181
|
+
await readSpzV4Stream(stream, reader, decoder);
|
|
182
|
+
data.finishBlock();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
let source;
|
|
186
|
+
if (this.compressLevel === -1) {
|
|
187
|
+
source = stream.getReader();
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
source = stream
|
|
191
|
+
.pipeThrough(new DecompressionStream('gzip'))
|
|
192
|
+
.getReader();
|
|
193
|
+
}
|
|
194
|
+
while (true) {
|
|
195
|
+
const { done, value } = await source.read();
|
|
196
|
+
if (done) {
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
reader.write(value);
|
|
200
|
+
decoder.flush();
|
|
201
|
+
}
|
|
202
|
+
data.finishBlock();
|
|
203
|
+
}
|
|
204
|
+
async write(writeStream, data, indices = mortonSort(data)) {
|
|
205
|
+
if (this.spzVersion === 4) {
|
|
206
|
+
await this.writeV4(writeStream, data, indices);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
await this.writeV3(writeStream, data, indices);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
async writeV3(writeStream, data, indices) {
|
|
213
|
+
let writer;
|
|
214
|
+
let pipePromise;
|
|
215
|
+
if (this.compressLevel === -1) {
|
|
216
|
+
writer = writeStream.getWriter();
|
|
217
|
+
pipePromise = Promise.resolve();
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
const compressStream = new CompressionStream('gzip');
|
|
221
|
+
pipePromise = compressStream.readable.pipeTo(writeStream);
|
|
222
|
+
writer = compressStream.writable.getWriter();
|
|
223
|
+
}
|
|
224
|
+
const version = SPZ_VERSION;
|
|
225
|
+
const counts = data.counts;
|
|
226
|
+
const shDegree = data.shDegree;
|
|
227
|
+
const fractionalBits = 12;
|
|
228
|
+
const flags = FLAG_ANTIALIASED;
|
|
229
|
+
const shCounts = getShCounts(shDegree);
|
|
230
|
+
const context = createSpzEncodeContext(data, indices, fractionalBits, shCounts);
|
|
231
|
+
// header
|
|
232
|
+
writer.write(createSpzHeader(version, counts, shDegree, fractionalBits, flags, 0));
|
|
233
|
+
for (const attribute of getSpzAttributes(shDegree)) {
|
|
234
|
+
await writeSpzAttribute(writer, context, attribute);
|
|
235
|
+
}
|
|
236
|
+
await writer.close();
|
|
237
|
+
await pipePromise;
|
|
238
|
+
}
|
|
239
|
+
async writeV4(writeStream, data, indices) {
|
|
240
|
+
const version = 4;
|
|
241
|
+
const counts = data.counts;
|
|
242
|
+
const shDegree = data.shDegree;
|
|
243
|
+
const fractionalBits = 12;
|
|
244
|
+
const flags = FLAG_ANTIALIASED;
|
|
245
|
+
const shCounts = getShCounts(shDegree);
|
|
246
|
+
const context = createSpzEncodeContext(data, indices, fractionalBits, shCounts);
|
|
247
|
+
const compressed = [];
|
|
248
|
+
const uncompressedSizes = [];
|
|
249
|
+
for (const attribute of getSpzAttributes(shDegree)) {
|
|
250
|
+
const chunk = createSpzAttributeChunk(context, attribute, 0, counts);
|
|
251
|
+
uncompressedSizes.push(chunk.byteLength);
|
|
252
|
+
compressed.push(zstdCompressSync(chunk, {
|
|
253
|
+
params: {
|
|
254
|
+
[zlibConstants.ZSTD_c_compressionLevel]: ZSTD_COMPRESSION_LEVEL,
|
|
255
|
+
},
|
|
256
|
+
}));
|
|
257
|
+
}
|
|
258
|
+
const tocByteOffset = 32;
|
|
259
|
+
const tocSize = compressed.length * 16;
|
|
260
|
+
const header = createSpzHeader(version, counts, shDegree, fractionalBits, flags, compressed.length, 32);
|
|
261
|
+
new DataView(header.buffer).setUint32(16, tocByteOffset, true);
|
|
262
|
+
const toc = new Uint8Array(tocSize);
|
|
263
|
+
const tocView = new DataView(toc.buffer);
|
|
264
|
+
for (let i = 0; i < compressed.length; i++) {
|
|
265
|
+
const entryOffset = i * 16;
|
|
266
|
+
writeUint64(tocView, entryOffset, compressed[i].byteLength);
|
|
267
|
+
writeUint64(tocView, entryOffset + 8, uncompressedSizes[i]);
|
|
268
|
+
}
|
|
269
|
+
const writer = writeStream.getWriter();
|
|
270
|
+
await writer.write(header);
|
|
271
|
+
await writer.write(toc);
|
|
272
|
+
for (const chunk of compressed) {
|
|
273
|
+
await writer.write(chunk);
|
|
274
|
+
}
|
|
275
|
+
await writer.close();
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function getShCounts(shDegree) {
|
|
279
|
+
const shCounts = SH_MAPS[shDegree];
|
|
280
|
+
if (shCounts === undefined) {
|
|
281
|
+
throw new Error(`Unsupported SPZ SH degree: ${shDegree}`);
|
|
282
|
+
}
|
|
283
|
+
return shCounts;
|
|
284
|
+
}
|
|
285
|
+
function createSpzEncodeContext(data, indices, fractionalBits, shCounts) {
|
|
286
|
+
return {
|
|
287
|
+
data,
|
|
288
|
+
indices,
|
|
289
|
+
fractionalBits,
|
|
290
|
+
fraction: 1 << fractionalBits,
|
|
291
|
+
shCounts,
|
|
292
|
+
single: {
|
|
293
|
+
x: 0,
|
|
294
|
+
y: 0,
|
|
295
|
+
z: 0,
|
|
296
|
+
sx: 0,
|
|
297
|
+
sy: 0,
|
|
298
|
+
sz: 0,
|
|
299
|
+
qx: 0,
|
|
300
|
+
qy: 0,
|
|
301
|
+
qz: 0,
|
|
302
|
+
qw: 0,
|
|
303
|
+
r: 0,
|
|
304
|
+
g: 0,
|
|
305
|
+
b: 0,
|
|
306
|
+
a: 0,
|
|
307
|
+
shN: new Array(shCounts),
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
function getSpzAttributes(shDegree) {
|
|
312
|
+
return shDegree > 0
|
|
313
|
+
? ['position', 'alpha', 'color', 'scale', 'quat', 'sh']
|
|
314
|
+
: ['position', 'alpha', 'color', 'scale', 'quat'];
|
|
315
|
+
}
|
|
316
|
+
function getSpzAttributeInfo(attribute, shCounts) {
|
|
317
|
+
switch (attribute) {
|
|
318
|
+
case 'position':
|
|
319
|
+
return { itemSize: 9, chunkSize: 4096 };
|
|
320
|
+
case 'alpha':
|
|
321
|
+
return { itemSize: 1, chunkSize: 65536 };
|
|
322
|
+
case 'color':
|
|
323
|
+
case 'scale':
|
|
324
|
+
return { itemSize: 3, chunkSize: 16384 };
|
|
325
|
+
case 'quat':
|
|
326
|
+
return { itemSize: 4, chunkSize: 16384 };
|
|
327
|
+
case 'sh':
|
|
328
|
+
return { itemSize: shCounts, chunkSize: 1024 };
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
function createSpzAttributeChunk(context, attribute, offset, counts) {
|
|
332
|
+
const { data, indices, single, shCounts } = context;
|
|
333
|
+
const { itemSize } = getSpzAttributeInfo(attribute, shCounts);
|
|
334
|
+
const chunk = new Uint8Array(counts * itemSize);
|
|
335
|
+
for (let i = 0; i < counts; i++) {
|
|
336
|
+
const index = indices[offset + i];
|
|
337
|
+
switch (attribute) {
|
|
338
|
+
case 'position': {
|
|
339
|
+
data.getCenter(index, single);
|
|
340
|
+
const o = i * itemSize;
|
|
341
|
+
const ix = clamp(single.x * context.fraction, -0x7fffff, 0x7fffff);
|
|
342
|
+
chunk[o + 0] = ix & 0xff;
|
|
343
|
+
chunk[o + 1] = (ix >> 8) & 0xff;
|
|
344
|
+
chunk[o + 2] = (ix >> 16) & 0xff;
|
|
345
|
+
const iy = clamp(single.y * context.fraction, -0x7fffff, 0x7fffff);
|
|
346
|
+
chunk[o + 3] = iy & 0xff;
|
|
347
|
+
chunk[o + 4] = (iy >> 8) & 0xff;
|
|
348
|
+
chunk[o + 5] = (iy >> 16) & 0xff;
|
|
349
|
+
const iz = clamp(single.z * context.fraction, -0x7fffff, 0x7fffff);
|
|
350
|
+
chunk[o + 6] = iz & 0xff;
|
|
351
|
+
chunk[o + 7] = (iz >> 8) & 0xff;
|
|
352
|
+
chunk[o + 8] = (iz >> 16) & 0xff;
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
case 'alpha':
|
|
356
|
+
data.getAlpha(index, single);
|
|
357
|
+
chunk[i] = clamp(Math.round(single.a * 255), 0, 255);
|
|
358
|
+
break;
|
|
359
|
+
case 'color': {
|
|
360
|
+
data.getColor(index, single);
|
|
361
|
+
const o = i * itemSize;
|
|
362
|
+
chunk[o + 0] = clamp(Math.round(((single.r - 0.5) / COLOR_SCALE + 0.5) * 255), 0, 255);
|
|
363
|
+
chunk[o + 1] = clamp(Math.round(((single.g - 0.5) / COLOR_SCALE + 0.5) * 255), 0, 255);
|
|
364
|
+
chunk[o + 2] = clamp(Math.round(((single.b - 0.5) / COLOR_SCALE + 0.5) * 255), 0, 255);
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
case 'scale': {
|
|
368
|
+
data.getScale(index, single);
|
|
369
|
+
const o = i * itemSize;
|
|
370
|
+
chunk[o + 0] = clamp(Math.round((Math.log(single.sx) + 10) * 16), 0, 255);
|
|
371
|
+
chunk[o + 1] = clamp(Math.round((Math.log(single.sy) + 10) * 16), 0, 255);
|
|
372
|
+
chunk[o + 2] = clamp(Math.round((Math.log(single.sz) + 10) * 16), 0, 255);
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
case 'quat': {
|
|
376
|
+
data.getQuat(index, single);
|
|
377
|
+
const o = i * itemSize;
|
|
378
|
+
rotation[0] = single.qx;
|
|
379
|
+
rotation[1] = single.qy;
|
|
380
|
+
rotation[2] = single.qz;
|
|
381
|
+
rotation[3] = single.qw;
|
|
382
|
+
let iLargest = 0;
|
|
383
|
+
for (let j = 1; j < 4; ++j) {
|
|
384
|
+
if (Math.abs(rotation[j]) > Math.abs(rotation[iLargest])) {
|
|
385
|
+
iLargest = j;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
const negate = rotation[iLargest] < 0 ? 1 : 0;
|
|
389
|
+
let comp = iLargest;
|
|
390
|
+
for (let j = 0; j < 4; ++j) {
|
|
391
|
+
if (j !== iLargest) {
|
|
392
|
+
const negbit = (rotation[j] < 0 ? 1 : 0) ^ negate;
|
|
393
|
+
const mag = Math.floor(((1 << 9) - 1) * (Math.abs(rotation[j]) / Math.SQRT1_2) + 0.5);
|
|
394
|
+
comp = (comp << 10) | (negbit << 9) | mag;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
chunk[o + 0] = comp & 0xff;
|
|
398
|
+
chunk[o + 1] = (comp >> 8) & 0xff;
|
|
399
|
+
chunk[o + 2] = (comp >> 16) & 0xff;
|
|
400
|
+
chunk[o + 3] = (comp >> 24) & 0xff;
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
case 'sh': {
|
|
404
|
+
data.getShN(index, single.shN);
|
|
405
|
+
const o = i * itemSize;
|
|
406
|
+
for (let j = 0; j < itemSize; j++) {
|
|
407
|
+
if (j < 9) {
|
|
408
|
+
chunk[o + j] = clamp(Math.floor((Math.round(single.shN[j] * 128) + 128 + SH_SCALE1 / 2) / SH_SCALE1) * SH_SCALE1, 0, 255);
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
chunk[o + j] = clamp(Math.floor((Math.round(single.shN[j] * 128) + 128 + SH_SCALE2 / 2) / SH_SCALE2) * SH_SCALE2, 0, 255);
|
|
412
|
+
}
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return chunk;
|
|
418
|
+
}
|
|
419
|
+
async function writeSpzAttribute(writer, context, attribute) {
|
|
420
|
+
const { chunkSize } = getSpzAttributeInfo(attribute, context.shCounts);
|
|
421
|
+
const chunkCounts = Math.ceil(context.data.counts / chunkSize);
|
|
422
|
+
for (let i = 0; i < chunkCounts; i++) {
|
|
423
|
+
if (writer.desiredSize <= 0) {
|
|
424
|
+
await writer.ready;
|
|
425
|
+
}
|
|
426
|
+
const offset = i * chunkSize;
|
|
427
|
+
const counts = Math.min(chunkSize, context.data.counts - offset);
|
|
428
|
+
writer.write(createSpzAttributeChunk(context, attribute, offset, counts));
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
function readUint64(view, offset) {
|
|
432
|
+
const low = view.getUint32(offset, true);
|
|
433
|
+
const high = view.getUint32(offset + 4, true);
|
|
434
|
+
const value = high * 0x100000000 + low;
|
|
435
|
+
if (!Number.isSafeInteger(value)) {
|
|
436
|
+
throw new Error(`SPZ stream size is too large: ${value}`);
|
|
437
|
+
}
|
|
438
|
+
return value;
|
|
439
|
+
}
|
|
440
|
+
function writeUint64(view, offset, value) {
|
|
441
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
442
|
+
throw new Error(`Invalid SPZ stream size: ${value}`);
|
|
443
|
+
}
|
|
444
|
+
view.setUint32(offset, value >>> 0, true);
|
|
445
|
+
view.setUint32(offset + 4, Math.floor(value / 0x100000000), true);
|
|
446
|
+
}
|
|
447
|
+
function createSpzHeader(version, counts, shDegree, fractionalBits, flags, extra, byteLength = 16) {
|
|
448
|
+
const header = new DataView(new ArrayBuffer(byteLength));
|
|
449
|
+
header.setUint32(0, SPZ_MAGIC, true);
|
|
450
|
+
header.setUint32(4, version, true);
|
|
451
|
+
header.setUint32(8, counts, true);
|
|
452
|
+
header.setUint8(12, shDegree);
|
|
453
|
+
header.setUint8(13, fractionalBits);
|
|
454
|
+
header.setUint8(14, flags);
|
|
455
|
+
header.setUint8(15, extra);
|
|
456
|
+
return new Uint8Array(header.buffer);
|
|
457
|
+
}
|
|
458
|
+
function readSpzHeader(view) {
|
|
459
|
+
return {
|
|
460
|
+
version: view.getUint32(4, true),
|
|
461
|
+
counts: view.getUint32(8, true),
|
|
462
|
+
shDegree: view.getUint8(12),
|
|
463
|
+
fractionalBits: view.getUint8(13),
|
|
464
|
+
flags: view.getUint8(14),
|
|
465
|
+
extra: view.getUint8(15),
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
function getSpzV4AttributeSizes(counts, shDegree) {
|
|
469
|
+
const shCounts = getShCounts(shDegree);
|
|
470
|
+
const sizes = [
|
|
471
|
+
counts * 9, // position
|
|
472
|
+
counts, // alpha
|
|
473
|
+
counts * 3, // color
|
|
474
|
+
counts * 3, // scale
|
|
475
|
+
counts * 4, // quat
|
|
476
|
+
];
|
|
477
|
+
if (shDegree > 0) {
|
|
478
|
+
sizes.push(counts * shCounts); // sh
|
|
479
|
+
}
|
|
480
|
+
return sizes;
|
|
481
|
+
}
|
|
482
|
+
function isSpzV4(buffer) {
|
|
483
|
+
if (buffer.byteLength < 8) {
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
487
|
+
return view.getUint32(0, true) === SPZ_MAGIC && view.getUint32(4, true) === 4;
|
|
488
|
+
}
|
|
489
|
+
async function readSpzV4Stream(stream, reader, decoder) {
|
|
490
|
+
const read = createExactReader(stream);
|
|
491
|
+
const header = await read(32);
|
|
492
|
+
const view = new DataView(header.buffer, header.byteOffset, header.byteLength);
|
|
493
|
+
const { counts, shDegree, fractionalBits, flags, extra: numStreams } = readSpzHeader(view);
|
|
494
|
+
const tocByteOffset = view.getUint32(16, true);
|
|
495
|
+
const expectedSizes = getSpzV4AttributeSizes(counts, shDegree);
|
|
496
|
+
if (numStreams !== expectedSizes.length) {
|
|
497
|
+
throw new Error(`Invalid SPZ v4 stream count: ${numStreams}`);
|
|
498
|
+
}
|
|
499
|
+
if (tocByteOffset < 32) {
|
|
500
|
+
throw new Error(`Invalid SPZ v4 TOC offset: ${tocByteOffset}`);
|
|
501
|
+
}
|
|
502
|
+
if (tocByteOffset > 32) {
|
|
503
|
+
await read(tocByteOffset - 32);
|
|
504
|
+
}
|
|
505
|
+
const toc = await read(numStreams * 16);
|
|
506
|
+
const tocView = new DataView(toc.buffer, toc.byteOffset, toc.byteLength);
|
|
507
|
+
// Reuse the legacy v3 attribute decoder after parsing the v4 container.
|
|
508
|
+
reader.write(createSpzHeader(SPZ_VERSION, counts, shDegree, fractionalBits, flags & FLAG_ANTIALIASED, 0));
|
|
509
|
+
decoder.flush();
|
|
510
|
+
for (let i = 0; i < numStreams; i++) {
|
|
511
|
+
const entryOffset = i * 16;
|
|
512
|
+
const compressedSize = readUint64(tocView, entryOffset);
|
|
513
|
+
const uncompressedSize = readUint64(tocView, entryOffset + 8);
|
|
514
|
+
if (uncompressedSize !== expectedSizes[i]) {
|
|
515
|
+
throw new Error(`Invalid SPZ v4 stream size at index ${i}`);
|
|
516
|
+
}
|
|
517
|
+
const compressed = await read(compressedSize);
|
|
518
|
+
const decompressed = zstdDecompressSync(compressed, {
|
|
519
|
+
maxOutputLength: uncompressedSize,
|
|
520
|
+
});
|
|
521
|
+
if (decompressed.byteLength !== uncompressedSize) {
|
|
522
|
+
throw new Error(`Invalid SPZ v4 decompressed size at index ${i}`);
|
|
523
|
+
}
|
|
524
|
+
reader.write(new Uint8Array(decompressed.buffer, decompressed.byteOffset, decompressed.byteLength));
|
|
525
|
+
decoder.flush();
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
// Return a reader that resolves exactly byteLength bytes and keeps leftover bytes for the next read.
|
|
529
|
+
function createExactReader(stream) {
|
|
530
|
+
const reader = stream.getReader();
|
|
531
|
+
let chunk;
|
|
532
|
+
let chunkOffset = 0;
|
|
533
|
+
return async (byteLength) => {
|
|
534
|
+
const result = new Uint8Array(byteLength);
|
|
535
|
+
let offset = 0;
|
|
536
|
+
while (offset < byteLength) {
|
|
537
|
+
if (!chunk || chunkOffset >= chunk.byteLength) {
|
|
538
|
+
const { done, value } = await reader.read();
|
|
539
|
+
if (done || !value) {
|
|
540
|
+
throw new Error('Invalid SPZ v4 file: stream ended unexpectedly');
|
|
541
|
+
}
|
|
542
|
+
chunk = value;
|
|
543
|
+
chunkOffset = 0;
|
|
544
|
+
}
|
|
545
|
+
const copyLength = Math.min(byteLength - offset, chunk.byteLength - chunkOffset);
|
|
546
|
+
result.set(chunk.subarray(chunkOffset, chunkOffset + copyLength), offset);
|
|
547
|
+
chunkOffset += copyLength;
|
|
548
|
+
offset += copyLength;
|
|
549
|
+
}
|
|
550
|
+
return result;
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
// Peek leading bytes for format detection, then replay the consumed chunks through a replacement stream.
|
|
554
|
+
async function peekStream(stream, byteLength) {
|
|
555
|
+
const reader = stream.getReader();
|
|
556
|
+
const chunks = [];
|
|
557
|
+
let size = 0;
|
|
558
|
+
while (size < byteLength) {
|
|
559
|
+
const { done, value } = await reader.read();
|
|
560
|
+
if (done || !value) {
|
|
561
|
+
break;
|
|
562
|
+
}
|
|
563
|
+
chunks.push(value);
|
|
564
|
+
size += value.byteLength;
|
|
565
|
+
}
|
|
566
|
+
const prefix = new Uint8Array(Math.min(size, byteLength));
|
|
567
|
+
let offset = 0;
|
|
568
|
+
for (const chunk of chunks) {
|
|
569
|
+
const copyLength = Math.min(chunk.byteLength, prefix.byteLength - offset);
|
|
570
|
+
prefix.set(chunk.subarray(0, copyLength), offset);
|
|
571
|
+
offset += copyLength;
|
|
572
|
+
if (offset === prefix.byteLength) {
|
|
573
|
+
break;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return {
|
|
577
|
+
prefix,
|
|
578
|
+
stream: new ReadableStream({
|
|
579
|
+
start(controller) {
|
|
580
|
+
for (const chunk of chunks) {
|
|
581
|
+
controller.enqueue(chunk);
|
|
582
|
+
}
|
|
583
|
+
},
|
|
584
|
+
async pull(controller) {
|
|
585
|
+
const { done, value } = await reader.read();
|
|
586
|
+
if (done) {
|
|
587
|
+
controller.close();
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
controller.enqueue(value);
|
|
591
|
+
},
|
|
592
|
+
cancel(reason) {
|
|
593
|
+
return reader.cancel(reason);
|
|
594
|
+
},
|
|
595
|
+
}),
|
|
596
|
+
};
|
|
597
|
+
}
|