@myned-ai/gsplat-flame-avatar-renderer 1.0.6 → 1.0.7
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/README.md +30 -0
- package/dist/gsplat-flame-avatar-renderer.cjs.js +38 -33
- package/dist/gsplat-flame-avatar-renderer.cjs.min.js +1 -1
- package/dist/gsplat-flame-avatar-renderer.cjs.min.js.map +1 -1
- package/dist/gsplat-flame-avatar-renderer.esm.js +38 -33
- package/dist/gsplat-flame-avatar-renderer.esm.min.js +1 -1
- package/dist/gsplat-flame-avatar-renderer.esm.min.js.map +1 -1
- package/package.json +5 -2
- package/src/api/index.js +0 -7
- package/src/buffers/SplatBuffer.js +0 -1394
- package/src/buffers/SplatBufferGenerator.js +0 -41
- package/src/buffers/SplatPartitioner.js +0 -110
- package/src/buffers/UncompressedSplatArray.js +0 -106
- package/src/buffers/index.js +0 -11
- package/src/core/SplatGeometry.js +0 -48
- package/src/core/SplatMesh.js +0 -2627
- package/src/core/SplatScene.js +0 -43
- package/src/core/SplatTree.js +0 -200
- package/src/core/Viewer.js +0 -2746
- package/src/core/index.js +0 -13
- package/src/enums/EngineConstants.js +0 -58
- package/src/enums/LogLevel.js +0 -13
- package/src/enums/RenderMode.js +0 -11
- package/src/enums/SceneFormat.js +0 -21
- package/src/enums/SceneRevealMode.js +0 -11
- package/src/enums/SplatRenderMode.js +0 -10
- package/src/enums/index.js +0 -13
- package/src/errors/ApplicationError.js +0 -185
- package/src/errors/index.js +0 -17
- package/src/flame/FlameAnimator.js +0 -496
- package/src/flame/FlameConstants.js +0 -21
- package/src/flame/FlameTextureManager.js +0 -293
- package/src/flame/index.js +0 -22
- package/src/flame/utils.js +0 -50
- package/src/index.js +0 -39
- package/src/loaders/DirectLoadError.js +0 -14
- package/src/loaders/INRIAV1PlyParser.js +0 -223
- package/src/loaders/PlyLoader.js +0 -519
- package/src/loaders/PlyParser.js +0 -19
- package/src/loaders/PlyParserUtils.js +0 -311
- package/src/loaders/index.js +0 -13
- package/src/materials/SplatMaterial.js +0 -1068
- package/src/materials/SplatMaterial2D.js +0 -358
- package/src/materials/SplatMaterial3D.js +0 -323
- package/src/materials/index.js +0 -11
- package/src/raycaster/Hit.js +0 -37
- package/src/raycaster/Ray.js +0 -123
- package/src/raycaster/Raycaster.js +0 -175
- package/src/raycaster/index.js +0 -10
- package/src/renderer/AnimationManager.js +0 -577
- package/src/renderer/AppConstants.js +0 -101
- package/src/renderer/GaussianSplatRenderer.js +0 -1146
- package/src/renderer/index.js +0 -24
- package/src/utils/BlobUrlManager.js +0 -294
- package/src/utils/EventEmitter.js +0 -349
- package/src/utils/LoaderUtils.js +0 -66
- package/src/utils/Logger.js +0 -171
- package/src/utils/ObjectPool.js +0 -248
- package/src/utils/RenderLoop.js +0 -306
- package/src/utils/Util.js +0 -416
- package/src/utils/ValidationUtils.js +0 -331
- package/src/utils/index.js +0 -18
- package/src/worker/SortWorker.js +0 -284
- package/src/worker/index.js +0 -8
package/src/loaders/PlyLoader.js
DELETED
|
@@ -1,519 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PlyLoader - Loads and parses PLY format Gaussian Splat files
|
|
3
|
-
*
|
|
4
|
-
* Derived from @mkkellogg/gaussian-splats-3d (MIT License)
|
|
5
|
-
* https://github.com/mkkellogg/GaussianSplats3D
|
|
6
|
-
*
|
|
7
|
-
* Simplified for FLAME avatar - only supports INRIAV1 PLY format.
|
|
8
|
-
* Provides both progressive streaming and file-based loading.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { Vector3 } from 'three';
|
|
12
|
-
import { SplatBuffer } from '../buffers/SplatBuffer.js';
|
|
13
|
-
import { UncompressedSplatArray } from '../buffers/UncompressedSplatArray.js';
|
|
14
|
-
import { SplatBufferGenerator } from '../buffers/SplatBufferGenerator.js';
|
|
15
|
-
import { PlyParser } from './PlyParser.js';
|
|
16
|
-
import { PlyParserUtils } from './PlyParserUtils.js';
|
|
17
|
-
import { INRIAV1PlyParser } from './INRIAV1PlyParser.js';
|
|
18
|
-
import { Constants, InternalLoadType, LoaderStatus } from '../enums/EngineConstants.js';
|
|
19
|
-
import { fetchWithProgress, delayedExecute, nativePromiseWithExtractedComponents } from '../utils/Util.js';
|
|
20
|
-
import { getLogger } from '../utils/Logger.js';
|
|
21
|
-
import { ValidationError, NetworkError, ParseError, AssetLoadError } from '../errors/index.js';
|
|
22
|
-
import { validateUrl, validateCallback, validateArrayBuffer } from '../utils/ValidationUtils.js';
|
|
23
|
-
|
|
24
|
-
const logger = getLogger('PlyLoader');
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Store data chunks into a single ArrayBuffer
|
|
28
|
-
*
|
|
29
|
-
* Combines multiple downloaded chunks into a contiguous buffer for parsing.
|
|
30
|
-
* Reallocates buffer if needed to fit all chunks.
|
|
31
|
-
*
|
|
32
|
-
* @private
|
|
33
|
-
* @param {Array<{data: Uint8Array, sizeBytes: number}>} chunks - Array of data chunks
|
|
34
|
-
* @param {ArrayBuffer} [buffer] - Existing buffer to reuse (will reallocate if too small)
|
|
35
|
-
* @returns {ArrayBuffer} Buffer containing all chunk data
|
|
36
|
-
*/
|
|
37
|
-
function storeChunksInBuffer(chunks, buffer) {
|
|
38
|
-
let inBytes = 0;
|
|
39
|
-
for (let chunk of chunks) {
|
|
40
|
-
inBytes += chunk.sizeBytes;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Reallocate if buffer doesn't exist or is too small
|
|
44
|
-
if (!buffer || buffer.byteLength < inBytes) {
|
|
45
|
-
buffer = new ArrayBuffer(inBytes);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Copy all chunks into buffer sequentially
|
|
49
|
-
let offset = 0;
|
|
50
|
-
for (let chunk of chunks) {
|
|
51
|
-
new Uint8Array(buffer, offset, chunk.sizeBytes).set(chunk.data);
|
|
52
|
-
offset += chunk.sizeBytes;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return buffer;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Finalize splat data into a SplatBuffer
|
|
60
|
-
*
|
|
61
|
-
* Converts UncompressedSplatArray into final optimized SplatBuffer format.
|
|
62
|
-
* Applies compression and optimization if requested.
|
|
63
|
-
*
|
|
64
|
-
* @private
|
|
65
|
-
* @param {UncompressedSplatArray} splatData - Parsed splat data
|
|
66
|
-
* @param {boolean} optimizeSplatData - Whether to optimize/compress the data
|
|
67
|
-
* @param {number} minimumAlpha - Minimum alpha threshold for splat culling
|
|
68
|
-
* @param {number} compressionLevel - Compression level (0-2)
|
|
69
|
-
* @param {number} sectionSize - Section size for partitioning
|
|
70
|
-
* @param {Vector3} sceneCenter - Center point of the scene
|
|
71
|
-
* @param {number} blockSize - Block size for spatial partitioning
|
|
72
|
-
* @param {number} bucketSize - Bucket size for sorting
|
|
73
|
-
* @returns {SplatBuffer} Finalized splat buffer ready for rendering
|
|
74
|
-
* @throws {ParseError} If splat data is invalid or finalization fails
|
|
75
|
-
*/
|
|
76
|
-
function finalizeSplatData(splatData, optimizeSplatData, minimumAlpha, compressionLevel, sectionSize, sceneCenter, blockSize, bucketSize) {
|
|
77
|
-
try {
|
|
78
|
-
if (optimizeSplatData) {
|
|
79
|
-
const splatBufferGenerator = SplatBufferGenerator.getStandardGenerator(
|
|
80
|
-
minimumAlpha,
|
|
81
|
-
compressionLevel,
|
|
82
|
-
sectionSize,
|
|
83
|
-
sceneCenter,
|
|
84
|
-
blockSize,
|
|
85
|
-
bucketSize
|
|
86
|
-
);
|
|
87
|
-
return splatBufferGenerator.generateFromUncompressedSplatArray(splatData);
|
|
88
|
-
} else {
|
|
89
|
-
return SplatBuffer.generateFromUncompressedSplatArrays([splatData], minimumAlpha, 0, new Vector3());
|
|
90
|
-
}
|
|
91
|
-
} catch (error) {
|
|
92
|
-
throw new ParseError(
|
|
93
|
-
`Failed to finalize splat data: ${error.message}`,
|
|
94
|
-
'splatData',
|
|
95
|
-
error
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* PlyLoader - Loads and parses PLY format Gaussian Splat files
|
|
102
|
-
*
|
|
103
|
-
* Supports both progressive streaming and complete file loading.
|
|
104
|
-
* Optimized for INRIAV1 PLY format used in FLAME avatars.
|
|
105
|
-
*/
|
|
106
|
-
export class PlyLoader {
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Load PLY file from URL with progressive streaming support
|
|
110
|
-
*
|
|
111
|
-
* Downloads and parses PLY data progressively, enabling render during load.
|
|
112
|
-
* Supports both direct-to-buffer and array-based loading modes.
|
|
113
|
-
*
|
|
114
|
-
* @static
|
|
115
|
-
* @param {string} fileName - URL to the PLY file
|
|
116
|
-
* @param {Function} [onProgress] - Progress callback (percent, percentLabel, status)
|
|
117
|
-
* @param {boolean} [loadDirectoToSplatBuffer=false] - Load directly to SplatBuffer (faster but less flexible)
|
|
118
|
-
* @param {Function} [onProgressiveLoadSectionProgress] - Callback for progressive section updates
|
|
119
|
-
* @param {number} [minimumAlpha=1] - Minimum alpha threshold for splat culling
|
|
120
|
-
* @param {number} [compressionLevel=0] - Compression level (0=none, 1=medium, 2=high)
|
|
121
|
-
* @param {boolean} [optimizeSplatData=true] - Whether to optimize/compress splat data
|
|
122
|
-
* @param {number} [outSphericalHarmonicsDegree=0] - Spherical harmonics degree (0-3)
|
|
123
|
-
* @param {Object} [headers] - HTTP headers for fetch request
|
|
124
|
-
* @param {number} [sectionSize] - Section size for partitioning
|
|
125
|
-
* @param {Vector3} [sceneCenter] - Center point of the scene
|
|
126
|
-
* @param {number} [blockSize] - Block size for spatial partitioning
|
|
127
|
-
* @param {number} [bucketSize] - Bucket size for sorting
|
|
128
|
-
* @returns {Promise<SplatBuffer>} Loaded and parsed splat buffer
|
|
129
|
-
* @throws {ValidationError} If parameters are invalid
|
|
130
|
-
* @throws {NetworkError} If file download fails
|
|
131
|
-
* @throws {ParseError} If PLY parsing fails
|
|
132
|
-
* @throws {AssetLoadError} If asset loading fails
|
|
133
|
-
*/
|
|
134
|
-
static loadFromURL(fileName, onProgress, loadDirectoToSplatBuffer, onProgressiveLoadSectionProgress,
|
|
135
|
-
minimumAlpha, compressionLevel, optimizeSplatData = true, outSphericalHarmonicsDegree = 0,
|
|
136
|
-
headers, sectionSize, sceneCenter, blockSize, bucketSize) {
|
|
137
|
-
|
|
138
|
-
// Validate required parameters
|
|
139
|
-
try {
|
|
140
|
-
validateUrl(fileName);
|
|
141
|
-
} catch (error) {
|
|
142
|
-
logger.error('Invalid URL provided to loadFromURL', { fileName, error });
|
|
143
|
-
throw error;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Validate optional callbacks
|
|
147
|
-
if (onProgress) {
|
|
148
|
-
validateCallback(onProgress, 'onProgress', false);
|
|
149
|
-
}
|
|
150
|
-
if (onProgressiveLoadSectionProgress) {
|
|
151
|
-
validateCallback(onProgressiveLoadSectionProgress, 'onProgressiveLoadSectionProgress', false);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
logger.info('Loading PLY from URL', { fileName, optimizeSplatData, outSphericalHarmonicsDegree });
|
|
155
|
-
|
|
156
|
-
let internalLoadType = loadDirectoToSplatBuffer ? InternalLoadType.DirectToSplatBuffer : InternalLoadType.DirectToSplatArray;
|
|
157
|
-
if (optimizeSplatData) internalLoadType = InternalLoadType.DirectToSplatArray;
|
|
158
|
-
|
|
159
|
-
const directLoadSectionSizeBytes = Constants.ProgressiveLoadSectionSize;
|
|
160
|
-
const splatDataOffsetBytes = SplatBuffer.HeaderSizeBytes + SplatBuffer.SectionHeaderSizeBytes;
|
|
161
|
-
const sectionCount = 1;
|
|
162
|
-
|
|
163
|
-
let directLoadBufferIn;
|
|
164
|
-
let directLoadBufferOut;
|
|
165
|
-
let directLoadSplatBuffer;
|
|
166
|
-
let maxSplatCount = 0;
|
|
167
|
-
let splatCount = 0;
|
|
168
|
-
|
|
169
|
-
let headerLoaded = false;
|
|
170
|
-
let readyToLoadSplatData = false;
|
|
171
|
-
|
|
172
|
-
const loadPromise = nativePromiseWithExtractedComponents();
|
|
173
|
-
|
|
174
|
-
let numBytesStreamed = 0;
|
|
175
|
-
let numBytesParsed = 0;
|
|
176
|
-
let numBytesDownloaded = 0;
|
|
177
|
-
let headerText = '';
|
|
178
|
-
let header = null;
|
|
179
|
-
let chunks = [];
|
|
180
|
-
|
|
181
|
-
let standardLoadUncompressedSplatArray;
|
|
182
|
-
|
|
183
|
-
const textDecoder = new TextDecoder();
|
|
184
|
-
const inriaV1PlyParser = new INRIAV1PlyParser();
|
|
185
|
-
|
|
186
|
-
const localOnProgress = (percent, percentLabel, chunkData) => {
|
|
187
|
-
const loadComplete = percent >= 100;
|
|
188
|
-
|
|
189
|
-
if (chunkData) {
|
|
190
|
-
chunks.push({
|
|
191
|
-
'data': chunkData,
|
|
192
|
-
'sizeBytes': chunkData.byteLength,
|
|
193
|
-
'startBytes': numBytesDownloaded,
|
|
194
|
-
'endBytes': numBytesDownloaded + chunkData.byteLength
|
|
195
|
-
});
|
|
196
|
-
numBytesDownloaded += chunkData.byteLength;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (internalLoadType === InternalLoadType.DownloadBeforeProcessing) {
|
|
200
|
-
if (loadComplete) {
|
|
201
|
-
loadPromise.resolve(chunks);
|
|
202
|
-
}
|
|
203
|
-
} else {
|
|
204
|
-
if (!headerLoaded) {
|
|
205
|
-
headerText += textDecoder.decode(chunkData);
|
|
206
|
-
if (PlyParserUtils.checkTextForEndHeader(headerText)) {
|
|
207
|
-
// FLAME avatars use INRIAV1 format - parse header
|
|
208
|
-
try {
|
|
209
|
-
header = inriaV1PlyParser.decodeHeaderText(headerText);
|
|
210
|
-
maxSplatCount = header.splatCount;
|
|
211
|
-
readyToLoadSplatData = true;
|
|
212
|
-
|
|
213
|
-
logger.debug('PLY header decoded', {
|
|
214
|
-
splatCount: maxSplatCount,
|
|
215
|
-
sphericalHarmonicsDegree: header.sphericalHarmonicsDegree
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
outSphericalHarmonicsDegree = Math.min(outSphericalHarmonicsDegree, header.sphericalHarmonicsDegree);
|
|
219
|
-
} catch (error) {
|
|
220
|
-
const parseError = new ParseError(
|
|
221
|
-
`Failed to decode PLY header: ${error.message}`,
|
|
222
|
-
'headerText',
|
|
223
|
-
error
|
|
224
|
-
);
|
|
225
|
-
logger.error('Header parsing failed', parseError);
|
|
226
|
-
loadPromise.reject(parseError);
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const shDescriptor = SplatBuffer.CompressionLevels[0].SphericalHarmonicsDegrees[outSphericalHarmonicsDegree];
|
|
231
|
-
const splatBufferSizeBytes = splatDataOffsetBytes + shDescriptor.BytesPerSplat * maxSplatCount;
|
|
232
|
-
|
|
233
|
-
if (internalLoadType === InternalLoadType.DirectToSplatBuffer) {
|
|
234
|
-
directLoadBufferOut = new ArrayBuffer(splatBufferSizeBytes);
|
|
235
|
-
SplatBuffer.writeHeaderToBuffer({
|
|
236
|
-
versionMajor: SplatBuffer.CurrentMajorVersion,
|
|
237
|
-
versionMinor: SplatBuffer.CurrentMinorVersion,
|
|
238
|
-
maxSectionCount: sectionCount,
|
|
239
|
-
sectionCount: sectionCount,
|
|
240
|
-
maxSplatCount: maxSplatCount,
|
|
241
|
-
splatCount: splatCount,
|
|
242
|
-
compressionLevel: 0,
|
|
243
|
-
sceneCenter: new Vector3()
|
|
244
|
-
}, directLoadBufferOut);
|
|
245
|
-
} else {
|
|
246
|
-
standardLoadUncompressedSplatArray = new UncompressedSplatArray(outSphericalHarmonicsDegree);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
numBytesStreamed = header.headerSizeBytes;
|
|
250
|
-
numBytesParsed = header.headerSizeBytes;
|
|
251
|
-
headerLoaded = true;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
if (headerLoaded && readyToLoadSplatData) {
|
|
256
|
-
|
|
257
|
-
if (chunks.length > 0) {
|
|
258
|
-
|
|
259
|
-
directLoadBufferIn = storeChunksInBuffer(chunks, directLoadBufferIn);
|
|
260
|
-
|
|
261
|
-
const bytesLoadedSinceLastStreamedSection = numBytesDownloaded - numBytesStreamed;
|
|
262
|
-
if (bytesLoadedSinceLastStreamedSection > directLoadSectionSizeBytes || loadComplete) {
|
|
263
|
-
const numBytesToProcess = numBytesDownloaded - numBytesParsed;
|
|
264
|
-
const addedSplatCount = Math.floor(numBytesToProcess / header.bytesPerSplat);
|
|
265
|
-
const numBytesToParse = addedSplatCount * header.bytesPerSplat;
|
|
266
|
-
const numBytesLeftOver = numBytesToProcess - numBytesToParse;
|
|
267
|
-
const newSplatCount = splatCount + addedSplatCount;
|
|
268
|
-
const parsedDataViewOffset = numBytesParsed - chunks[0].startBytes;
|
|
269
|
-
const dataToParse = new DataView(directLoadBufferIn, parsedDataViewOffset, numBytesToParse);
|
|
270
|
-
|
|
271
|
-
const shDescriptor = SplatBuffer.CompressionLevels[0].SphericalHarmonicsDegrees[outSphericalHarmonicsDegree];
|
|
272
|
-
const outOffset = splatCount * shDescriptor.BytesPerSplat + splatDataOffsetBytes;
|
|
273
|
-
|
|
274
|
-
// Parse splat data with error handling
|
|
275
|
-
try {
|
|
276
|
-
if (internalLoadType === InternalLoadType.DirectToSplatBuffer) {
|
|
277
|
-
inriaV1PlyParser.parseToUncompressedSplatBufferSection(
|
|
278
|
-
header, 0, addedSplatCount - 1, dataToParse,
|
|
279
|
-
0, directLoadBufferOut, outOffset,
|
|
280
|
-
outSphericalHarmonicsDegree
|
|
281
|
-
);
|
|
282
|
-
} else {
|
|
283
|
-
inriaV1PlyParser.parseToUncompressedSplatArraySection(
|
|
284
|
-
header, 0, addedSplatCount - 1, dataToParse,
|
|
285
|
-
0, standardLoadUncompressedSplatArray,
|
|
286
|
-
outSphericalHarmonicsDegree
|
|
287
|
-
);
|
|
288
|
-
}
|
|
289
|
-
} catch (error) {
|
|
290
|
-
const parseError = new ParseError(
|
|
291
|
-
`Failed to parse splat data section: ${error.message}`,
|
|
292
|
-
'splatData',
|
|
293
|
-
error
|
|
294
|
-
);
|
|
295
|
-
logger.error('Splat data parsing failed', { splatCount, addedSplatCount, error });
|
|
296
|
-
loadPromise.reject(parseError);
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
splatCount = newSplatCount;
|
|
301
|
-
|
|
302
|
-
if (internalLoadType === InternalLoadType.DirectToSplatBuffer) {
|
|
303
|
-
if (!directLoadSplatBuffer) {
|
|
304
|
-
SplatBuffer.writeSectionHeaderToBuffer({
|
|
305
|
-
maxSplatCount: maxSplatCount,
|
|
306
|
-
splatCount: splatCount,
|
|
307
|
-
bucketSize: 0,
|
|
308
|
-
bucketCount: 0,
|
|
309
|
-
bucketBlockSize: 0,
|
|
310
|
-
compressionScaleRange: 0,
|
|
311
|
-
storageSizeBytes: 0,
|
|
312
|
-
fullBucketCount: 0,
|
|
313
|
-
partiallyFilledBucketCount: 0,
|
|
314
|
-
sphericalHarmonicsDegree: outSphericalHarmonicsDegree
|
|
315
|
-
}, 0, directLoadBufferOut, SplatBuffer.HeaderSizeBytes);
|
|
316
|
-
directLoadSplatBuffer = new SplatBuffer(directLoadBufferOut, false);
|
|
317
|
-
}
|
|
318
|
-
directLoadSplatBuffer.updateLoadedCounts(1, splatCount);
|
|
319
|
-
if (onProgressiveLoadSectionProgress) {
|
|
320
|
-
onProgressiveLoadSectionProgress(directLoadSplatBuffer, loadComplete);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
numBytesStreamed += directLoadSectionSizeBytes;
|
|
325
|
-
numBytesParsed += numBytesToParse;
|
|
326
|
-
|
|
327
|
-
if (numBytesLeftOver === 0) {
|
|
328
|
-
chunks = [];
|
|
329
|
-
} else {
|
|
330
|
-
let keepChunks = [];
|
|
331
|
-
let keepSize = 0;
|
|
332
|
-
for (let i = chunks.length - 1; i >= 0; i--) {
|
|
333
|
-
const chunk = chunks[i];
|
|
334
|
-
keepSize += chunk.sizeBytes;
|
|
335
|
-
keepChunks.unshift(chunk);
|
|
336
|
-
if (keepSize >= numBytesLeftOver) break;
|
|
337
|
-
}
|
|
338
|
-
chunks = keepChunks;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
if (loadComplete) {
|
|
344
|
-
if (internalLoadType === InternalLoadType.DirectToSplatBuffer) {
|
|
345
|
-
loadPromise.resolve(directLoadSplatBuffer);
|
|
346
|
-
} else {
|
|
347
|
-
loadPromise.resolve(standardLoadUncompressedSplatArray);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// Progress callback with error isolation
|
|
354
|
-
if (onProgress) {
|
|
355
|
-
try {
|
|
356
|
-
onProgress(percent, percentLabel, LoaderStatus.Downloading);
|
|
357
|
-
} catch (error) {
|
|
358
|
-
logger.warn('Error in onProgress callback', error);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
};
|
|
362
|
-
|
|
363
|
-
// Initial progress callback
|
|
364
|
-
if (onProgress) {
|
|
365
|
-
try {
|
|
366
|
-
onProgress(0, '0%', LoaderStatus.Downloading);
|
|
367
|
-
} catch (error) {
|
|
368
|
-
logger.warn('Error in onProgress callback', error);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// Fetch and process the PLY file
|
|
373
|
-
return fetchWithProgress(fileName, localOnProgress, false, headers)
|
|
374
|
-
.then(() => {
|
|
375
|
-
if (onProgress) {
|
|
376
|
-
try {
|
|
377
|
-
onProgress(0, '0%', LoaderStatus.Processing);
|
|
378
|
-
} catch (error) {
|
|
379
|
-
logger.warn('Error in onProgress callback', error);
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
return loadPromise.promise;
|
|
383
|
-
})
|
|
384
|
-
.then((splatData) => {
|
|
385
|
-
if (onProgress) {
|
|
386
|
-
try {
|
|
387
|
-
onProgress(100, '100%', LoaderStatus.Done);
|
|
388
|
-
} catch (error) {
|
|
389
|
-
logger.warn('Error in onProgress callback', error);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
logger.debug('PLY data loaded successfully', {
|
|
394
|
-
internalLoadType,
|
|
395
|
-
splatCount: splatData?.splatCount || 'unknown'
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
// Process based on load type
|
|
399
|
-
if (internalLoadType === InternalLoadType.DownloadBeforeProcessing) {
|
|
400
|
-
const chunkDatas = chunks.map((chunk) => chunk.data);
|
|
401
|
-
return new Blob(chunkDatas).arrayBuffer()
|
|
402
|
-
.then((plyFileData) => {
|
|
403
|
-
return PlyLoader.loadFromFileData(
|
|
404
|
-
plyFileData, minimumAlpha, compressionLevel, optimizeSplatData,
|
|
405
|
-
outSphericalHarmonicsDegree, sectionSize, sceneCenter, blockSize, bucketSize
|
|
406
|
-
);
|
|
407
|
-
})
|
|
408
|
-
.catch((error) => {
|
|
409
|
-
throw new AssetLoadError(
|
|
410
|
-
`Failed to process downloaded PLY data: ${error.message}`,
|
|
411
|
-
fileName,
|
|
412
|
-
error
|
|
413
|
-
);
|
|
414
|
-
});
|
|
415
|
-
} else if (internalLoadType === InternalLoadType.DirectToSplatBuffer) {
|
|
416
|
-
return splatData;
|
|
417
|
-
} else {
|
|
418
|
-
return delayedExecute(() => {
|
|
419
|
-
return finalizeSplatData(
|
|
420
|
-
splatData, optimizeSplatData, minimumAlpha, compressionLevel,
|
|
421
|
-
sectionSize, sceneCenter, blockSize, bucketSize
|
|
422
|
-
);
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
|
-
})
|
|
426
|
-
.catch((error) => {
|
|
427
|
-
// Re-throw custom errors as-is
|
|
428
|
-
if (error instanceof ValidationError ||
|
|
429
|
-
error instanceof NetworkError ||
|
|
430
|
-
error instanceof ParseError ||
|
|
431
|
-
error instanceof AssetLoadError) {
|
|
432
|
-
logger.error('PLY loading failed', { fileName, errorCode: error.code });
|
|
433
|
-
throw error;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// Wrap unexpected errors
|
|
437
|
-
logger.error('Unexpected error loading PLY', { fileName, error });
|
|
438
|
-
throw new AssetLoadError(
|
|
439
|
-
`Unexpected error loading PLY file: ${error.message}`,
|
|
440
|
-
fileName,
|
|
441
|
-
error
|
|
442
|
-
);
|
|
443
|
-
});
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
/**
|
|
447
|
-
* Load PLY file from raw ArrayBuffer data
|
|
448
|
-
*
|
|
449
|
-
* Parses PLY data that has already been downloaded. Useful for loading
|
|
450
|
-
* from local files or when data is provided directly.
|
|
451
|
-
*
|
|
452
|
-
* @static
|
|
453
|
-
* @param {ArrayBuffer} plyFileData - Raw PLY file data as ArrayBuffer
|
|
454
|
-
* @param {number} [minimumAlpha=1] - Minimum alpha threshold for splat culling
|
|
455
|
-
* @param {number} [compressionLevel=0] - Compression level (0=none, 1=medium, 2=high)
|
|
456
|
-
* @param {boolean} [optimizeSplatData=true] - Whether to optimize/compress splat data
|
|
457
|
-
* @param {number} [outSphericalHarmonicsDegree=0] - Spherical harmonics degree (0-3)
|
|
458
|
-
* @param {number} [sectionSize] - Section size for partitioning
|
|
459
|
-
* @param {Vector3} [sceneCenter] - Center point of the scene
|
|
460
|
-
* @param {number} [blockSize] - Block size for spatial partitioning
|
|
461
|
-
* @param {number} [bucketSize] - Bucket size for sorting
|
|
462
|
-
* @returns {Promise<SplatBuffer>} Parsed and finalized splat buffer
|
|
463
|
-
* @throws {ValidationError} If plyFileData is invalid
|
|
464
|
-
* @throws {ParseError} If parsing fails
|
|
465
|
-
*/
|
|
466
|
-
static loadFromFileData(plyFileData, minimumAlpha, compressionLevel, optimizeSplatData, outSphericalHarmonicsDegree = 0,
|
|
467
|
-
sectionSize, sceneCenter, blockSize, bucketSize) {
|
|
468
|
-
// Validate input
|
|
469
|
-
try {
|
|
470
|
-
validateArrayBuffer(plyFileData, 'plyFileData');
|
|
471
|
-
} catch (error) {
|
|
472
|
-
logger.error('Invalid PLY file data', error);
|
|
473
|
-
return Promise.reject(error);
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
logger.info('Loading PLY from file data', {
|
|
477
|
-
sizeBytes: plyFileData.byteLength,
|
|
478
|
-
optimizeSplatData,
|
|
479
|
-
outSphericalHarmonicsDegree
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
return delayedExecute(() => {
|
|
483
|
-
try {
|
|
484
|
-
return PlyParser.parseToUncompressedSplatArray(plyFileData, outSphericalHarmonicsDegree);
|
|
485
|
-
} catch (error) {
|
|
486
|
-
throw new ParseError(
|
|
487
|
-
`Failed to parse PLY file data: ${error.message}`,
|
|
488
|
-
'plyFileData',
|
|
489
|
-
error
|
|
490
|
-
);
|
|
491
|
-
}
|
|
492
|
-
})
|
|
493
|
-
.then((splatArray) => {
|
|
494
|
-
logger.debug('PLY parsed successfully', {
|
|
495
|
-
splatCount: splatArray?.splatCount || 'unknown'
|
|
496
|
-
});
|
|
497
|
-
|
|
498
|
-
return finalizeSplatData(
|
|
499
|
-
splatArray, optimizeSplatData, minimumAlpha, compressionLevel,
|
|
500
|
-
sectionSize, sceneCenter, blockSize, bucketSize
|
|
501
|
-
);
|
|
502
|
-
})
|
|
503
|
-
.catch((error) => {
|
|
504
|
-
// Re-throw custom errors as-is
|
|
505
|
-
if (error instanceof ValidationError || error instanceof ParseError) {
|
|
506
|
-
logger.error('PLY file data loading failed', { errorCode: error.code });
|
|
507
|
-
throw error;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
// Wrap unexpected errors
|
|
511
|
-
logger.error('Unexpected error loading PLY from file data', error);
|
|
512
|
-
throw new ParseError(
|
|
513
|
-
`Unexpected error parsing PLY data: ${error.message}`,
|
|
514
|
-
'plyFileData',
|
|
515
|
-
error
|
|
516
|
-
);
|
|
517
|
-
});
|
|
518
|
-
}
|
|
519
|
-
}
|
package/src/loaders/PlyParser.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PlyParser
|
|
3
|
-
*
|
|
4
|
-
* Derived from @mkkellogg/gaussian-splats-3d (MIT License)
|
|
5
|
-
* https://github.com/mkkellogg/GaussianSplats3D
|
|
6
|
-
*
|
|
7
|
-
* Simplified for FLAME avatar - only supports INRIAV1 PLY format.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { INRIAV1PlyParser } from './INRIAV1PlyParser.js';
|
|
11
|
-
|
|
12
|
-
export class PlyParser {
|
|
13
|
-
|
|
14
|
-
static parseToUncompressedSplatArray(plyBuffer, outSphericalHarmonicsDegree = 0) {
|
|
15
|
-
// FLAME avatars use INRIAV1 PLY format
|
|
16
|
-
return new INRIAV1PlyParser().parseToUncompressedSplatArray(plyBuffer, outSphericalHarmonicsDegree);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
}
|