@myned-ai/gsplat-flame-avatar-renderer 1.0.5 → 1.0.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.
@@ -0,0 +1,185 @@
1
+ /**
2
+ * ApplicationError - Base error class for all application errors
3
+ *
4
+ * Provides structured error handling with error codes and cause tracking.
5
+ * All domain-specific errors should extend this class.
6
+ *
7
+ * @extends Error
8
+ */
9
+ export class ApplicationError extends Error {
10
+ /**
11
+ * Create an ApplicationError
12
+ * @param {string} message - Human-readable error message
13
+ * @param {string} code - Machine-readable error code for programmatic handling
14
+ * @param {Error} [cause=null] - Original error that caused this error (for error chaining)
15
+ */
16
+ constructor(message, code, cause = null) {
17
+ super(message);
18
+ this.name = this.constructor.name;
19
+ this.code = code;
20
+ this.cause = cause;
21
+
22
+ // Capture stack trace, excluding constructor call from it
23
+ if (Error.captureStackTrace) {
24
+ Error.captureStackTrace(this, this.constructor);
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Convert error to JSON for logging/transmission
30
+ * @returns {object} JSON representation of error
31
+ */
32
+ toJSON() {
33
+ return {
34
+ name: this.name,
35
+ message: this.message,
36
+ code: this.code,
37
+ stack: this.stack,
38
+ cause: this.cause ? {
39
+ name: this.cause.name,
40
+ message: this.cause.message
41
+ } : null
42
+ };
43
+ }
44
+ }
45
+
46
+ /**
47
+ * ValidationError - Thrown when input validation fails
48
+ *
49
+ * Used for invalid parameters, out-of-range values, or malformed data.
50
+ *
51
+ * @extends ApplicationError
52
+ */
53
+ export class ValidationError extends ApplicationError {
54
+ /**
55
+ * Create a ValidationError
56
+ * @param {string} message - Validation failure description
57
+ * @param {string} field - Name of the field that failed validation
58
+ * @param {Error} [cause=null] - Original error that caused this validation failure
59
+ */
60
+ constructor(message, field, cause = null) {
61
+ super(message, 'VALIDATION_ERROR', cause);
62
+ this.field = field;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * NetworkError - Thrown when network operations fail
68
+ *
69
+ * Used for fetch failures, timeouts, or HTTP error responses.
70
+ *
71
+ * @extends ApplicationError
72
+ */
73
+ export class NetworkError extends ApplicationError {
74
+ /**
75
+ * Create a NetworkError
76
+ * @param {string} message - Network error description
77
+ * @param {number} [statusCode=0] - HTTP status code (if applicable)
78
+ * @param {Error} [cause=null] - Original error that caused this network failure
79
+ */
80
+ constructor(message, statusCode = 0, cause = null) {
81
+ super(message, 'NETWORK_ERROR', cause);
82
+ this.statusCode = statusCode;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * AssetLoadError - Thrown when asset loading fails
88
+ *
89
+ * Used for failures loading models, textures, or other asset files.
90
+ *
91
+ * @extends ApplicationError
92
+ */
93
+ export class AssetLoadError extends ApplicationError {
94
+ /**
95
+ * Create an AssetLoadError
96
+ * @param {string} message - Asset load failure description
97
+ * @param {string} assetPath - Path to the asset that failed to load
98
+ * @param {Error} [cause=null] - Original error that caused this load failure
99
+ */
100
+ constructor(message, assetPath, cause = null) {
101
+ super(message, 'ASSET_LOAD_ERROR', cause);
102
+ this.assetPath = assetPath;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * ResourceDisposedError - Thrown when attempting to use a disposed resource
108
+ *
109
+ * Used to prevent use-after-dispose bugs.
110
+ *
111
+ * @extends ApplicationError
112
+ */
113
+ export class ResourceDisposedError extends ApplicationError {
114
+ /**
115
+ * Create a ResourceDisposedError
116
+ * @param {string} resourceName - Name of the disposed resource
117
+ */
118
+ constructor(resourceName) {
119
+ super(
120
+ `Cannot use ${resourceName}: resource has been disposed`,
121
+ 'RESOURCE_DISPOSED_ERROR'
122
+ );
123
+ this.resourceName = resourceName;
124
+ }
125
+ }
126
+
127
+ /**
128
+ * InitializationError - Thrown when initialization fails
129
+ *
130
+ * Used when required setup steps fail or prerequisites are not met.
131
+ *
132
+ * @extends ApplicationError
133
+ */
134
+ export class InitializationError extends ApplicationError {
135
+ /**
136
+ * Create an InitializationError
137
+ * @param {string} message - Initialization failure description
138
+ * @param {string} component - Name of component that failed to initialize
139
+ * @param {Error} [cause=null] - Original error that caused this initialization failure
140
+ */
141
+ constructor(message, component, cause = null) {
142
+ super(message, 'INITIALIZATION_ERROR', cause);
143
+ this.component = component;
144
+ }
145
+ }
146
+
147
+ /**
148
+ * ParseError - Thrown when parsing data fails
149
+ *
150
+ * Used for malformed file formats, invalid JSON, or corrupt data.
151
+ *
152
+ * @extends ApplicationError
153
+ */
154
+ export class ParseError extends ApplicationError {
155
+ /**
156
+ * Create a ParseError
157
+ * @param {string} message - Parse failure description
158
+ * @param {string} dataType - Type of data being parsed (e.g., 'JSON', 'PLY', 'GLB')
159
+ * @param {Error} [cause=null] - Original error that caused this parse failure
160
+ */
161
+ constructor(message, dataType, cause = null) {
162
+ super(message, 'PARSE_ERROR', cause);
163
+ this.dataType = dataType;
164
+ }
165
+ }
166
+
167
+ /**
168
+ * ConfigurationError - Thrown when configuration is invalid
169
+ *
170
+ * Used for invalid settings, missing required configuration, or conflicting options.
171
+ *
172
+ * @extends ApplicationError
173
+ */
174
+ export class ConfigurationError extends ApplicationError {
175
+ /**
176
+ * Create a ConfigurationError
177
+ * @param {string} message - Configuration error description
178
+ * @param {string} configKey - Configuration key that is invalid
179
+ * @param {Error} [cause=null] - Original error
180
+ */
181
+ constructor(message, configKey, cause = null) {
182
+ super(message, 'CONFIGURATION_ERROR', cause);
183
+ this.configKey = configKey;
184
+ }
185
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Error classes for structured error handling
3
+ *
4
+ * This module exports all custom error classes used throughout the application.
5
+ * All errors extend ApplicationError for consistent error handling.
6
+ */
7
+
8
+ export {
9
+ ApplicationError,
10
+ ValidationError,
11
+ NetworkError,
12
+ AssetLoadError,
13
+ ResourceDisposedError,
14
+ InitializationError,
15
+ ParseError,
16
+ ConfigurationError
17
+ } from './ApplicationError.js';
@@ -1,60 +1,158 @@
1
1
  /**
2
- * FlameAnimator
3
- *
2
+ * FlameAnimator - FLAME Parametric Head Model Animation Controller
3
+ *
4
4
  * Derived from gaussian-splat-renderer-for-lam
5
- *
6
- * Handles FLAME model animation and skeleton updates:
7
- * - Bone rotation from FLAME parameters
8
- * - Blendshape weight updates
9
- * - Skeleton synchronization with gaussian splat mesh
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
10
13
  */
11
14
 
12
15
  import {
13
- Vector3,
14
- Quaternion,
15
16
  Matrix4,
16
17
  Bone,
17
18
  Skeleton
18
19
  } from 'three';
19
20
 
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');
21
28
 
22
29
  /**
23
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.
24
34
  */
25
35
  export class FlameAnimator {
36
+ /**
37
+ * Create a FlameAnimator instance
38
+ *
39
+ * @constructor
40
+ */
26
41
  constructor() {
42
+ /** @type {THREE.Skeleton|null} FLAME skeleton with 5 bones */
27
43
  this.skeleton = null;
44
+
45
+ /** @type {THREE.Bone[]|null} Array of skeleton bones */
28
46
  this.bones = null;
47
+
48
+ /** @type {Object|null} FLAME animation parameters (rotation, expr, neck_pose, jaw_pose, eyes_pose) */
29
49
  this.flameParams = null;
50
+
51
+ /** @type {Array|null} Linear blend skinning weights */
30
52
  this.lbsWeight = null;
53
+
54
+ /** @type {number} Current animation frame */
31
55
  this.frame = 0;
56
+
57
+ /** @type {number} Total frames in animation sequence */
32
58
  this.totalFrames = 0;
59
+
60
+ /** @type {boolean} Whether FLAME mode is enabled */
33
61
  this.useFlame = true;
62
+
63
+ /** @type {THREE.SkinnedMesh|null} The FLAME avatar mesh */
34
64
  this.avatarMesh = null;
65
+
66
+ /** @type {number} Number of gaussian splats in the mesh */
35
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
+ }
36
84
  }
37
85
 
38
86
  /**
39
- * Initialize with FLAME parameters and LBS weights
40
- * @param {object} flameParams - FLAME animation parameters
41
- * @param {object} boneTree - Skeleton hierarchy data
42
- * @param {Array} lbsWeight - LBS weights for each vertex
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
43
100
  * @param {THREE.SkinnedMesh} avatarMesh - The FLAME avatar mesh
101
+ * @throws {ValidationError} If required parameters are missing or invalid
102
+ * @returns {void}
44
103
  */
45
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
+
46
129
  this.flameParams = flameParams;
47
130
  this.lbsWeight = lbsWeight;
48
131
  this.avatarMesh = avatarMesh;
49
-
50
- if (flameParams && flameParams.rotation) {
132
+
133
+ // Calculate total frames from rotation data
134
+ if (flameParams.rotation && Array.isArray(flameParams.rotation)) {
51
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;
52
140
  }
53
141
 
54
142
  this.gaussianSplatCount = avatarMesh.geometry.attributes.position.count;
55
-
143
+
56
144
  // Build skeleton from bone tree
57
- this.buildSkeleton(boneTree);
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
+ }
58
156
  }
59
157
 
60
158
  /**
@@ -139,58 +237,115 @@ export class FlameAnimator {
139
237
  }
140
238
 
141
239
  /**
142
- * Set bone rotation from axis-angle or quaternion
143
- * @param {THREE.Bone} bone - Target bone
144
- * @param {Array} angles - Rotation values
145
- * @param {boolean} isQuat - Whether angles are quaternion
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}
146
250
  */
147
251
  setBoneRotation(bone, angles, isQuat = false) {
148
- let quaternion;
149
-
150
- if (isQuat) {
151
- quaternion = new Quaternion(angles[0], angles[1], angles[2], angles[3]);
152
- } else {
153
- // Axis-angle representation
154
- const value = new Vector3(angles[0], angles[1], angles[2]);
155
- const angleInRadians = value.length();
156
- const axis = value.normalize();
157
- quaternion = new Quaternion().setFromAxisAngle(axis, angleInRadians);
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);
158
293
  }
159
-
160
- bone.quaternion.copy(quaternion);
161
- bone.updateMatrixWorld(true);
162
294
  }
163
295
 
164
296
  /**
165
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
166
308
  */
167
309
  updateFlameBones() {
168
- if (!this.flameParams || !this.skeleton) return {};
310
+ this._assertNotDisposed();
169
311
 
170
- const frame = this.frame;
171
- const bsWeight = this.flameParams['expr'][frame];
172
-
173
- // Root bone rotation
174
- const rootAngle = this.flameParams['rotation'][frame];
175
- this.setBoneRotation(this.skeleton.bones[0], rootAngle);
312
+ if (!this.flameParams || !this.skeleton) {
313
+ logger.warn('Cannot update bones: flameParams or skeleton not initialized');
314
+ return {};
315
+ }
176
316
 
177
- // Neck rotation
178
- const neckAngle = this.flameParams['neck_pose'][frame];
179
- this.setBoneRotation(this.skeleton.bones[1], neckAngle);
317
+ const frame = this.frame;
180
318
 
181
- // Jaw rotation
182
- const jawAngle = this.flameParams['jaw_pose'][frame];
183
- this.setBoneRotation(this.skeleton.bones[2], jawAngle);
319
+ // Get blendshape weights for this frame
320
+ const bsWeight = this.flameParams['expr'][frame];
184
321
 
185
- // Eyes rotation (combined in eyes_pose array)
186
- const eyesAngle = this.flameParams['eyes_pose'][frame];
187
- this.setBoneRotation(this.skeleton.bones[3], eyesAngle);
188
- this.setBoneRotation(this.skeleton.bones[4], [eyesAngle[3], eyesAngle[4], eyesAngle[5]]);
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
+ }
189
344
 
190
- // Update skeleton matrices
345
+ // Update skeleton matrices after all rotations are set
191
346
  this.skeleton.update();
192
347
 
193
- // Get updated bone matrices
348
+ // Get updated bone matrices for shader
194
349
  const numBones = 5;
195
350
  const bonesMatrix = FlameTextureManager.getUpdatedBoneMatrices(this.skeleton, numBones);
196
351
 
@@ -233,39 +388,109 @@ export class FlameAnimator {
233
388
 
234
389
  /**
235
390
  * Set current animation frame
236
- * @param {number} frame - Frame number
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}
237
395
  */
238
396
  setFrame(frame) {
239
- this.frame = frame % this.totalFrames;
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
+ }
240
408
  }
241
409
 
242
410
  /**
243
- * Advance to next frame
411
+ * Advance to next frame in animation sequence
412
+ *
413
+ * Automatically wraps to frame 0 after reaching totalFrames.
414
+ *
415
+ * @returns {void}
244
416
  */
245
417
  nextFrame() {
246
- this.frame = (this.frame + 1) % this.totalFrames;
418
+ this._assertNotDisposed();
419
+
420
+ if (this.totalFrames > 0) {
421
+ this.frame = (this.frame + 1) % this.totalFrames;
422
+ }
247
423
  }
248
424
 
249
425
  /**
250
426
  * Get skeleton for external use
427
+ *
428
+ * @returns {THREE.Skeleton|null} The FLAME skeleton or null if not initialized
251
429
  */
252
430
  getSkeleton() {
431
+ this._assertNotDisposed();
253
432
  return this.skeleton;
254
433
  }
255
434
 
256
435
  /**
257
436
  * Get current frame number
437
+ *
438
+ * @returns {number} Current animation frame index
258
439
  */
259
440
  getFrame() {
441
+ this._assertNotDisposed();
260
442
  return this.frame;
261
443
  }
262
444
 
263
445
  /**
264
446
  * Get total frame count
447
+ *
448
+ * @returns {number} Total number of animation frames
265
449
  */
266
450
  getTotalFrames() {
451
+ this._assertNotDisposed();
267
452
  return this.totalFrames;
268
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
+ }
269
494
  }
270
495
 
271
496
  export default FlameAnimator;