@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
|
@@ -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;
|