@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.
Files changed (64) hide show
  1. package/README.md +30 -0
  2. package/dist/gsplat-flame-avatar-renderer.cjs.js +38 -33
  3. package/dist/gsplat-flame-avatar-renderer.cjs.min.js +1 -1
  4. package/dist/gsplat-flame-avatar-renderer.cjs.min.js.map +1 -1
  5. package/dist/gsplat-flame-avatar-renderer.esm.js +38 -33
  6. package/dist/gsplat-flame-avatar-renderer.esm.min.js +1 -1
  7. package/dist/gsplat-flame-avatar-renderer.esm.min.js.map +1 -1
  8. package/package.json +5 -2
  9. package/src/api/index.js +0 -7
  10. package/src/buffers/SplatBuffer.js +0 -1394
  11. package/src/buffers/SplatBufferGenerator.js +0 -41
  12. package/src/buffers/SplatPartitioner.js +0 -110
  13. package/src/buffers/UncompressedSplatArray.js +0 -106
  14. package/src/buffers/index.js +0 -11
  15. package/src/core/SplatGeometry.js +0 -48
  16. package/src/core/SplatMesh.js +0 -2627
  17. package/src/core/SplatScene.js +0 -43
  18. package/src/core/SplatTree.js +0 -200
  19. package/src/core/Viewer.js +0 -2746
  20. package/src/core/index.js +0 -13
  21. package/src/enums/EngineConstants.js +0 -58
  22. package/src/enums/LogLevel.js +0 -13
  23. package/src/enums/RenderMode.js +0 -11
  24. package/src/enums/SceneFormat.js +0 -21
  25. package/src/enums/SceneRevealMode.js +0 -11
  26. package/src/enums/SplatRenderMode.js +0 -10
  27. package/src/enums/index.js +0 -13
  28. package/src/errors/ApplicationError.js +0 -185
  29. package/src/errors/index.js +0 -17
  30. package/src/flame/FlameAnimator.js +0 -496
  31. package/src/flame/FlameConstants.js +0 -21
  32. package/src/flame/FlameTextureManager.js +0 -293
  33. package/src/flame/index.js +0 -22
  34. package/src/flame/utils.js +0 -50
  35. package/src/index.js +0 -39
  36. package/src/loaders/DirectLoadError.js +0 -14
  37. package/src/loaders/INRIAV1PlyParser.js +0 -223
  38. package/src/loaders/PlyLoader.js +0 -519
  39. package/src/loaders/PlyParser.js +0 -19
  40. package/src/loaders/PlyParserUtils.js +0 -311
  41. package/src/loaders/index.js +0 -13
  42. package/src/materials/SplatMaterial.js +0 -1068
  43. package/src/materials/SplatMaterial2D.js +0 -358
  44. package/src/materials/SplatMaterial3D.js +0 -323
  45. package/src/materials/index.js +0 -11
  46. package/src/raycaster/Hit.js +0 -37
  47. package/src/raycaster/Ray.js +0 -123
  48. package/src/raycaster/Raycaster.js +0 -175
  49. package/src/raycaster/index.js +0 -10
  50. package/src/renderer/AnimationManager.js +0 -577
  51. package/src/renderer/AppConstants.js +0 -101
  52. package/src/renderer/GaussianSplatRenderer.js +0 -1146
  53. package/src/renderer/index.js +0 -24
  54. package/src/utils/BlobUrlManager.js +0 -294
  55. package/src/utils/EventEmitter.js +0 -349
  56. package/src/utils/LoaderUtils.js +0 -66
  57. package/src/utils/Logger.js +0 -171
  58. package/src/utils/ObjectPool.js +0 -248
  59. package/src/utils/RenderLoop.js +0 -306
  60. package/src/utils/Util.js +0 -416
  61. package/src/utils/ValidationUtils.js +0 -331
  62. package/src/utils/index.js +0 -18
  63. package/src/worker/SortWorker.js +0 -284
  64. package/src/worker/index.js +0 -8
@@ -1,496 +0,0 @@
1
- /**
2
- * FlameAnimator - FLAME Parametric Head Model Animation Controller
3
- *
4
- * Derived from gaussian-splat-renderer-for-lam
5
- *
6
- * Manages FLAME (Faces Learned with an Articulated Model and Expressions) model animation:
7
- * - Skeletal bone rotation from FLAME parameters
8
- * - Blendshape weight updates for facial expressions
9
- * - Skeleton-mesh synchronization for gaussian splat rendering
10
- * - Linear blend skinning (LBS) with bone weights
11
- *
12
- * FLAME uses 5 bones: root, neck, jaw, left eye, right eye
13
- */
14
-
15
- import {
16
- Matrix4,
17
- Bone,
18
- Skeleton
19
- } from 'three';
20
-
21
- import { FlameTextureManager } from './FlameTextureManager.js';
22
- import { getLogger } from '../utils/Logger.js';
23
- import { ValidationError, ResourceDisposedError } from '../errors/index.js';
24
- import { validateRequiredProperties } from '../utils/ValidationUtils.js';
25
- import { vector3Pool, quaternionPool } from '../utils/ObjectPool.js';
26
-
27
- const logger = getLogger('FlameAnimator');
28
-
29
- /**
30
- * FlameAnimator - Manages FLAME parametric head model animation
31
- *
32
- * Provides skeletal animation, blendshape morphing, and mesh deformation
33
- * for FLAME-based avatar heads with gaussian splatting rendering.
34
- */
35
- export class FlameAnimator {
36
- /**
37
- * Create a FlameAnimator instance
38
- *
39
- * @constructor
40
- */
41
- constructor() {
42
- /** @type {THREE.Skeleton|null} FLAME skeleton with 5 bones */
43
- this.skeleton = null;
44
-
45
- /** @type {THREE.Bone[]|null} Array of skeleton bones */
46
- this.bones = null;
47
-
48
- /** @type {Object|null} FLAME animation parameters (rotation, expr, neck_pose, jaw_pose, eyes_pose) */
49
- this.flameParams = null;
50
-
51
- /** @type {Array|null} Linear blend skinning weights */
52
- this.lbsWeight = null;
53
-
54
- /** @type {number} Current animation frame */
55
- this.frame = 0;
56
-
57
- /** @type {number} Total frames in animation sequence */
58
- this.totalFrames = 0;
59
-
60
- /** @type {boolean} Whether FLAME mode is enabled */
61
- this.useFlame = true;
62
-
63
- /** @type {THREE.SkinnedMesh|null} The FLAME avatar mesh */
64
- this.avatarMesh = null;
65
-
66
- /** @type {number} Number of gaussian splats in the mesh */
67
- this.gaussianSplatCount = 0;
68
-
69
- /** @type {boolean} Whether animator has been disposed */
70
- this._disposed = false;
71
-
72
- logger.debug('FlameAnimator instance created');
73
- }
74
-
75
- /**
76
- * Assert animator is not disposed
77
- * @private
78
- * @throws {ResourceDisposedError} If animator has been disposed
79
- */
80
- _assertNotDisposed() {
81
- if (this._disposed) {
82
- throw new ResourceDisposedError('FlameAnimator has been disposed');
83
- }
84
- }
85
-
86
- /**
87
- * Initialize animator with FLAME parameters and skeleton data
88
- *
89
- * Sets up the FLAME animation system with parameters for bone rotations,
90
- * expressions, and linear blend skinning weights.
91
- *
92
- * @param {Object} flameParams - FLAME animation parameters
93
- * @param {Array} flameParams.rotation - Root bone rotations per frame (axis-angle)
94
- * @param {Array} flameParams.expr - Expression blendshape weights per frame
95
- * @param {Array} flameParams.neck_pose - Neck bone rotations per frame
96
- * @param {Array} flameParams.jaw_pose - Jaw bone rotations per frame
97
- * @param {Array} flameParams.eyes_pose - Eye bone rotations per frame (6 values: left 3, right 3)
98
- * @param {Object|Array} boneTree - Skeleton hierarchy definition
99
- * @param {Array} lbsWeight - Linear blend skinning weights for each vertex
100
- * @param {THREE.SkinnedMesh} avatarMesh - The FLAME avatar mesh
101
- * @throws {ValidationError} If required parameters are missing or invalid
102
- * @returns {void}
103
- */
104
- initialize(flameParams, boneTree, lbsWeight, avatarMesh) {
105
- this._assertNotDisposed();
106
-
107
- // Validate required parameters
108
- try {
109
- validateRequiredProperties(flameParams, ['rotation', 'expr', 'neck_pose', 'jaw_pose', 'eyes_pose'], 'flameParams');
110
- } catch (error) {
111
- logger.error('Invalid flameParams', error);
112
- throw error;
113
- }
114
-
115
- if (!avatarMesh || !avatarMesh.geometry || !avatarMesh.geometry.attributes || !avatarMesh.geometry.attributes.position) {
116
- const error = new ValidationError(
117
- 'avatarMesh must have geometry with position attribute',
118
- 'avatarMesh'
119
- );
120
- logger.error('Invalid avatarMesh', error);
121
- throw error;
122
- }
123
-
124
- logger.info('Initializing FlameAnimator', {
125
- frameCount: flameParams.rotation?.length,
126
- splatCount: avatarMesh.geometry.attributes.position.count
127
- });
128
-
129
- this.flameParams = flameParams;
130
- this.lbsWeight = lbsWeight;
131
- this.avatarMesh = avatarMesh;
132
-
133
- // Calculate total frames from rotation data
134
- if (flameParams.rotation && Array.isArray(flameParams.rotation)) {
135
- this.totalFrames = flameParams.rotation.length;
136
- logger.debug('Animation has frames', { totalFrames: this.totalFrames });
137
- } else {
138
- logger.warn('No rotation data found, totalFrames set to 0');
139
- this.totalFrames = 0;
140
- }
141
-
142
- this.gaussianSplatCount = avatarMesh.geometry.attributes.position.count;
143
-
144
- // Build skeleton from bone tree
145
- try {
146
- this.buildSkeleton(boneTree);
147
- logger.debug('Skeleton built successfully', { boneCount: this.bones?.length });
148
- } catch (error) {
149
- logger.error('Failed to build skeleton', error);
150
- throw new ValidationError(
151
- `Failed to build skeleton: ${error.message}`,
152
- 'boneTree',
153
- error
154
- );
155
- }
156
- }
157
-
158
- /**
159
- * Build skeleton from bone tree data
160
- * @param {object} boneTree - Skeleton hierarchy definition
161
- */
162
- buildSkeleton(boneTree) {
163
- if (!boneTree) return;
164
-
165
- this.bones = [];
166
- const boneInverses = [];
167
-
168
- // Create bones from tree structure
169
- const createBone = (boneData, parentBone = null) => {
170
- const bone = new Bone();
171
- bone.name = boneData.name || `bone_${this.bones.length}`;
172
-
173
- if (boneData.position) {
174
- bone.position.fromArray(boneData.position);
175
- }
176
- if (boneData.rotation) {
177
- bone.rotation.fromArray(boneData.rotation);
178
- }
179
- if (boneData.scale) {
180
- bone.scale.fromArray(boneData.scale);
181
- }
182
-
183
- if (parentBone) {
184
- parentBone.add(bone);
185
- }
186
-
187
- this.bones.push(bone);
188
-
189
- // Create inverse bind matrix
190
- const inverseMatrix = new Matrix4();
191
- if (boneData.inverseBindMatrix) {
192
- inverseMatrix.fromArray(boneData.inverseBindMatrix);
193
- } else {
194
- bone.updateMatrixWorld(true);
195
- inverseMatrix.copy(bone.matrixWorld).invert();
196
- }
197
- boneInverses.push(inverseMatrix);
198
-
199
- // Process children
200
- if (boneData.children) {
201
- boneData.children.forEach(child => createBone(child, bone));
202
- }
203
- };
204
-
205
- // If boneTree is array, create bones directly
206
- if (Array.isArray(boneTree)) {
207
- boneTree.forEach((boneData, index) => {
208
- const bone = new Bone();
209
- bone.name = boneData.name || `bone_${index}`;
210
-
211
- if (boneData.position) {
212
- bone.position.fromArray(boneData.position);
213
- }
214
-
215
- this.bones.push(bone);
216
-
217
- const inverseMatrix = new Matrix4();
218
- if (boneData.inverseBindMatrix) {
219
- inverseMatrix.fromArray(boneData.inverseBindMatrix);
220
- }
221
- boneInverses.push(inverseMatrix);
222
- });
223
-
224
- // Set up hierarchy (FLAME: root -> neck -> jaw, eyes)
225
- if (this.bones.length >= 5) {
226
- this.bones[0].add(this.bones[1]); // root -> neck
227
- this.bones[1].add(this.bones[2]); // neck -> jaw
228
- this.bones[1].add(this.bones[3]); // neck -> leftEye
229
- this.bones[1].add(this.bones[4]); // neck -> rightEye
230
- }
231
- } else if (boneTree.root) {
232
- createBone(boneTree.root);
233
- }
234
-
235
- // Create skeleton
236
- this.skeleton = new Skeleton(this.bones, boneInverses);
237
- }
238
-
239
- /**
240
- * Set bone rotation from axis-angle or quaternion representation
241
- *
242
- * Converts rotation data to quaternion and applies it to the bone.
243
- * Uses object pooling to avoid allocations in animation loop.
244
- *
245
- * @param {THREE.Bone} bone - Target bone to rotate
246
- * @param {Array<number>} angles - Rotation values (3 for axis-angle, 4 for quaternion)
247
- * @param {boolean} [isQuat=false] - Whether angles are quaternion [x, y, z, w]
248
- * @throws {ValidationError} If bone or angles are invalid
249
- * @returns {void}
250
- */
251
- setBoneRotation(bone, angles, isQuat = false) {
252
- this._assertNotDisposed();
253
-
254
- if (!bone || !angles || !Array.isArray(angles)) {
255
- throw new ValidationError(
256
- 'bone and angles array are required',
257
- 'bone/angles'
258
- );
259
- }
260
-
261
- // Use object pooling for temp objects
262
- const quaternion = quaternionPool.acquire();
263
-
264
- try {
265
- if (isQuat) {
266
- // Direct quaternion (XYZW format)
267
- if (angles.length < 4) {
268
- throw new ValidationError('Quaternion requires 4 values', 'angles');
269
- }
270
- quaternion.set(angles[0], angles[1], angles[2], angles[3]);
271
- } else {
272
- // Axis-angle representation: [x, y, z] where magnitude is angle
273
- if (angles.length < 3) {
274
- throw new ValidationError('Axis-angle requires 3 values', 'angles');
275
- }
276
-
277
- const axis = vector3Pool.acquire();
278
- try {
279
- axis.set(angles[0], angles[1], angles[2]);
280
- const angleInRadians = axis.length();
281
- axis.normalize();
282
- quaternion.setFromAxisAngle(axis, angleInRadians);
283
- } finally {
284
- vector3Pool.release(axis);
285
- }
286
- }
287
-
288
- // Apply rotation to bone
289
- bone.quaternion.copy(quaternion);
290
- bone.updateMatrixWorld(true);
291
- } finally {
292
- quaternionPool.release(quaternion);
293
- }
294
- }
295
-
296
- /**
297
- * Update FLAME bones from current frame parameters
298
- *
299
- * Applies bone rotations for all 5 FLAME bones (root, neck, jaw, left eye, right eye)
300
- * from the current animation frame. Updates skeleton matrices and returns data
301
- * needed for mesh deformation.
302
- *
303
- * @returns {Object} Bone and blendshape data for rendering
304
- * @returns {Array} return.bsWeight - Expression blendshape weights for current frame
305
- * @returns {Float32Array} return.bonesMatrix - Updated bone transformation matrices
306
- * @returns {number} return.bonesNum - Number of bones (always 5 for FLAME)
307
- * @returns {Array} return.bonesWeight - LBS weights for vertices
308
- */
309
- updateFlameBones() {
310
- this._assertNotDisposed();
311
-
312
- if (!this.flameParams || !this.skeleton) {
313
- logger.warn('Cannot update bones: flameParams or skeleton not initialized');
314
- return {};
315
- }
316
-
317
- const frame = this.frame;
318
-
319
- // Get blendshape weights for this frame
320
- const bsWeight = this.flameParams['expr'][frame];
321
-
322
- // Apply bone rotations for current frame
323
- try {
324
- // Root bone rotation (global head orientation)
325
- const rootAngle = this.flameParams['rotation'][frame];
326
- this.setBoneRotation(this.skeleton.bones[0], rootAngle);
327
-
328
- // Neck rotation
329
- const neckAngle = this.flameParams['neck_pose'][frame];
330
- this.setBoneRotation(this.skeleton.bones[1], neckAngle);
331
-
332
- // Jaw rotation
333
- const jawAngle = this.flameParams['jaw_pose'][frame];
334
- this.setBoneRotation(this.skeleton.bones[2], jawAngle);
335
-
336
- // Eyes rotation (6 values: left eye xyz, right eye xyz)
337
- const eyesAngle = this.flameParams['eyes_pose'][frame];
338
- this.setBoneRotation(this.skeleton.bones[3], eyesAngle.slice(0, 3)); // Left eye
339
- this.setBoneRotation(this.skeleton.bones[4], eyesAngle.slice(3, 6)); // Right eye
340
- } catch (error) {
341
- logger.error('Error setting bone rotations', { frame, error });
342
- throw error;
343
- }
344
-
345
- // Update skeleton matrices after all rotations are set
346
- this.skeleton.update();
347
-
348
- // Get updated bone matrices for shader
349
- const numBones = 5;
350
- const bonesMatrix = FlameTextureManager.getUpdatedBoneMatrices(this.skeleton, numBones);
351
-
352
- return {
353
- bsWeight,
354
- bonesMatrix,
355
- bonesNum: numBones,
356
- bonesWeight: this.lbsWeight
357
- };
358
- }
359
-
360
- /**
361
- * Run morph update - main animation loop function
362
- * @param {SplatMesh} splatMesh - The gaussian splat mesh to update
363
- */
364
- runMorphUpdate(splatMesh) {
365
- const morphedMesh = new Float32Array(
366
- this.avatarMesh.geometry.attributes.position.array
367
- );
368
-
369
- splatMesh.gaussianSplatCount = this.gaussianSplatCount;
370
- splatMesh.bonesNum = 5;
371
-
372
- if (this.useFlame) {
373
- const updateData = this.updateFlameBones();
374
- splatMesh.bsWeight = updateData.bsWeight;
375
- splatMesh.bonesMatrix = updateData.bonesMatrix;
376
- splatMesh.bonesNum = updateData.bonesNum;
377
- splatMesh.bonesWeight = updateData.bonesWeight;
378
- }
379
-
380
- splatMesh.morphedMesh = morphedMesh;
381
-
382
- // Update textures
383
- const splatNum = splatMesh.morphedMesh.length / 3;
384
- if (splatMesh.splatDataTextures && splatMesh.splatDataTextures['flameModel']) {
385
- splatMesh.updateTextureAfterBSAndSkeleton(0, splatNum - 1, this.useFlame);
386
- }
387
- }
388
-
389
- /**
390
- * Set current animation frame
391
- *
392
- * @param {number} frame - Frame number to set (will wrap if exceeds totalFrames)
393
- * @throws {ValidationError} If frame is not a valid number
394
- * @returns {void}
395
- */
396
- setFrame(frame) {
397
- this._assertNotDisposed();
398
-
399
- if (typeof frame !== 'number' || isNaN(frame)) {
400
- throw new ValidationError('frame must be a valid number', 'frame');
401
- }
402
-
403
- if (this.totalFrames > 0) {
404
- this.frame = ((frame % this.totalFrames) + this.totalFrames) % this.totalFrames; // Handle negative
405
- } else {
406
- this.frame = 0;
407
- }
408
- }
409
-
410
- /**
411
- * Advance to next frame in animation sequence
412
- *
413
- * Automatically wraps to frame 0 after reaching totalFrames.
414
- *
415
- * @returns {void}
416
- */
417
- nextFrame() {
418
- this._assertNotDisposed();
419
-
420
- if (this.totalFrames > 0) {
421
- this.frame = (this.frame + 1) % this.totalFrames;
422
- }
423
- }
424
-
425
- /**
426
- * Get skeleton for external use
427
- *
428
- * @returns {THREE.Skeleton|null} The FLAME skeleton or null if not initialized
429
- */
430
- getSkeleton() {
431
- this._assertNotDisposed();
432
- return this.skeleton;
433
- }
434
-
435
- /**
436
- * Get current frame number
437
- *
438
- * @returns {number} Current animation frame index
439
- */
440
- getFrame() {
441
- this._assertNotDisposed();
442
- return this.frame;
443
- }
444
-
445
- /**
446
- * Get total frame count
447
- *
448
- * @returns {number} Total number of animation frames
449
- */
450
- getTotalFrames() {
451
- this._assertNotDisposed();
452
- return this.totalFrames;
453
- }
454
-
455
- /**
456
- * Dispose animator and free resources
457
- *
458
- * Properly cleans up:
459
- * - Skeleton and bones
460
- * - References to meshes and parameters
461
- *
462
- * @returns {void}
463
- */
464
- dispose() {
465
- if (this._disposed) {
466
- logger.warn('FlameAnimator.dispose() called on already disposed instance');
467
- return;
468
- }
469
-
470
- logger.info('Disposing FlameAnimator');
471
-
472
- // Dispose skeleton (bones are part of skeleton)
473
- if (this.skeleton) {
474
- this.skeleton.dispose();
475
- this.skeleton = null;
476
- }
477
-
478
- // Nullify all references to aid GC
479
- this.bones = null;
480
- this.flameParams = null;
481
- this.lbsWeight = null;
482
- this.avatarMesh = null;
483
-
484
- // Reset state
485
- this.frame = 0;
486
- this.totalFrames = 0;
487
- this.gaussianSplatCount = 0;
488
-
489
- // Mark as disposed
490
- this._disposed = true;
491
-
492
- logger.debug('FlameAnimator disposed successfully');
493
- }
494
- }
495
-
496
- export default FlameAnimator;
@@ -1,21 +0,0 @@
1
- /**
2
- * FlameConstants
3
- *
4
- * Derived from gaussian-splat-renderer-for-lam
5
- * FLAME-specific constants for parametric head model.
6
- *
7
- * Note: General engine constants (MaxScenes, etc.) are in enums/EngineConstants.js
8
- */
9
-
10
- export const Constants = {
11
- // FLAME model constants
12
- FlameBonesCount: 5, // root, neck, jaw, leftEye, rightEye
13
- DefaultBlendshapeCount: 52, // ARKit blendshapes
14
-
15
- // Texture sizes for FLAME data
16
- FlameModelTextureSize: { width: 4096, height: 2048 },
17
- BoneTextureSize: { width: 4, height: 32 },
18
- BoneWeightTextureSize: { width: 512, height: 512 }
19
- };
20
-
21
- export default Constants;