@manycore/aholo-splat-transform 1.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +102 -0
- package/README.md +33 -0
- package/bin/cli.js +118 -0
- package/dist/SplatData.d.ts +67 -0
- package/dist/SplatData.js +156 -0
- package/dist/constant.d.ts +3 -0
- package/dist/constant.js +13 -0
- package/dist/file/IFile.d.ts +5 -0
- package/dist/file/IFile.js +1 -0
- package/dist/file/index.d.ts +7 -0
- package/dist/file/index.js +6 -0
- package/dist/file/ksplat.d.ts +12 -0
- package/dist/file/ksplat.js +232 -0
- package/dist/file/lcc.d.ts +11 -0
- package/dist/file/lcc.js +157 -0
- package/dist/file/ply.d.ts +13 -0
- package/dist/file/ply.js +388 -0
- package/dist/file/sog.d.ts +80 -0
- package/dist/file/sog.js +504 -0
- package/dist/file/splat.d.ts +6 -0
- package/dist/file/splat.js +99 -0
- package/dist/file/spz.d.ts +8 -0
- package/dist/file/spz.js +400 -0
- package/dist/file/voxel.d.ts +37 -0
- package/dist/file/voxel.js +280 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +54 -0
- package/dist/native/cpp/bin/linux/binding.node +0 -0
- package/dist/native/cpp/bin/windows/binding.node +0 -0
- package/dist/native/index.d.ts +54 -0
- package/dist/native/index.js +128 -0
- package/dist/tasks/AutoChunkLodTask.d.ts +13 -0
- package/dist/tasks/AutoChunkLodTask.js +117 -0
- package/dist/tasks/AutoLodTask.d.ts +10 -0
- package/dist/tasks/AutoLodTask.js +20 -0
- package/dist/tasks/BaseTask.d.ts +15 -0
- package/dist/tasks/BaseTask.js +5 -0
- package/dist/tasks/FlexLodTask.d.ts +12 -0
- package/dist/tasks/FlexLodTask.js +44 -0
- package/dist/tasks/ModifyTask.d.ts +9 -0
- package/dist/tasks/ModifyTask.js +156 -0
- package/dist/tasks/ReadTask.d.ts +8 -0
- package/dist/tasks/ReadTask.js +29 -0
- package/dist/tasks/SkeletonLodTask.d.ts +10 -0
- package/dist/tasks/SkeletonLodTask.js +156 -0
- package/dist/tasks/VoxelTask.d.ts +30 -0
- package/dist/tasks/VoxelTask.js +37 -0
- package/dist/tasks/WriteTask.d.ts +11 -0
- package/dist/tasks/WriteTask.js +70 -0
- package/dist/utils/BufferReader.d.ts +12 -0
- package/dist/utils/BufferReader.js +47 -0
- package/dist/utils/Logger.d.ts +11 -0
- package/dist/utils/Logger.js +38 -0
- package/dist/utils/StreamChunkDecoder.d.ts +16 -0
- package/dist/utils/StreamChunkDecoder.js +36 -0
- package/dist/utils/index.d.ts +27 -0
- package/dist/utils/index.js +101 -0
- package/dist/utils/k-means.d.ts +4 -0
- package/dist/utils/k-means.js +350 -0
- package/dist/utils/math.d.ts +46 -0
- package/dist/utils/math.js +351 -0
- package/dist/utils/quantize-1d.d.ts +4 -0
- package/dist/utils/quantize-1d.js +164 -0
- package/dist/utils/sh-rotate.d.ts +2 -0
- package/dist/utils/sh-rotate.js +175 -0
- package/dist/utils/splat.d.ts +20 -0
- package/dist/utils/splat.js +378 -0
- package/dist/utils/voxel/common.d.ts +162 -0
- package/dist/utils/voxel/common.js +1700 -0
- package/dist/utils/voxel/coplanar-merge.d.ts +63 -0
- package/dist/utils/voxel/coplanar-merge.js +819 -0
- package/dist/utils/voxel/gpu-dilation.d.ts +2 -0
- package/dist/utils/voxel/gpu-dilation.js +665 -0
- package/dist/utils/voxel/marching-cubes.d.ts +42 -0
- package/dist/utils/voxel/marching-cubes.js +1657 -0
- package/dist/utils/voxel/mesh.d.ts +3 -0
- package/dist/utils/voxel/mesh.js +130 -0
- package/dist/utils/voxel/nav.d.ts +29 -0
- package/dist/utils/voxel/nav.js +1043 -0
- package/dist/utils/voxel/postprocess.d.ts +23 -0
- package/dist/utils/voxel/postprocess.js +375 -0
- package/dist/utils/voxel/voxel-faces.d.ts +18 -0
- package/dist/utils/voxel/voxel-faces.js +663 -0
- package/dist/utils/voxel/voxelize.d.ts +33 -0
- package/dist/utils/voxel/voxelize.js +1193 -0
- package/dist/utils/webgpu.d.ts +8 -0
- package/dist/utils/webgpu.js +122 -0
- package/package.json +32 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { SH_MAPS } from '../constant.js';
|
|
2
|
+
import { fromHalf } from '../utils/index.js';
|
|
3
|
+
const KSPLAT_COMPRESSION = {
|
|
4
|
+
0: {
|
|
5
|
+
bytesPerCenter: 12,
|
|
6
|
+
bytesPerScale: 12,
|
|
7
|
+
bytesPerRotation: 16,
|
|
8
|
+
bytesPerColor: 4,
|
|
9
|
+
bytesPerSphericalHarmonicsComponent: 4,
|
|
10
|
+
scaleOffsetBytes: 12,
|
|
11
|
+
rotationOffsetBytes: 24,
|
|
12
|
+
colorOffsetBytes: 40,
|
|
13
|
+
sphericalHarmonicsOffsetBytes: 44,
|
|
14
|
+
scaleRange: 1,
|
|
15
|
+
},
|
|
16
|
+
1: {
|
|
17
|
+
bytesPerCenter: 6,
|
|
18
|
+
bytesPerScale: 6,
|
|
19
|
+
bytesPerRotation: 8,
|
|
20
|
+
bytesPerColor: 4,
|
|
21
|
+
bytesPerSphericalHarmonicsComponent: 2,
|
|
22
|
+
scaleOffsetBytes: 6,
|
|
23
|
+
rotationOffsetBytes: 12,
|
|
24
|
+
colorOffsetBytes: 20,
|
|
25
|
+
sphericalHarmonicsOffsetBytes: 24,
|
|
26
|
+
scaleRange: 32767,
|
|
27
|
+
},
|
|
28
|
+
2: {
|
|
29
|
+
bytesPerCenter: 6,
|
|
30
|
+
bytesPerScale: 6,
|
|
31
|
+
bytesPerRotation: 8,
|
|
32
|
+
bytesPerColor: 4,
|
|
33
|
+
bytesPerSphericalHarmonicsComponent: 1,
|
|
34
|
+
scaleOffsetBytes: 6,
|
|
35
|
+
rotationOffsetBytes: 12,
|
|
36
|
+
colorOffsetBytes: 20,
|
|
37
|
+
sphericalHarmonicsOffsetBytes: 24,
|
|
38
|
+
scaleRange: 32767,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
const SHIndex = [
|
|
42
|
+
0, 3, 6, 1, 4, 7, 2, 5, 8, // sh1
|
|
43
|
+
9, 14, 19, 10, 15, 20, 11, 16, 21, 12, 17, 22, 13, 18, 23, // sh2
|
|
44
|
+
24, 31, 38, 25, 32, 39, 26, 33, 40, 27, 34, 41, 28, 35, 42, 29, 36, 43, 30, 37, 44, // sh3
|
|
45
|
+
];
|
|
46
|
+
const HEADER_BYTES = 4096;
|
|
47
|
+
const SECTION_BYTES = 1024;
|
|
48
|
+
export class KsplatFile {
|
|
49
|
+
counts = 0;
|
|
50
|
+
shDegree = 0;
|
|
51
|
+
header;
|
|
52
|
+
sections;
|
|
53
|
+
buffer;
|
|
54
|
+
load(buffer) {
|
|
55
|
+
this.buffer = buffer;
|
|
56
|
+
const header = new DataView(buffer.buffer, 0, HEADER_BYTES);
|
|
57
|
+
const versionMajor = header.getUint8(0);
|
|
58
|
+
const versionMinor = header.getUint8(1);
|
|
59
|
+
if (versionMajor !== 0 || versionMinor < 1) {
|
|
60
|
+
throw new Error(`Unsupported .ksplat version: ${versionMajor}.${versionMinor}`);
|
|
61
|
+
}
|
|
62
|
+
const maxSectionCount = header.getUint32(4, true);
|
|
63
|
+
const sectionCount = header.getUint32(8, true);
|
|
64
|
+
const maxSplatCount = header.getUint32(12, true);
|
|
65
|
+
const splatCount = header.getUint32(16, true);
|
|
66
|
+
const compressionLevel = header.getUint16(20, true);
|
|
67
|
+
if (compressionLevel < 0 || compressionLevel > 2) {
|
|
68
|
+
throw new Error(`Invalid .ksplat compression level: ${compressionLevel}`);
|
|
69
|
+
}
|
|
70
|
+
const sceneCenterX = header.getFloat32(24, true);
|
|
71
|
+
const sceneCenterY = header.getFloat32(28, true);
|
|
72
|
+
const sceneCenterZ = header.getFloat32(32, true);
|
|
73
|
+
const minSH = header.getFloat32(36, true) || -1.5;
|
|
74
|
+
const maxSH = header.getFloat32(40, true) || 1.5;
|
|
75
|
+
let maxSHDegree = 0;
|
|
76
|
+
const sections = [];
|
|
77
|
+
for (let i = 0; i < maxSectionCount; i++) {
|
|
78
|
+
const section = new DataView(buffer.buffer, HEADER_BYTES + i * SECTION_BYTES, SECTION_BYTES);
|
|
79
|
+
const sectionSplatCount = section.getUint32(0, true);
|
|
80
|
+
const sectionMaxSplatCount = section.getUint32(4, true);
|
|
81
|
+
const bucketSize = section.getUint32(8, true);
|
|
82
|
+
const bucketCount = section.getUint32(12, true);
|
|
83
|
+
const bucketBlockSize = section.getFloat32(16, true);
|
|
84
|
+
const bucketStorageSizeBytes = section.getUint16(20, true);
|
|
85
|
+
const compressionScaleRange = section.getUint32(24, true);
|
|
86
|
+
const fullBucketCount = section.getUint32(32, true);
|
|
87
|
+
const partiallyFilledBucketCount = section.getUint32(36, true);
|
|
88
|
+
const shDegree = section.getUint16(40, true);
|
|
89
|
+
maxSHDegree = Math.max(maxSHDegree, shDegree);
|
|
90
|
+
sections.push({
|
|
91
|
+
sectionSplatCount,
|
|
92
|
+
sectionMaxSplatCount,
|
|
93
|
+
bucketSize,
|
|
94
|
+
bucketCount,
|
|
95
|
+
bucketBlockSize,
|
|
96
|
+
bucketStorageSizeBytes,
|
|
97
|
+
compressionScaleRange: compressionScaleRange || KSPLAT_COMPRESSION[compressionLevel].scaleRange,
|
|
98
|
+
fullBucketCount,
|
|
99
|
+
partiallyFilledBucketCount,
|
|
100
|
+
shDegree,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
this.header = {
|
|
104
|
+
versionMajor,
|
|
105
|
+
versionMinor,
|
|
106
|
+
maxSectionCount,
|
|
107
|
+
sectionCount,
|
|
108
|
+
maxSplatCount,
|
|
109
|
+
splatCount,
|
|
110
|
+
compressionLevel,
|
|
111
|
+
sceneCenter: [sceneCenterX, sceneCenterY, sceneCenterZ],
|
|
112
|
+
shRange: [minSH, maxSH],
|
|
113
|
+
};
|
|
114
|
+
this.sections = sections;
|
|
115
|
+
this.counts = splatCount;
|
|
116
|
+
this.shDegree = maxSHDegree;
|
|
117
|
+
}
|
|
118
|
+
async read(stream, contentLength, data) {
|
|
119
|
+
let BlockOffset = 0;
|
|
120
|
+
{
|
|
121
|
+
const buffer = new Uint8Array(contentLength);
|
|
122
|
+
const reader = stream.getReader();
|
|
123
|
+
let offset = 0;
|
|
124
|
+
while (true) {
|
|
125
|
+
const { done, value } = await reader.read();
|
|
126
|
+
if (done) {
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
buffer.set(value, offset);
|
|
130
|
+
offset += value.length;
|
|
131
|
+
}
|
|
132
|
+
this.load(buffer);
|
|
133
|
+
BlockOffset = await data.initBlock(this.counts, this.shDegree);
|
|
134
|
+
}
|
|
135
|
+
const setFn = data.set.bind(data);
|
|
136
|
+
const setShFn = data.setShN.bind(data);
|
|
137
|
+
const { buffer, header, sections, shDegree: maxSHDegree } = this;
|
|
138
|
+
const { maxSectionCount, compressionLevel, shRange: [minSH, maxSH] } = header;
|
|
139
|
+
const isHighQualitySplatData = compressionLevel === 0;
|
|
140
|
+
const single = {
|
|
141
|
+
x: 0, y: 0, z: 0,
|
|
142
|
+
sx: 0, sy: 0, sz: 0,
|
|
143
|
+
qx: 0, qy: 0, qz: 0, qw: 0,
|
|
144
|
+
r: 0, g: 0, b: 0, a: 0,
|
|
145
|
+
shN: [],
|
|
146
|
+
};
|
|
147
|
+
const maxSHSize = SH_MAPS[maxSHDegree];
|
|
148
|
+
const shData = new Array(maxSHSize);
|
|
149
|
+
let sectionBase = HEADER_BYTES + maxSectionCount * SECTION_BYTES;
|
|
150
|
+
for (let i = 0; i < maxSectionCount; i++) {
|
|
151
|
+
const { sectionSplatCount, sectionMaxSplatCount, bucketSize, bucketCount, bucketBlockSize, bucketStorageSizeBytes, fullBucketCount, partiallyFilledBucketCount, compressionScaleRange, shDegree, } = sections[i];
|
|
152
|
+
const fullBucketSplats = fullBucketCount * bucketSize;
|
|
153
|
+
const bucketsMetaDataSizeBytes = partiallyFilledBucketCount * 4;
|
|
154
|
+
const bucketsStorageSizeBytes = bucketStorageSizeBytes * bucketCount + bucketsMetaDataSizeBytes;
|
|
155
|
+
const shComponents = SH_MAPS[shDegree];
|
|
156
|
+
const { bytesPerCenter, bytesPerScale, bytesPerRotation, bytesPerColor, bytesPerSphericalHarmonicsComponent, scaleOffsetBytes, rotationOffsetBytes, colorOffsetBytes, sphericalHarmonicsOffsetBytes, } = KSPLAT_COMPRESSION[compressionLevel];
|
|
157
|
+
const bytesPerSplat = bytesPerCenter + bytesPerScale + bytesPerRotation + bytesPerColor +
|
|
158
|
+
shComponents * bytesPerSphericalHarmonicsComponent;
|
|
159
|
+
const splatDataStorageSizeBytes = bytesPerSplat * sectionMaxSplatCount;
|
|
160
|
+
const storageSizeBytes = splatDataStorageSizeBytes + bucketsStorageSizeBytes;
|
|
161
|
+
const compressionScaleFactor = bucketBlockSize / 2 / compressionScaleRange;
|
|
162
|
+
const bucketsBase = sectionBase + bucketsMetaDataSizeBytes;
|
|
163
|
+
const dataBase = sectionBase + bucketsStorageSizeBytes;
|
|
164
|
+
const data = new DataView(buffer.buffer, dataBase, splatDataStorageSizeBytes);
|
|
165
|
+
const bucketArray = new Float32Array(buffer.buffer, bucketsBase, bucketCount * 3);
|
|
166
|
+
const partiallyFilledBucketLengths = new Uint32Array(buffer.buffer, sectionBase, partiallyFilledBucketCount);
|
|
167
|
+
let partialBucketIndex = fullBucketCount;
|
|
168
|
+
let partialBucketBase = fullBucketSplats;
|
|
169
|
+
for (let j = 0; j < sectionSplatCount; j++) {
|
|
170
|
+
const splatOffset = j * bytesPerSplat;
|
|
171
|
+
let bucketIndex;
|
|
172
|
+
if (j < fullBucketSplats) {
|
|
173
|
+
bucketIndex = Math.floor(j / bucketSize);
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
const bucketLength = partiallyFilledBucketLengths[partialBucketIndex - fullBucketCount];
|
|
177
|
+
if (j >= partialBucketBase + bucketLength) {
|
|
178
|
+
partialBucketIndex += 1;
|
|
179
|
+
partialBucketBase += bucketLength;
|
|
180
|
+
}
|
|
181
|
+
bucketIndex = partialBucketIndex;
|
|
182
|
+
}
|
|
183
|
+
if (isHighQualitySplatData) {
|
|
184
|
+
single.x = data.getFloat32(splatOffset + 0, true);
|
|
185
|
+
single.y = data.getFloat32(splatOffset + 4, true);
|
|
186
|
+
single.z = data.getFloat32(splatOffset + 8, true);
|
|
187
|
+
single.sx = data.getFloat32(splatOffset + scaleOffsetBytes + 0, true);
|
|
188
|
+
single.sy = data.getFloat32(splatOffset + scaleOffsetBytes + 4, true);
|
|
189
|
+
single.sz = data.getFloat32(splatOffset + scaleOffsetBytes + 8, true);
|
|
190
|
+
single.qw = data.getFloat32(splatOffset + rotationOffsetBytes + 0, true);
|
|
191
|
+
single.qx = data.getFloat32(splatOffset + rotationOffsetBytes + 4, true);
|
|
192
|
+
single.qy = data.getFloat32(splatOffset + rotationOffsetBytes + 8, true);
|
|
193
|
+
single.qz = data.getFloat32(splatOffset + rotationOffsetBytes + 12, true);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
single.x = (data.getUint16(splatOffset + 0, true) - compressionScaleRange) * compressionScaleFactor + bucketArray[3 * bucketIndex + 0];
|
|
197
|
+
single.y = (data.getUint16(splatOffset + 2, true) - compressionScaleRange) * compressionScaleFactor + bucketArray[3 * bucketIndex + 1];
|
|
198
|
+
single.z = (data.getUint16(splatOffset + 4, true) - compressionScaleRange) * compressionScaleFactor + bucketArray[3 * bucketIndex + 2];
|
|
199
|
+
single.sx = fromHalf(data.getUint16(splatOffset + scaleOffsetBytes + 0, true));
|
|
200
|
+
single.sy = fromHalf(data.getUint16(splatOffset + scaleOffsetBytes + 2, true));
|
|
201
|
+
single.sz = fromHalf(data.getUint16(splatOffset + scaleOffsetBytes + 4, true));
|
|
202
|
+
single.qw = fromHalf(data.getUint16(splatOffset + rotationOffsetBytes + 0, true));
|
|
203
|
+
single.qx = fromHalf(data.getUint16(splatOffset + rotationOffsetBytes + 2, true));
|
|
204
|
+
single.qy = fromHalf(data.getUint16(splatOffset + rotationOffsetBytes + 4, true));
|
|
205
|
+
single.qz = fromHalf(data.getUint16(splatOffset + rotationOffsetBytes + 6, true));
|
|
206
|
+
}
|
|
207
|
+
single.r = data.getUint8(splatOffset + colorOffsetBytes + 0) / 255;
|
|
208
|
+
single.g = data.getUint8(splatOffset + colorOffsetBytes + 1) / 255;
|
|
209
|
+
single.b = data.getUint8(splatOffset + colorOffsetBytes + 2) / 255;
|
|
210
|
+
single.a = data.getUint8(splatOffset + colorOffsetBytes + 3) / 255;
|
|
211
|
+
setFn(j + BlockOffset, single);
|
|
212
|
+
const shOffsetBytes = splatOffset + sphericalHarmonicsOffsetBytes;
|
|
213
|
+
for (let k = 0; k < shComponents; k++) {
|
|
214
|
+
shData[k] = compressionLevel === 0 ?
|
|
215
|
+
data.getFloat32(shOffsetBytes + SHIndex[k] * 4, true) :
|
|
216
|
+
compressionLevel === 1 ?
|
|
217
|
+
fromHalf(data.getUint16(shOffsetBytes + SHIndex[k] * 2, true)) :
|
|
218
|
+
(minSH + data.getUint8(shOffsetBytes + SHIndex[k]) / 255 * (maxSH - minSH));
|
|
219
|
+
}
|
|
220
|
+
for (let k = maxSHSize - 1; k >= shComponents; k--) {
|
|
221
|
+
shData[k] = 0;
|
|
222
|
+
}
|
|
223
|
+
setShFn(j + BlockOffset, shData);
|
|
224
|
+
}
|
|
225
|
+
sectionBase += storageSizeBytes;
|
|
226
|
+
}
|
|
227
|
+
data.finishBlock();
|
|
228
|
+
}
|
|
229
|
+
async write(_stream, _data) {
|
|
230
|
+
throw new Error('Method not implemented.');
|
|
231
|
+
}
|
|
232
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { SplatData } from '../SplatData.js';
|
|
2
|
+
import { IFile } from './IFile.js';
|
|
3
|
+
export declare class LccFile implements IFile {
|
|
4
|
+
private counts;
|
|
5
|
+
private shDegree;
|
|
6
|
+
private meta;
|
|
7
|
+
private refs;
|
|
8
|
+
private load;
|
|
9
|
+
read(stream: ReadableStream<Uint8Array>, contentLength: number, data: SplatData): Promise<void>;
|
|
10
|
+
write(_stream: WritableStream<Uint8Array>, _data: SplatData): Promise<void>;
|
|
11
|
+
}
|
package/dist/file/lcc.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { unzipSync } from 'fflate';
|
|
2
|
+
import { extractFromRootDir } from '../utils/index.js';
|
|
3
|
+
const ZIP_MAGIC = 0x04034b50;
|
|
4
|
+
const SQRT_2 = 1.414213562373095;
|
|
5
|
+
const SQRT_2_INV = 0.7071067811865475;
|
|
6
|
+
function decodeRotation(v) {
|
|
7
|
+
const d0 = (v & 1023) / 1023.0;
|
|
8
|
+
const d1 = ((v >> 10) & 1023) / 1023.0;
|
|
9
|
+
const d2 = ((v >> 20) & 1023) / 1023.0;
|
|
10
|
+
const d3 = (v >> 30) & 3;
|
|
11
|
+
const qx = d0 * SQRT_2 - SQRT_2_INV;
|
|
12
|
+
const qy = d1 * SQRT_2 - SQRT_2_INV;
|
|
13
|
+
const qz = d2 * SQRT_2 - SQRT_2_INV;
|
|
14
|
+
let sum = qx * qx + qy * qy + qz * qz;
|
|
15
|
+
sum = Math.min(1.0, sum);
|
|
16
|
+
const qw = Math.sqrt(1 - sum);
|
|
17
|
+
if (d3 === 0) {
|
|
18
|
+
return [qw, qx, qy, qz];
|
|
19
|
+
}
|
|
20
|
+
else if (d3 === 1) {
|
|
21
|
+
return [qx, qw, qy, qz];
|
|
22
|
+
}
|
|
23
|
+
else if (d3 === 2) {
|
|
24
|
+
return [qx, qy, qw, qz];
|
|
25
|
+
}
|
|
26
|
+
return [qx, qy, qz, qw];
|
|
27
|
+
}
|
|
28
|
+
;
|
|
29
|
+
function DecodePacked_11_10_11(enc) {
|
|
30
|
+
return [
|
|
31
|
+
(enc & 0x7FF) / 2047.0,
|
|
32
|
+
((enc >> 11) & 0x3FF) / 1023.0,
|
|
33
|
+
((enc >> 21) & 0x7FF) / 2047.0,
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
;
|
|
37
|
+
function mix(min, max, s) {
|
|
38
|
+
return (1.0 - s) * min + s * max;
|
|
39
|
+
}
|
|
40
|
+
;
|
|
41
|
+
export class LccFile {
|
|
42
|
+
counts = 0;
|
|
43
|
+
shDegree = 0;
|
|
44
|
+
meta;
|
|
45
|
+
refs = {};
|
|
46
|
+
load(buffer) {
|
|
47
|
+
const view = new DataView(buffer.buffer);
|
|
48
|
+
if (view.getUint32(0, true) !== ZIP_MAGIC) {
|
|
49
|
+
throw new Error('LCC file is not a valid zip archive.');
|
|
50
|
+
}
|
|
51
|
+
this.refs = extractFromRootDir(unzipSync(buffer));
|
|
52
|
+
if (!['meta.lcc', 'index.bin', 'data.bin'].every(name => !!this.refs[name])) {
|
|
53
|
+
throw new Error('LCC file is missing required files.');
|
|
54
|
+
}
|
|
55
|
+
this.meta = JSON.parse(new TextDecoder().decode(this.refs['meta.lcc']));
|
|
56
|
+
this.counts = this.meta.splats[0];
|
|
57
|
+
this.shDegree = !!this.refs['shcoef.bin'] ? 3 : 0;
|
|
58
|
+
}
|
|
59
|
+
async read(stream, contentLength, data) {
|
|
60
|
+
let BlockOffset = 0;
|
|
61
|
+
{
|
|
62
|
+
const buffer = new Uint8Array(contentLength);
|
|
63
|
+
const reader = stream.getReader();
|
|
64
|
+
let offset = 0;
|
|
65
|
+
while (true) {
|
|
66
|
+
const { done, value } = await reader.read();
|
|
67
|
+
if (done) {
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
buffer.set(value, offset);
|
|
71
|
+
offset += value.length;
|
|
72
|
+
}
|
|
73
|
+
this.load(buffer);
|
|
74
|
+
BlockOffset = await data.initBlock(this.counts, this.shDegree);
|
|
75
|
+
}
|
|
76
|
+
const setFn = data.set.bind(data);
|
|
77
|
+
const setShFn = data.setShN.bind(data);
|
|
78
|
+
const { meta, refs } = this;
|
|
79
|
+
const infos = [];
|
|
80
|
+
{
|
|
81
|
+
const index = new DataView(refs['index.bin'].buffer);
|
|
82
|
+
const infoCounts = Math.floor(index.byteLength / (4 + 16 * meta.totalLevel));
|
|
83
|
+
let offset = 0;
|
|
84
|
+
for (let i = 0; i < infoCounts; i++) {
|
|
85
|
+
const x = index.getInt16(offset, true);
|
|
86
|
+
offset += 2;
|
|
87
|
+
const y = index.getInt16(offset, true);
|
|
88
|
+
offset += 2;
|
|
89
|
+
const lods = [];
|
|
90
|
+
for (let j = 0; j < meta.totalLevel; j++) {
|
|
91
|
+
const points = index.getInt32(offset, true);
|
|
92
|
+
offset += 4;
|
|
93
|
+
const ldOffset = Number(index.getBigInt64(offset, true));
|
|
94
|
+
offset += 8;
|
|
95
|
+
const size = index.getInt32(offset, true);
|
|
96
|
+
offset += 4;
|
|
97
|
+
lods.push({ points, offset: ldOffset, size });
|
|
98
|
+
}
|
|
99
|
+
infos.push({ x, y, lods });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const attributes = meta.attributes.reduce((p, c) => {
|
|
103
|
+
p[c.name] = c;
|
|
104
|
+
return p;
|
|
105
|
+
}, {});
|
|
106
|
+
const { scale: { min: scaleMin, max: scaleMax }, shcoef: { min: shMin, max: shMax } } = attributes;
|
|
107
|
+
const single = {
|
|
108
|
+
x: 0, y: 0, z: 0,
|
|
109
|
+
sx: 0, sy: 0, sz: 0,
|
|
110
|
+
qx: 0, qy: 0, qz: 0, qw: 0,
|
|
111
|
+
r: 0, g: 0, b: 0, a: 0,
|
|
112
|
+
shN: [],
|
|
113
|
+
};
|
|
114
|
+
const shData = new Array(45);
|
|
115
|
+
let index = BlockOffset;
|
|
116
|
+
for (let i = 0; i < infos.length; i++) {
|
|
117
|
+
const info = infos[i];
|
|
118
|
+
const { points, offset, size } = info.lods[0];
|
|
119
|
+
const dataview = new DataView(refs['data.bin'].buffer, offset, size);
|
|
120
|
+
const shN = refs['shcoef.bin'] ? new DataView(refs['shcoef.bin'].buffer, offset * 2, size * 2) : undefined;
|
|
121
|
+
for (let j = 0; j < points; j++) {
|
|
122
|
+
const off = j * 32;
|
|
123
|
+
single.x = dataview.getFloat32(off + 0, true);
|
|
124
|
+
single.y = dataview.getFloat32(off + 4, true);
|
|
125
|
+
single.z = dataview.getFloat32(off + 8, true);
|
|
126
|
+
single.r = dataview.getUint8(off + 12) / 255.0;
|
|
127
|
+
single.g = dataview.getUint8(off + 13) / 255.0;
|
|
128
|
+
single.b = dataview.getUint8(off + 14) / 255.0;
|
|
129
|
+
single.a = dataview.getUint8(off + 15) / 255.0;
|
|
130
|
+
single.sx = mix(scaleMin[0], scaleMax[0], dataview.getUint16(off + 16, true) / 65535.0);
|
|
131
|
+
single.sy = mix(scaleMin[1], scaleMax[1], dataview.getUint16(off + 18, true) / 65535.0);
|
|
132
|
+
single.sz = mix(scaleMin[2], scaleMax[2], dataview.getUint16(off + 20, true) / 65535.0);
|
|
133
|
+
const quat = decodeRotation(dataview.getUint32(off + 22, true));
|
|
134
|
+
single.qx = quat[0];
|
|
135
|
+
single.qy = quat[1];
|
|
136
|
+
single.qz = quat[2];
|
|
137
|
+
single.qw = quat[3];
|
|
138
|
+
setFn(index, single);
|
|
139
|
+
if (shN) {
|
|
140
|
+
const shOff = off * 2;
|
|
141
|
+
for (let k = 0; k < 15; k++) {
|
|
142
|
+
const v = DecodePacked_11_10_11(shN.getUint32(shOff + k * 4, true));
|
|
143
|
+
shData[k * 3] = mix(shMin[0], shMax[0], v[0]);
|
|
144
|
+
shData[k * 3 + 1] = mix(shMin[1], shMax[1], v[1]);
|
|
145
|
+
shData[k * 3 + 2] = mix(shMin[2], shMax[2], v[2]);
|
|
146
|
+
}
|
|
147
|
+
setShFn(index, shData);
|
|
148
|
+
}
|
|
149
|
+
index++;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
data.finishBlock();
|
|
153
|
+
}
|
|
154
|
+
async write(_stream, _data) {
|
|
155
|
+
throw new Error('Method not implemented.');
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { SplatData } from '../SplatData.js';
|
|
2
|
+
import { IFile } from './IFile.js';
|
|
3
|
+
export declare class PlyFile implements IFile {
|
|
4
|
+
private littleEndian;
|
|
5
|
+
private comments;
|
|
6
|
+
private elements;
|
|
7
|
+
private isSuperSplatCompressed;
|
|
8
|
+
private counts;
|
|
9
|
+
private shDegree;
|
|
10
|
+
private initHeader;
|
|
11
|
+
read(stream: ReadableStream<Uint8Array>, _contentLength: number, data: SplatData): Promise<void>;
|
|
12
|
+
write(stream: WritableStream<Uint8Array>, data: SplatData, indices?: Uint32Array): Promise<void>;
|
|
13
|
+
}
|