@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.
- package/CHANGELOG.md +120 -113
- package/README.md +39 -39
- package/THIRD_PARTY_LICENSES.txt +1373 -1373
- package/bin/cli.js +125 -118
- package/dist/SplatData.d.ts +67 -67
- package/dist/SplatData.js +167 -150
- 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 -11
- package/dist/file/esz.js +337 -322
- package/dist/file/index.d.ts +8 -8
- package/dist/file/index.js +7 -7
- package/dist/file/ksplat.d.ts +12 -12
- package/dist/file/ksplat.js +293 -231
- package/dist/file/lcc.d.ts +11 -11
- package/dist/file/lcc.js +161 -158
- package/dist/file/ply.d.ts +13 -13
- package/dist/file/ply.js +439 -390
- package/dist/file/sog.d.ts +80 -80
- package/dist/file/sog.js +525 -494
- package/dist/file/splat.d.ts +6 -6
- package/dist/file/splat.js +119 -99
- package/dist/file/spz.d.ts +11 -11
- package/dist/file/spz.js +597 -583
- 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 -129
- 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 -12
- package/dist/tasks/WriteTask.js +70 -70
- package/dist/utils/BufferReader.d.ts +12 -12
- package/dist/utils/BufferReader.js +45 -45
- package/dist/utils/Logger.d.ts +11 -11
- package/dist/utils/Logger.js +40 -40
- package/dist/utils/StreamChunkDecoder.d.ts +16 -16
- package/dist/utils/StreamChunkDecoder.js +31 -31
- 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 -341
- package/dist/utils/math.d.ts +46 -46
- package/dist/utils/math.js +350 -346
- 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 -21
- package/dist/utils/splat.js +397 -387
- 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 -1682
- 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 -656
- 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 -39
- package/dist/native/cpp/bin/linux/binding.node +0 -0
- package/dist/native/cpp/bin/windows/binding.node +0 -0
package/dist/file/ply.js
CHANGED
|
@@ -1,390 +1,439 @@
|
|
|
1
|
-
import { SH_C0, SH_MAPS, NUM_F_REST_TO_SH_DEGREE } from '../constant.js';
|
|
2
|
-
import { BufferReader, StreamChunkDecoder, mortonSort } from '../utils/index.js';
|
|
3
|
-
const F_REST_REGEX = /^f_rest_([0-9]{1,2})$/;
|
|
4
|
-
function createEmptyBlock(properties, shDegree) {
|
|
5
|
-
const result = {
|
|
6
|
-
f_rest: new Array(SH_MAPS[shDegree]),
|
|
7
|
-
};
|
|
8
|
-
for (const name of Object.keys(properties)) {
|
|
9
|
-
if (F_REST_REGEX.test(name)) {
|
|
10
|
-
continue;
|
|
11
|
-
}
|
|
12
|
-
result[name] = 0;
|
|
13
|
-
}
|
|
14
|
-
return result;
|
|
15
|
-
}
|
|
16
|
-
const FIELD_BYTES = {
|
|
17
|
-
char: 1,
|
|
18
|
-
uchar: 1,
|
|
19
|
-
short: 2,
|
|
20
|
-
ushort: 2,
|
|
21
|
-
int: 4,
|
|
22
|
-
uint: 4,
|
|
23
|
-
float: 4,
|
|
24
|
-
double: 8,
|
|
25
|
-
};
|
|
26
|
-
function createParseFn(properties, littleEndian, shDegree) {
|
|
27
|
-
function createPropertyParse(type) {
|
|
28
|
-
switch (type) {
|
|
29
|
-
case 'char':
|
|
30
|
-
return 'data.getInt8(offset)';
|
|
31
|
-
case 'uchar':
|
|
32
|
-
return 'data.getUint8(offset)';
|
|
33
|
-
case 'short':
|
|
34
|
-
return `data.getInt16(offset, ${littleEndian})`;
|
|
35
|
-
case 'ushort':
|
|
36
|
-
return `data.getUint16(offset, ${littleEndian})`;
|
|
37
|
-
case 'int':
|
|
38
|
-
return `data.getInt32(offset, ${littleEndian})`;
|
|
39
|
-
case 'uint':
|
|
40
|
-
return `data.getUint32(offset, ${littleEndian})`;
|
|
41
|
-
case 'float':
|
|
42
|
-
return `data.getFloat32(offset, ${littleEndian})`;
|
|
43
|
-
case 'double':
|
|
44
|
-
return `data.getFloat64(offset, ${littleEndian})`;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
let itemSize = 0;
|
|
48
|
-
const parserSrc = [];
|
|
49
|
-
const shLen = SH_MAPS[shDegree] / 3;
|
|
50
|
-
for (const [propertyName, propertyType] of Object.entries(properties)) {
|
|
51
|
-
const fRestMatch = propertyName.match(F_REST_REGEX);
|
|
52
|
-
if (fRestMatch) {
|
|
53
|
-
let fRestIndex = parseInt(fRestMatch[1], 10);
|
|
54
|
-
fRestIndex = (fRestIndex % shLen) * 3 + Math.floor(fRestIndex / shLen);
|
|
55
|
-
parserSrc.push(`item.f_rest[${fRestIndex}] = ${createPropertyParse(propertyType)};`);
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
parserSrc.push(`item.${propertyName} = ${createPropertyParse(propertyType)};`);
|
|
59
|
-
}
|
|
60
|
-
parserSrc.push(`offset += ${FIELD_BYTES[propertyType]};`);
|
|
61
|
-
itemSize += FIELD_BYTES[propertyType];
|
|
62
|
-
}
|
|
63
|
-
return [itemSize, new Function('data', 'offset', 'item', parserSrc.join('\n'))];
|
|
64
|
-
}
|
|
65
|
-
const HeaderTerminator = 'end_header\n';
|
|
66
|
-
export class PlyFile {
|
|
67
|
-
constructor() {
|
|
68
|
-
this.littleEndian = true;
|
|
69
|
-
this.comments = [];
|
|
70
|
-
this.elements = {};
|
|
71
|
-
this.isSuperSplatCompressed = false;
|
|
72
|
-
this.counts = 0;
|
|
73
|
-
this.shDegree = 0;
|
|
74
|
-
}
|
|
75
|
-
initHeader(header) {
|
|
76
|
-
let curElement;
|
|
77
|
-
const lines = header
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
!
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
!
|
|
173
|
-
!
|
|
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
|
-
|
|
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
|
-
const
|
|
318
|
-
const
|
|
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
|
-
const
|
|
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
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
1
|
+
import { SH_C0, SH_MAPS, NUM_F_REST_TO_SH_DEGREE } from '../constant.js';
|
|
2
|
+
import { BufferReader, StreamChunkDecoder, mortonSort } from '../utils/index.js';
|
|
3
|
+
const F_REST_REGEX = /^f_rest_([0-9]{1,2})$/;
|
|
4
|
+
function createEmptyBlock(properties, shDegree) {
|
|
5
|
+
const result = {
|
|
6
|
+
f_rest: new Array(SH_MAPS[shDegree]),
|
|
7
|
+
};
|
|
8
|
+
for (const name of Object.keys(properties)) {
|
|
9
|
+
if (F_REST_REGEX.test(name)) {
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
result[name] = 0;
|
|
13
|
+
}
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
const FIELD_BYTES = {
|
|
17
|
+
char: 1,
|
|
18
|
+
uchar: 1,
|
|
19
|
+
short: 2,
|
|
20
|
+
ushort: 2,
|
|
21
|
+
int: 4,
|
|
22
|
+
uint: 4,
|
|
23
|
+
float: 4,
|
|
24
|
+
double: 8,
|
|
25
|
+
};
|
|
26
|
+
function createParseFn(properties, littleEndian, shDegree) {
|
|
27
|
+
function createPropertyParse(type) {
|
|
28
|
+
switch (type) {
|
|
29
|
+
case 'char':
|
|
30
|
+
return 'data.getInt8(offset)';
|
|
31
|
+
case 'uchar':
|
|
32
|
+
return 'data.getUint8(offset)';
|
|
33
|
+
case 'short':
|
|
34
|
+
return `data.getInt16(offset, ${littleEndian})`;
|
|
35
|
+
case 'ushort':
|
|
36
|
+
return `data.getUint16(offset, ${littleEndian})`;
|
|
37
|
+
case 'int':
|
|
38
|
+
return `data.getInt32(offset, ${littleEndian})`;
|
|
39
|
+
case 'uint':
|
|
40
|
+
return `data.getUint32(offset, ${littleEndian})`;
|
|
41
|
+
case 'float':
|
|
42
|
+
return `data.getFloat32(offset, ${littleEndian})`;
|
|
43
|
+
case 'double':
|
|
44
|
+
return `data.getFloat64(offset, ${littleEndian})`;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
let itemSize = 0;
|
|
48
|
+
const parserSrc = [];
|
|
49
|
+
const shLen = SH_MAPS[shDegree] / 3;
|
|
50
|
+
for (const [propertyName, propertyType] of Object.entries(properties)) {
|
|
51
|
+
const fRestMatch = propertyName.match(F_REST_REGEX);
|
|
52
|
+
if (fRestMatch) {
|
|
53
|
+
let fRestIndex = parseInt(fRestMatch[1], 10);
|
|
54
|
+
fRestIndex = (fRestIndex % shLen) * 3 + Math.floor(fRestIndex / shLen);
|
|
55
|
+
parserSrc.push(`item.f_rest[${fRestIndex}] = ${createPropertyParse(propertyType)};`);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
parserSrc.push(`item.${propertyName} = ${createPropertyParse(propertyType)};`);
|
|
59
|
+
}
|
|
60
|
+
parserSrc.push(`offset += ${FIELD_BYTES[propertyType]};`);
|
|
61
|
+
itemSize += FIELD_BYTES[propertyType];
|
|
62
|
+
}
|
|
63
|
+
return [itemSize, new Function('data', 'offset', 'item', parserSrc.join('\n'))];
|
|
64
|
+
}
|
|
65
|
+
const HeaderTerminator = 'end_header\n';
|
|
66
|
+
export class PlyFile {
|
|
67
|
+
constructor() {
|
|
68
|
+
this.littleEndian = true;
|
|
69
|
+
this.comments = [];
|
|
70
|
+
this.elements = {};
|
|
71
|
+
this.isSuperSplatCompressed = false;
|
|
72
|
+
this.counts = 0;
|
|
73
|
+
this.shDegree = 0;
|
|
74
|
+
}
|
|
75
|
+
initHeader(header) {
|
|
76
|
+
let curElement;
|
|
77
|
+
const lines = header
|
|
78
|
+
.trim()
|
|
79
|
+
.split('\n')
|
|
80
|
+
.map(v => v.trim())
|
|
81
|
+
.filter(v => !!v);
|
|
82
|
+
for (let i = 0; i < lines.length; i++) {
|
|
83
|
+
const line = lines[i];
|
|
84
|
+
if (i === 0) {
|
|
85
|
+
if (line !== 'ply') {
|
|
86
|
+
throw new Error('Invalid PLY header');
|
|
87
|
+
}
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const fields = line.split(' ');
|
|
91
|
+
switch (fields[0]) {
|
|
92
|
+
case 'format':
|
|
93
|
+
if (fields[1] === 'binary_little_endian') {
|
|
94
|
+
this.littleEndian = true;
|
|
95
|
+
}
|
|
96
|
+
else if (fields[1] === 'binary_big_endian') {
|
|
97
|
+
this.littleEndian = false;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
throw new Error(`Unsupported PLY format: ${fields[1]}`);
|
|
101
|
+
}
|
|
102
|
+
if (fields[2] !== '1.0') {
|
|
103
|
+
throw new Error(`Unsupported PLY version: ${fields[2]}`);
|
|
104
|
+
}
|
|
105
|
+
break;
|
|
106
|
+
case 'comment':
|
|
107
|
+
this.comments.push(line.slice('comment '.length));
|
|
108
|
+
break;
|
|
109
|
+
case 'element': {
|
|
110
|
+
const name = fields[1];
|
|
111
|
+
curElement = this.elements[name] = {
|
|
112
|
+
name,
|
|
113
|
+
count: parseInt(fields[2], 10),
|
|
114
|
+
properties: {},
|
|
115
|
+
};
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
case 'property':
|
|
119
|
+
if (!curElement) {
|
|
120
|
+
throw new Error('Property must be inside an element');
|
|
121
|
+
}
|
|
122
|
+
if (!FIELD_BYTES[fields[1]]) {
|
|
123
|
+
throw new Error(`Unsupported property type '${fields[1]}'`);
|
|
124
|
+
}
|
|
125
|
+
curElement.properties[fields[2]] = fields[1];
|
|
126
|
+
break;
|
|
127
|
+
case 'end_header':
|
|
128
|
+
break;
|
|
129
|
+
default:
|
|
130
|
+
console.warn(`Skipping unsupported PLY keyword: ${fields[0]}`);
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const { elements } = this;
|
|
135
|
+
const isSuperSplatCompressed = (this.isSuperSplatCompressed = !!elements.chunk);
|
|
136
|
+
this.counts = elements.vertex?.count ?? 0;
|
|
137
|
+
const shElement = isSuperSplatCompressed ? elements.sh : elements.vertex;
|
|
138
|
+
if (shElement) {
|
|
139
|
+
const { properties } = shElement;
|
|
140
|
+
let num_f_rest = 0;
|
|
141
|
+
while (properties[`f_rest_${num_f_rest}`]) {
|
|
142
|
+
num_f_rest += 1;
|
|
143
|
+
}
|
|
144
|
+
const shDegree = NUM_F_REST_TO_SH_DEGREE[num_f_rest];
|
|
145
|
+
if (shDegree === undefined) {
|
|
146
|
+
throw new Error(`Unsupported number of SH coefficients: ${num_f_rest}`);
|
|
147
|
+
}
|
|
148
|
+
this.shDegree = shDegree;
|
|
149
|
+
}
|
|
150
|
+
// check invalid
|
|
151
|
+
for (const name in elements) {
|
|
152
|
+
const { properties } = elements[name];
|
|
153
|
+
if (isSuperSplatCompressed) {
|
|
154
|
+
if (name === 'chunk') {
|
|
155
|
+
const { min_x, min_y, min_z, max_x, max_y, max_z, min_scale_x, min_scale_y, min_scale_z, max_scale_x, max_scale_y, max_scale_z, min_r, min_g, min_b, max_r, max_g, max_b, } = properties;
|
|
156
|
+
if (!min_x ||
|
|
157
|
+
!min_y ||
|
|
158
|
+
!min_z ||
|
|
159
|
+
!max_x ||
|
|
160
|
+
!max_y ||
|
|
161
|
+
!max_z ||
|
|
162
|
+
!min_scale_x ||
|
|
163
|
+
!min_scale_y ||
|
|
164
|
+
!min_scale_z ||
|
|
165
|
+
!max_scale_x ||
|
|
166
|
+
!max_scale_y ||
|
|
167
|
+
!max_scale_z ||
|
|
168
|
+
!min_r ||
|
|
169
|
+
!min_g ||
|
|
170
|
+
!min_b ||
|
|
171
|
+
!max_r ||
|
|
172
|
+
!max_g ||
|
|
173
|
+
!max_b) {
|
|
174
|
+
throw new Error('Missing Compressed PLY chunk properties');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
else if (name === 'vertex') {
|
|
178
|
+
const { packed_position, packed_rotation, packed_scale, packed_color } = properties;
|
|
179
|
+
if (!packed_position || !packed_rotation || !packed_scale || !packed_color) {
|
|
180
|
+
throw new Error('Missing Compressed PLY vertex properties');
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
if (name === 'vertex') {
|
|
186
|
+
const { x, y, z, scale_0, scale_1, scale_2, rot_0, rot_1, rot_2, rot_3, f_dc_0, f_dc_1, f_dc_2, opacity, } = properties;
|
|
187
|
+
if (!x ||
|
|
188
|
+
!y ||
|
|
189
|
+
!z ||
|
|
190
|
+
!scale_0 ||
|
|
191
|
+
!scale_1 ||
|
|
192
|
+
!scale_2 ||
|
|
193
|
+
!rot_0 ||
|
|
194
|
+
!rot_1 ||
|
|
195
|
+
!rot_2 ||
|
|
196
|
+
!rot_3 ||
|
|
197
|
+
!f_dc_0 ||
|
|
198
|
+
!f_dc_1 ||
|
|
199
|
+
!f_dc_2 ||
|
|
200
|
+
!opacity) {
|
|
201
|
+
throw new Error('Missing PLY vertex properties');
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
async read(stream, _contentLength, data) {
|
|
208
|
+
const setFn = data.set.bind(data);
|
|
209
|
+
const setShFn = data.setShN.bind(data);
|
|
210
|
+
let headerParsed = false;
|
|
211
|
+
let header = '';
|
|
212
|
+
const reader = new BufferReader();
|
|
213
|
+
const decoder = new StreamChunkDecoder(reader);
|
|
214
|
+
let BlockOffset = 0;
|
|
215
|
+
const chunks = [];
|
|
216
|
+
const single = {
|
|
217
|
+
x: 0,
|
|
218
|
+
y: 0,
|
|
219
|
+
z: 0,
|
|
220
|
+
sx: 0,
|
|
221
|
+
sy: 0,
|
|
222
|
+
sz: 0,
|
|
223
|
+
qx: 0,
|
|
224
|
+
qy: 0,
|
|
225
|
+
qz: 0,
|
|
226
|
+
qw: 0,
|
|
227
|
+
r: 0,
|
|
228
|
+
g: 0,
|
|
229
|
+
b: 0,
|
|
230
|
+
a: 0,
|
|
231
|
+
shN: [],
|
|
232
|
+
};
|
|
233
|
+
const initDecoder = () => {
|
|
234
|
+
const { elements, littleEndian, isSuperSplatCompressed, shDegree } = this;
|
|
235
|
+
const chunkDecoders = [];
|
|
236
|
+
for (const name in elements) {
|
|
237
|
+
const { count, properties } = elements[name];
|
|
238
|
+
const block = createEmptyBlock(properties, shDegree);
|
|
239
|
+
const [itemSize, parseFn] = createParseFn(properties, littleEndian, shDegree);
|
|
240
|
+
let fn = () => { };
|
|
241
|
+
if (isSuperSplatCompressed) {
|
|
242
|
+
if (name === 'chunk') {
|
|
243
|
+
fn = (i, item) => {
|
|
244
|
+
chunks[i - BlockOffset] = { ...item };
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
else if (name === 'sh') {
|
|
248
|
+
fn = (i, item) => {
|
|
249
|
+
setShFn(i, item.f_rest.map(v => (v * 8) / 255 - 4));
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
else if (name === 'vertex') {
|
|
253
|
+
fn = (i, item) => {
|
|
254
|
+
const chunk = chunks[(i - BlockOffset) >>> 8];
|
|
255
|
+
if (!chunk) {
|
|
256
|
+
throw new Error('Missing PLY chunk');
|
|
257
|
+
}
|
|
258
|
+
const { min_x, min_y, min_z, max_x, max_y, max_z, min_scale_x, min_scale_y, min_scale_z, max_scale_x, max_scale_y, max_scale_z, min_r, min_g, min_b, max_r, max_g, max_b, } = chunk;
|
|
259
|
+
const { packed_position, packed_rotation, packed_scale, packed_color } = item;
|
|
260
|
+
single.x = (((packed_position >>> 21) & 2047) / 2047) * (max_x - min_x) + min_x;
|
|
261
|
+
single.y = (((packed_position >>> 11) & 1023) / 1023) * (max_y - min_y) + min_y;
|
|
262
|
+
single.z = ((packed_position & 2047) / 2047) * (max_z - min_z) + min_z;
|
|
263
|
+
const r0 = (((packed_rotation >>> 20) & 1023) / 1023 - 0.5) * Math.SQRT2;
|
|
264
|
+
const r1 = (((packed_rotation >>> 10) & 1023) / 1023 - 0.5) * Math.SQRT2;
|
|
265
|
+
const r2 = ((packed_rotation & 1023) / 1023 - 0.5) * Math.SQRT2;
|
|
266
|
+
const rr = Math.sqrt(Math.max(0, 1.0 - r0 * r0 - r1 * r1 - r2 * r2));
|
|
267
|
+
const rOrder = packed_rotation >>> 30;
|
|
268
|
+
single.qx = rOrder === 0 ? r0 : rOrder === 1 ? rr : r1;
|
|
269
|
+
single.qy = rOrder <= 1 ? r1 : rOrder === 2 ? rr : r2;
|
|
270
|
+
single.qz = rOrder <= 2 ? r2 : rr;
|
|
271
|
+
single.qw = rOrder === 0 ? rr : r0;
|
|
272
|
+
single.sx = Math.exp((((packed_scale >>> 21) & 2047) / 2047) * (max_scale_x - min_scale_x) + min_scale_x);
|
|
273
|
+
single.sy = Math.exp((((packed_scale >>> 11) & 1023) / 1023) * (max_scale_y - min_scale_y) + min_scale_y);
|
|
274
|
+
single.sz = Math.exp(((packed_scale & 2047) / 2047) * (max_scale_z - min_scale_z) + min_scale_z);
|
|
275
|
+
single.r = (((packed_color >>> 24) & 255) / 255) * (max_r - min_r) + min_r;
|
|
276
|
+
single.g = (((packed_color >>> 16) & 255) / 255) * (max_g - min_g) + min_g;
|
|
277
|
+
single.b = (((packed_color >>> 8) & 255) / 255) * (max_b - min_b) + min_b;
|
|
278
|
+
single.a = (packed_color & 255) / 255;
|
|
279
|
+
setFn(i, single);
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
else if (name === 'vertex') {
|
|
284
|
+
fn = (i, item) => {
|
|
285
|
+
single.x = item.x;
|
|
286
|
+
single.y = item.y;
|
|
287
|
+
single.z = item.z;
|
|
288
|
+
single.sx = Math.exp(item.scale_0);
|
|
289
|
+
single.sy = Math.exp(item.scale_1);
|
|
290
|
+
single.sz = Math.exp(item.scale_2);
|
|
291
|
+
single.qx = item.rot_1;
|
|
292
|
+
single.qy = item.rot_2;
|
|
293
|
+
single.qz = item.rot_3;
|
|
294
|
+
single.qw = item.rot_0;
|
|
295
|
+
single.r = item.f_dc_0 * SH_C0 + 0.5;
|
|
296
|
+
single.g = item.f_dc_1 * SH_C0 + 0.5;
|
|
297
|
+
single.b = item.f_dc_2 * SH_C0 + 0.5;
|
|
298
|
+
single.a = 1.0 / (1.0 + Math.exp(-item.opacity));
|
|
299
|
+
setFn(i, single);
|
|
300
|
+
setShFn(i, item.f_rest);
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
chunkDecoders.push({
|
|
304
|
+
init: () => [count, itemSize],
|
|
305
|
+
decode: (offset, counts, buffer) => {
|
|
306
|
+
offset += BlockOffset;
|
|
307
|
+
const dataview = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
308
|
+
for (let i = 0; i < counts; i++) {
|
|
309
|
+
parseFn(dataview, i * itemSize, block);
|
|
310
|
+
fn(offset + i, block);
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
decoder.setDecoders(chunkDecoders);
|
|
316
|
+
};
|
|
317
|
+
const textDecoder = new TextDecoder();
|
|
318
|
+
const source = stream.getReader();
|
|
319
|
+
while (true) {
|
|
320
|
+
const { done, value } = await source.read();
|
|
321
|
+
if (done) {
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
reader.write(value);
|
|
325
|
+
if (!headerParsed) {
|
|
326
|
+
const HeaderReadBlockSize = 4096;
|
|
327
|
+
const counts = (reader.remaining / HeaderReadBlockSize) | 0;
|
|
328
|
+
for (let i = 0; i < counts; i++) {
|
|
329
|
+
const chunk = reader.read(HeaderReadBlockSize);
|
|
330
|
+
header += textDecoder.decode(chunk, { stream: true });
|
|
331
|
+
const idx = header.indexOf(HeaderTerminator);
|
|
332
|
+
if (idx >= 0) {
|
|
333
|
+
header = header.slice(0, idx + HeaderTerminator.length);
|
|
334
|
+
reader.head -=
|
|
335
|
+
HeaderReadBlockSize - (new TextEncoder().encode(header).length % HeaderReadBlockSize);
|
|
336
|
+
this.initHeader(header);
|
|
337
|
+
initDecoder();
|
|
338
|
+
BlockOffset = await data.initBlock(this.counts, this.shDegree);
|
|
339
|
+
headerParsed = true;
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (!headerParsed) {
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
decoder.flush();
|
|
348
|
+
}
|
|
349
|
+
data.finishBlock();
|
|
350
|
+
}
|
|
351
|
+
async write(stream, data, indices = mortonSort(data)) {
|
|
352
|
+
const writer = stream.getWriter();
|
|
353
|
+
const counts = data.counts;
|
|
354
|
+
const shDegree = data.shDegree;
|
|
355
|
+
const shCounts = SH_MAPS[shDegree];
|
|
356
|
+
const shCoeffs = shCounts / 3;
|
|
357
|
+
const header = [
|
|
358
|
+
'ply',
|
|
359
|
+
'format binary_little_endian 1.0',
|
|
360
|
+
`comment Generated by EGS`,
|
|
361
|
+
`element vertex ${counts}`,
|
|
362
|
+
'property float x',
|
|
363
|
+
'property float y',
|
|
364
|
+
'property float z',
|
|
365
|
+
'property float scale_0',
|
|
366
|
+
'property float scale_1',
|
|
367
|
+
'property float scale_2',
|
|
368
|
+
'property float rot_1',
|
|
369
|
+
'property float rot_2',
|
|
370
|
+
'property float rot_3',
|
|
371
|
+
'property float rot_0',
|
|
372
|
+
'property float f_dc_0',
|
|
373
|
+
'property float f_dc_1',
|
|
374
|
+
'property float f_dc_2',
|
|
375
|
+
'property float opacity',
|
|
376
|
+
new Array(shCounts).fill(0).map((_, i) => `property float f_rest_${i}`),
|
|
377
|
+
'end_header',
|
|
378
|
+
'',
|
|
379
|
+
]
|
|
380
|
+
.flat()
|
|
381
|
+
.join('\n');
|
|
382
|
+
writer.write(new TextEncoder().encode(header));
|
|
383
|
+
const ItemSize = 14 + shCounts;
|
|
384
|
+
const chunkSize = 1024;
|
|
385
|
+
const chunkCounts = Math.ceil(counts / chunkSize);
|
|
386
|
+
const single = {
|
|
387
|
+
x: 0,
|
|
388
|
+
y: 0,
|
|
389
|
+
z: 0,
|
|
390
|
+
sx: 0,
|
|
391
|
+
sy: 0,
|
|
392
|
+
sz: 0,
|
|
393
|
+
qx: 0,
|
|
394
|
+
qy: 0,
|
|
395
|
+
qz: 0,
|
|
396
|
+
qw: 0,
|
|
397
|
+
r: 0,
|
|
398
|
+
g: 0,
|
|
399
|
+
b: 0,
|
|
400
|
+
a: 0,
|
|
401
|
+
shN: new Array(shCounts),
|
|
402
|
+
};
|
|
403
|
+
const shN = single.shN;
|
|
404
|
+
for (let i = 0; i < chunkCounts; i++) {
|
|
405
|
+
if (writer.desiredSize <= 0) {
|
|
406
|
+
await writer.ready;
|
|
407
|
+
}
|
|
408
|
+
const currentChunkSize = Math.min(chunkSize, counts - i * chunkSize);
|
|
409
|
+
const chunk = new Float32Array(currentChunkSize * ItemSize);
|
|
410
|
+
const offset = i * chunkSize;
|
|
411
|
+
for (let j = 0; j < currentChunkSize; j++) {
|
|
412
|
+
const idx = indices[offset + j];
|
|
413
|
+
data.get(idx, single);
|
|
414
|
+
data.getShN(idx, shN);
|
|
415
|
+
const o = j * ItemSize;
|
|
416
|
+
chunk[o + 0] = single.x;
|
|
417
|
+
chunk[o + 1] = single.y;
|
|
418
|
+
chunk[o + 2] = single.z;
|
|
419
|
+
chunk[o + 3] = Math.log(single.sx);
|
|
420
|
+
chunk[o + 4] = Math.log(single.sy);
|
|
421
|
+
chunk[o + 5] = Math.log(single.sz);
|
|
422
|
+
chunk[o + 6] = single.qx;
|
|
423
|
+
chunk[o + 7] = single.qy;
|
|
424
|
+
chunk[o + 8] = single.qz;
|
|
425
|
+
chunk[o + 9] = single.qw;
|
|
426
|
+
chunk[o + 10] = (single.r - 0.5) / SH_C0;
|
|
427
|
+
chunk[o + 11] = (single.g - 0.5) / SH_C0;
|
|
428
|
+
chunk[o + 12] = (single.b - 0.5) / SH_C0;
|
|
429
|
+
chunk[o + 13] = single.a === 0 ? -100 : -Math.log(1 / single.a - 1);
|
|
430
|
+
for (let k = 0; k < shCounts; k++) {
|
|
431
|
+
chunk[o + 14 + k] = shN[(k % shCoeffs) * 3 + ((k / shCoeffs) | 0)];
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
writer.write(new Uint8Array(chunk.buffer));
|
|
435
|
+
await Promise.resolve();
|
|
436
|
+
}
|
|
437
|
+
await writer.close();
|
|
438
|
+
}
|
|
439
|
+
}
|