@myned-ai/gsplat-flame-avatar-renderer 1.0.2 → 1.0.4

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 (66) hide show
  1. package/README.md +1 -21
  2. package/dist/gsplat-flame-avatar-renderer.cjs.js +12875 -0
  3. package/dist/{gsplat-flame-avatar-renderer.umd.js.map → gsplat-flame-avatar-renderer.cjs.js.map} +1 -1
  4. package/dist/gsplat-flame-avatar-renderer.esm.js +1 -1
  5. package/package.json +5 -3
  6. package/src/api/index.js +7 -0
  7. package/src/buffers/SplatBuffer.js +1394 -0
  8. package/src/buffers/SplatBufferGenerator.js +41 -0
  9. package/src/buffers/SplatPartitioner.js +110 -0
  10. package/src/buffers/UncompressedSplatArray.js +106 -0
  11. package/src/buffers/index.js +11 -0
  12. package/src/core/SplatGeometry.js +48 -0
  13. package/src/core/SplatMesh.js +2620 -0
  14. package/src/core/SplatScene.js +43 -0
  15. package/src/core/SplatTree.js +200 -0
  16. package/src/core/Viewer.js +2895 -0
  17. package/src/core/index.js +13 -0
  18. package/src/enums/EngineConstants.js +58 -0
  19. package/src/enums/LogLevel.js +13 -0
  20. package/src/enums/RenderMode.js +11 -0
  21. package/src/enums/SceneFormat.js +21 -0
  22. package/src/enums/SceneRevealMode.js +11 -0
  23. package/src/enums/SplatRenderMode.js +10 -0
  24. package/src/enums/index.js +13 -0
  25. package/src/flame/FlameAnimator.js +271 -0
  26. package/src/flame/FlameConstants.js +21 -0
  27. package/src/flame/FlameTextureManager.js +293 -0
  28. package/src/flame/index.js +22 -0
  29. package/src/flame/utils.js +50 -0
  30. package/src/index.js +39 -0
  31. package/src/loaders/DirectLoadError.js +14 -0
  32. package/src/loaders/INRIAV1PlyParser.js +223 -0
  33. package/src/loaders/PlyLoader.js +261 -0
  34. package/src/loaders/PlyParser.js +19 -0
  35. package/src/loaders/PlyParserUtils.js +311 -0
  36. package/src/loaders/index.js +13 -0
  37. package/src/materials/SplatMaterial.js +1065 -0
  38. package/src/materials/SplatMaterial2D.js +358 -0
  39. package/src/materials/SplatMaterial3D.js +278 -0
  40. package/src/materials/index.js +11 -0
  41. package/src/raycaster/Hit.js +37 -0
  42. package/src/raycaster/Ray.js +123 -0
  43. package/src/raycaster/Raycaster.js +175 -0
  44. package/src/raycaster/index.js +10 -0
  45. package/src/renderer/AnimationManager.js +574 -0
  46. package/src/renderer/AppConstants.js +101 -0
  47. package/src/renderer/GaussianSplatRenderer.js +695 -0
  48. package/src/renderer/index.js +24 -0
  49. package/src/utils/LoaderUtils.js +65 -0
  50. package/src/utils/Util.js +375 -0
  51. package/src/utils/index.js +9 -0
  52. package/src/worker/SortWorker.js +284 -0
  53. package/src/worker/index.js +8 -0
  54. package/dist/gsplat-flame-avatar-renderer.esm.min.js +0 -2
  55. package/dist/gsplat-flame-avatar-renderer.esm.min.js.map +0 -1
  56. package/dist/gsplat-flame-avatar-renderer.umd.js +0 -12876
  57. package/dist/gsplat-flame-avatar-renderer.umd.min.js +0 -2
  58. package/dist/gsplat-flame-avatar-renderer.umd.min.js.map +0 -1
  59. package/dist/gsplat-flame-avatar.esm.js +0 -12755
  60. package/dist/gsplat-flame-avatar.esm.js.map +0 -1
  61. package/dist/gsplat-flame-avatar.esm.min.js +0 -2
  62. package/dist/gsplat-flame-avatar.esm.min.js.map +0 -1
  63. package/dist/gsplat-flame-avatar.umd.js +0 -12876
  64. package/dist/gsplat-flame-avatar.umd.js.map +0 -1
  65. package/dist/gsplat-flame-avatar.umd.min.js +0 -2
  66. package/dist/gsplat-flame-avatar.umd.min.js.map +0 -1
@@ -0,0 +1,574 @@
1
+ /**
2
+ * AnimationManager
3
+ *
4
+ * Derived from gaussian-splat-renderer-for-lam
5
+ * Manages animation state machine with Three.js AnimationMixer.
6
+ */
7
+
8
+ import { LoopOnce, LoopRepeat } from 'three';
9
+ import { TYVoiceChatState } from './AppConstants.js';
10
+
11
+ /**
12
+ * Base State class for animation states
13
+ */
14
+ class State {
15
+ constructor(actions, isGroup) {
16
+ this.isPlaying = false;
17
+ this.stage = 0;
18
+ this.actions = actions || [];
19
+ this.blendingTime = 0.5;
20
+ this.isGroup = isGroup || false;
21
+ }
22
+
23
+ dispose() {
24
+ this.actions = [];
25
+ }
26
+
27
+ update(state) {
28
+ // Override in subclasses
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Hello state - initial greeting animation
34
+ */
35
+ class Hello extends State {
36
+ constructor(actions, isGroup) {
37
+ super(actions, isGroup);
38
+ }
39
+
40
+ update(state) {
41
+ // Safety check: return early if no actions available
42
+ if (!this.actions || this.actions.length === 0) return;
43
+
44
+ if (AnimationManager.CurPlaying === undefined &&
45
+ state === TYVoiceChatState.Idle &&
46
+ this.isPlaying === false) {
47
+ this.stage = 0;
48
+ this.actions[this.stage].time = 0;
49
+ AnimationManager.SetWeight(this.actions[this.stage], 1.0);
50
+ this.actions[this.stage].loop = LoopRepeat;
51
+ this.actions[this.stage].clampWhenFinished = false;
52
+ this.actions[this.stage].paused = false;
53
+ this.actions[this.stage].play();
54
+ if (AnimationManager.LastAction !== undefined) {
55
+ AnimationManager.PrepareCrossFade(AnimationManager.LastAction, this.actions[this.stage], this.blendingTime);
56
+ }
57
+ this.isPlaying = true;
58
+ }
59
+
60
+ if (AnimationManager.CurPlaying === TYVoiceChatState.Idle &&
61
+ state === TYVoiceChatState.Idle &&
62
+ this.isPlaying === true) {
63
+ if (this.actions[this.stage].time >
64
+ this.actions[this.stage].getClip().duration - this.blendingTime) {
65
+ let nextStage = this.stage + 1;
66
+ if (nextStage >= this.actions.length) nextStage = 0;
67
+ this.actions[nextStage].time = 0;
68
+ AnimationManager.SetWeight(this.actions[nextStage], 1.0);
69
+ this.actions[nextStage].loop = LoopRepeat;
70
+ this.actions[nextStage].play();
71
+ AnimationManager.PrepareCrossFade(this.actions[this.stage], this.actions[nextStage], this.blendingTime);
72
+ this.stage = nextStage;
73
+ }
74
+ }
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Idle state - resting animation
80
+ */
81
+ class Idle extends State {
82
+ constructor(actions, isGroup) {
83
+ super(actions, isGroup);
84
+ }
85
+
86
+ update(state) {
87
+ // Safety check: return early if no actions available
88
+ if (!this.actions || this.actions.length === 0) return;
89
+
90
+ if (AnimationManager.CurPlaying === undefined &&
91
+ state === TYVoiceChatState.Idle &&
92
+ this.isPlaying === false) {
93
+ this.stage = 0;
94
+ this.actions[this.stage].time = 0;
95
+ AnimationManager.SetWeight(this.actions[this.stage], 1.0);
96
+ this.actions[this.stage].loop = LoopRepeat;
97
+ this.actions[this.stage].clampWhenFinished = false;
98
+ this.actions[this.stage].paused = false;
99
+ this.actions[this.stage].play();
100
+ if (AnimationManager.LastAction !== undefined) {
101
+ AnimationManager.PrepareCrossFade(AnimationManager.LastAction, this.actions[this.stage], this.blendingTime);
102
+ }
103
+ this.isPlaying = true;
104
+ }
105
+
106
+ if (AnimationManager.CurPlaying === TYVoiceChatState.Idle &&
107
+ state !== TYVoiceChatState.Idle &&
108
+ this.isPlaying === true &&
109
+ this.stage === 0) {
110
+ this.actions[this.stage].loop = LoopOnce;
111
+ this.actions[this.stage].clampWhenFinished = true;
112
+ this.isPlaying = false;
113
+ AnimationManager.LastAction = this.actions[this.stage];
114
+ }
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Listen state - listening animation
120
+ */
121
+ class Listen extends State {
122
+ constructor(actions, isGroup) {
123
+ super(actions, isGroup);
124
+ }
125
+
126
+ update(state) {
127
+ // Safety check: return early if no actions available
128
+ if (!this.actions || this.actions.length === 0) return;
129
+
130
+ if (AnimationManager.CurPlaying === undefined &&
131
+ state === TYVoiceChatState.Listening &&
132
+ this.isPlaying === false) {
133
+ this.stage = 0;
134
+ this.actions[this.stage].time = 0;
135
+ this.actions[this.stage].play();
136
+ AnimationManager.SetWeight(this.actions[this.stage], 1.0);
137
+ this.actions[this.stage].loop = this.isGroup ? LoopOnce : LoopRepeat;
138
+ this.actions[this.stage].clampWhenFinished = this.isGroup ? true : false;
139
+ if (AnimationManager.LastAction !== undefined) {
140
+ AnimationManager.PrepareCrossFade(AnimationManager.LastAction, this.actions[this.stage], this.blendingTime);
141
+ }
142
+ this.isPlaying = true;
143
+ }
144
+
145
+ if (this.isGroup) {
146
+ if (AnimationManager.CurPlaying === TYVoiceChatState.Listening &&
147
+ state === TYVoiceChatState.Listening &&
148
+ this.isPlaying === true &&
149
+ this.stage === 0) {
150
+ if (this.actions[this.stage].time >
151
+ this.actions[this.stage].getClip().duration - this.blendingTime) {
152
+ this.actions[this.stage + 1].time = 0;
153
+ AnimationManager.SetWeight(this.actions[this.stage + 1], 1.0);
154
+ this.actions[this.stage + 1].loop = LoopRepeat;
155
+ this.actions[this.stage + 1].play();
156
+ AnimationManager.PrepareCrossFade(this.actions[this.stage], this.actions[this.stage + 1], this.blendingTime);
157
+ this.stage = 1;
158
+ }
159
+ }
160
+
161
+ if (AnimationManager.CurPlaying === TYVoiceChatState.Listening &&
162
+ state !== TYVoiceChatState.Listening &&
163
+ this.isPlaying === true &&
164
+ (this.stage === 0 || this.stage === 1)) {
165
+ this.actions[2].time = 0;
166
+ this.actions[2].play();
167
+ AnimationManager.SetWeight(this.actions[2], 1.0);
168
+ this.actions[2].loop = LoopOnce;
169
+ AnimationManager.PrepareCrossFade(this.actions[this.stage], this.actions[2], this.blendingTime);
170
+ this.stage = 2;
171
+ }
172
+ }
173
+
174
+ if (AnimationManager.CurPlaying === TYVoiceChatState.Listening &&
175
+ state !== TYVoiceChatState.Listening &&
176
+ this.isPlaying === true &&
177
+ this.stage === (this.isGroup ? this.actions.length - 1 : 0)) {
178
+ this.actions[this.stage].loop = LoopOnce;
179
+ this.actions[this.stage].clampWhenFinished = true;
180
+ this.isPlaying = false;
181
+ AnimationManager.LastAction = this.actions[this.stage];
182
+ }
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Think state - thinking animation
188
+ */
189
+ class Think extends State {
190
+ constructor(actions, isGroup) {
191
+ super(actions, isGroup);
192
+ }
193
+
194
+ update(state) {
195
+ // Safety check: return early if no actions available
196
+ if (!this.actions || this.actions.length === 0) return;
197
+
198
+ if (AnimationManager.CurPlaying === undefined &&
199
+ state === TYVoiceChatState.Thinking &&
200
+ this.isPlaying === false) {
201
+ this.stage = 0;
202
+ this.actions[this.stage].time = 0;
203
+ this.actions[this.stage].play();
204
+ AnimationManager.SetWeight(this.actions[this.stage], 1.0);
205
+ this.actions[this.stage].loop = LoopOnce;
206
+ if (AnimationManager.LastAction !== undefined) {
207
+ AnimationManager.PrepareCrossFade(AnimationManager.LastAction, this.actions[this.stage], this.blendingTime);
208
+ }
209
+ this.isPlaying = true;
210
+ }
211
+
212
+ if (this.isGroup) {
213
+ if (AnimationManager.CurPlaying === TYVoiceChatState.Thinking &&
214
+ state === TYVoiceChatState.Thinking &&
215
+ this.isPlaying === true &&
216
+ this.stage === 0) {
217
+ if (this.actions[this.stage].time >
218
+ this.actions[this.stage].getClip().duration - this.blendingTime) {
219
+ this.actions[this.stage + 1].time = 0;
220
+ AnimationManager.SetWeight(this.actions[this.stage + 1], 1.0);
221
+ this.actions[this.stage + 1].loop = LoopRepeat;
222
+ this.actions[this.stage + 1].play();
223
+ AnimationManager.PrepareCrossFade(this.actions[this.stage], this.actions[this.stage + 1], this.blendingTime);
224
+ this.stage = 1;
225
+ }
226
+ }
227
+
228
+ if (AnimationManager.CurPlaying === TYVoiceChatState.Thinking &&
229
+ state !== TYVoiceChatState.Thinking &&
230
+ this.isPlaying === true &&
231
+ (this.stage === 0 || this.stage === 1)) {
232
+ this.actions[2].time = 0;
233
+ this.actions[2].play();
234
+ AnimationManager.SetWeight(this.actions[2], 1.0);
235
+ this.actions[2].loop = LoopOnce;
236
+ AnimationManager.PrepareCrossFade(this.actions[this.stage], this.actions[2], this.blendingTime);
237
+ this.stage = 2;
238
+ }
239
+ }
240
+
241
+ if (AnimationManager.CurPlaying === TYVoiceChatState.Thinking &&
242
+ state !== TYVoiceChatState.Thinking &&
243
+ this.isPlaying === true &&
244
+ this.stage === (this.isGroup ? this.actions.length - 1 : 0)) {
245
+ this.actions[this.stage].loop = LoopOnce;
246
+ this.actions[this.stage].clampWhenFinished = true;
247
+ this.isPlaying = false;
248
+ AnimationManager.LastAction = this.actions[this.stage];
249
+ }
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Speak state - speaking animation with random movement selection
255
+ */
256
+ class Speak extends State {
257
+ constructor(actions, isGroup) {
258
+ super(actions, isGroup);
259
+ console.log('[SPEAK] Initialized with', actions?.length || 0, 'actions, isGroup:', isGroup);
260
+ }
261
+
262
+ /**
263
+ * Get a random number in range [min, max]
264
+ */
265
+ getRandomNumber(max, min) {
266
+ const range = max - min;
267
+ return min + Math.round(Math.random() * range);
268
+ }
269
+
270
+ update(state) {
271
+ // Safety check: return early if no actions available
272
+ if (!this.actions || this.actions.length === 0) {
273
+ if (!this._warnedNoActions) {
274
+ console.warn('[SPEAK] No actions available!');
275
+ this._warnedNoActions = true;
276
+ }
277
+ return;
278
+ }
279
+
280
+ // Start speaking - pick a random animation
281
+ if (AnimationManager.CurPlaying === undefined &&
282
+ state === TYVoiceChatState.Responding &&
283
+ this.isPlaying === false) {
284
+ // Randomly select initial animation
285
+ this.stage = Math.ceil(this.getRandomNumber(0, this.actions.length - 1));
286
+ console.log('[SPEAK] Starting animation, stage:', this.stage, 'of', this.actions.length);
287
+ this.actions[this.stage].time = 0;
288
+ this.actions[this.stage].play();
289
+ AnimationManager.SetWeight(this.actions[this.stage], 1.0);
290
+ this.actions[this.stage].loop = LoopOnce;
291
+ this.actions[this.stage].clampWhenFinished = true;
292
+ if (AnimationManager.LastAction !== undefined) {
293
+ AnimationManager.PrepareCrossFade(AnimationManager.LastAction, this.actions[this.stage], this.blendingTime);
294
+ }
295
+ this.isPlaying = true;
296
+ }
297
+
298
+ // Continue speaking - cycle through random animations
299
+ if (AnimationManager.CurPlaying === TYVoiceChatState.Responding &&
300
+ state === TYVoiceChatState.Responding &&
301
+ this.isPlaying === true) {
302
+ if (this.actions[this.stage].time >=
303
+ this.actions[this.stage].getClip().duration - this.blendingTime) {
304
+ const lastAction = this.actions[this.stage];
305
+ // Pick a different random animation
306
+ this.stage = (this.stage + Math.ceil(this.getRandomNumber(1, this.actions.length - 1))) % this.actions.length;
307
+ console.log('[SPEAK] Cycling to next animation, stage:', this.stage);
308
+ this.actions[this.stage].time = 0;
309
+ this.actions[this.stage].play();
310
+ AnimationManager.SetWeight(this.actions[this.stage], 1.0);
311
+ this.actions[this.stage].loop = LoopOnce;
312
+ this.actions[this.stage].clampWhenFinished = true;
313
+ AnimationManager.PrepareCrossFade(lastAction, this.actions[this.stage], this.blendingTime);
314
+ }
315
+ }
316
+
317
+ // Stop speaking - finish current animation
318
+ if (AnimationManager.CurPlaying === TYVoiceChatState.Responding &&
319
+ state !== TYVoiceChatState.Responding &&
320
+ this.isPlaying === true) {
321
+ this.actions[this.stage].loop = LoopOnce;
322
+ this.actions[this.stage].clampWhenFinished = true;
323
+ this.isPlaying = false;
324
+ AnimationManager.LastAction = this.actions[this.stage];
325
+ }
326
+ }
327
+ }
328
+
329
+ /**
330
+ * AnimationManager - Main animation controller
331
+ * Manages state machine with crossfade transitions between animation states
332
+ */
333
+ export class AnimationManager {
334
+ // Static properties
335
+ static IsBlending = false;
336
+ static actions = [];
337
+ static NeedReset = false;
338
+ static NeedFullReset = false;
339
+ static LastAction = undefined;
340
+ static CurPlaying = undefined;
341
+
342
+ /**
343
+ * Set animation action weight
344
+ */
345
+ static SetWeight(action, weight) {
346
+ action.enabled = true;
347
+ action.setEffectiveTimeScale(1);
348
+ action.setEffectiveWeight(weight);
349
+ }
350
+
351
+ /**
352
+ * Prepare crossfade between two actions
353
+ */
354
+ static PrepareCrossFade(startAction, endAction, defaultDuration) {
355
+ const duration = defaultDuration;
356
+ AnimationManager.UnPauseAllActions();
357
+ AnimationManager.ExecuteCrossFade(startAction, endAction, duration);
358
+ AnimationManager.IsBlending = true;
359
+ setTimeout(() => {
360
+ AnimationManager.IsBlending = false;
361
+ }, defaultDuration + 0.1);
362
+ }
363
+
364
+ /**
365
+ * Pause all animation actions
366
+ */
367
+ static PauseAllActions() {
368
+ AnimationManager.actions.forEach(function(action) {
369
+ action.paused = true;
370
+ });
371
+ }
372
+
373
+ /**
374
+ * Unpause all animation actions
375
+ */
376
+ static UnPauseAllActions() {
377
+ AnimationManager.actions.forEach(function(action) {
378
+ action.paused = false;
379
+ });
380
+ }
381
+
382
+ /**
383
+ * Execute crossfade between two actions
384
+ */
385
+ static ExecuteCrossFade(startAction, endAction, duration) {
386
+ AnimationManager.SetWeight(endAction, 1);
387
+ endAction.time = 0;
388
+ startAction.crossFadeTo(endAction, duration, true);
389
+ }
390
+
391
+ /**
392
+ * Constructor
393
+ * @param {THREE.AnimationMixer} mixer - Three.js animation mixer
394
+ * @param {THREE.AnimationClip[]} animations - Animation clips
395
+ * @param {object} animationcfg - Animation configuration
396
+ */
397
+ constructor(mixer, animations, animationcfg) {
398
+ const helloActions = [];
399
+ const idleActions = [];
400
+ const listenActions = [];
401
+ const speakActions = [];
402
+ const thinkActions = [];
403
+
404
+ this.mixer = mixer;
405
+
406
+ // Calculate action indices based on config
407
+ const helloIdx = animationcfg?.hello?.size || 0;
408
+ const idleIdx = (animationcfg?.idle?.size || 0) + helloIdx;
409
+ const listenIdx = (animationcfg?.listen?.size || 0) + idleIdx;
410
+ const speakIdx = (animationcfg?.speak?.size || 0) + listenIdx;
411
+ const thinkIdx = (animationcfg?.think?.size || 0) + speakIdx;
412
+
413
+ // Distribute animation clips to state action arrays
414
+ if (animations && animations.length > 0) {
415
+ for (let i = 0; i < animations.length; i++) {
416
+ const clip = animations[i];
417
+ const action = mixer.clipAction(clip);
418
+
419
+ if (i < helloIdx) {
420
+ helloActions.push(action);
421
+ } else if (i < idleIdx) {
422
+ idleActions.push(action);
423
+ // Duplicate for states that share idle
424
+ if (listenIdx === idleIdx) {
425
+ listenActions.push(mixer.clipAction(clip.clone()));
426
+ }
427
+ if (speakIdx === listenIdx) {
428
+ speakActions.push(mixer.clipAction(clip.clone()));
429
+ }
430
+ if (thinkIdx === speakIdx) {
431
+ thinkActions.push(mixer.clipAction(clip.clone()));
432
+ }
433
+ } else if (i < listenIdx) {
434
+ listenActions.push(action);
435
+ } else if (i < speakIdx) {
436
+ speakActions.push(action);
437
+ } else if (i < thinkIdx) {
438
+ thinkActions.push(action);
439
+ }
440
+
441
+ AnimationManager.actions.push(action);
442
+ AnimationManager.SetWeight(action, 0);
443
+ }
444
+ }
445
+
446
+ // Create state instances
447
+ this.hello = new Hello(helloActions, animationcfg?.hello?.isGroup || false);
448
+ this.idle = new Idle(idleActions, animationcfg?.idle?.isGroup || false);
449
+ this.listen = new Listen(listenActions, animationcfg?.listen?.isGroup || false);
450
+ this.think = new Think(thinkActions, animationcfg?.think?.isGroup || false);
451
+ this.speak = new Speak(speakActions, animationcfg?.speak?.isGroup || false);
452
+ }
453
+
454
+ /**
455
+ * Get currently playing state
456
+ */
457
+ curPlaying() {
458
+ if (this.hello.isPlaying) return TYVoiceChatState.Idle;
459
+ if (this.idle.isPlaying) return TYVoiceChatState.Idle;
460
+ if (this.listen.isPlaying) return TYVoiceChatState.Listening;
461
+ if (this.think.isPlaying) return TYVoiceChatState.Thinking;
462
+ if (this.speak.isPlaying) return TYVoiceChatState.Responding;
463
+ return undefined;
464
+ }
465
+
466
+ /**
467
+ * Dispose animation manager
468
+ */
469
+ dispose() {
470
+ this.hello.dispose();
471
+ this.idle.dispose();
472
+ this.listen.dispose();
473
+ this.think.dispose();
474
+ this.speak.dispose();
475
+ AnimationManager.actions = [];
476
+ }
477
+
478
+ /**
479
+ * Reset all animation actions
480
+ */
481
+ resetAllActions(ignoreBlending = false) {
482
+ const curPlaying = this.curPlaying();
483
+
484
+ switch (curPlaying) {
485
+ case TYVoiceChatState.Idle:
486
+ AnimationManager.LastAction = this.hello.actions[this.hello.stage];
487
+ break;
488
+ case TYVoiceChatState.Listening:
489
+ AnimationManager.LastAction = this.listen.actions[this.listen.stage];
490
+ break;
491
+ case TYVoiceChatState.Thinking:
492
+ AnimationManager.LastAction = this.think.actions[this.think.stage];
493
+ break;
494
+ case TYVoiceChatState.Responding:
495
+ AnimationManager.LastAction = this.speak.actions[this.speak.stage];
496
+ break;
497
+ default:
498
+ AnimationManager.LastAction = undefined;
499
+ break;
500
+ }
501
+
502
+ if (AnimationManager.LastAction) {
503
+ AnimationManager.LastAction.loop = LoopOnce;
504
+ AnimationManager.LastAction.clampWhenFinished = true;
505
+ AnimationManager.SetWeight(AnimationManager.LastAction, 1.0);
506
+ }
507
+
508
+ if (ignoreBlending) {
509
+ AnimationManager.PauseAllActions();
510
+ AnimationManager.actions.forEach(function(action) {
511
+ action.time = 0;
512
+ AnimationManager.SetWeight(action, 0.0);
513
+ });
514
+ AnimationManager.LastAction = undefined;
515
+ }
516
+
517
+ this.hello.isPlaying = false;
518
+ this.idle.isPlaying = false;
519
+ this.listen.isPlaying = false;
520
+ this.think.isPlaying = false;
521
+ this.speak.isPlaying = false;
522
+ }
523
+
524
+ /**
525
+ * Update animation state
526
+ * @param {string} state - Target state (TYVoiceChatState)
527
+ */
528
+ update(state) {
529
+ if (AnimationManager.IsBlending) return;
530
+
531
+ AnimationManager.CurPlaying = this.curPlaying();
532
+
533
+ if (AnimationManager.CurPlaying === undefined) {
534
+ switch (state) {
535
+ case TYVoiceChatState.Idle:
536
+ this.idle.update(state);
537
+ break;
538
+ case TYVoiceChatState.Listening:
539
+ this.listen.update(state);
540
+ break;
541
+ case TYVoiceChatState.Thinking:
542
+ this.think.update(state);
543
+ break;
544
+ case TYVoiceChatState.Responding:
545
+ this.speak.update(state);
546
+ break;
547
+ default:
548
+ this.idle.update(state);
549
+ break;
550
+ }
551
+ } else {
552
+ switch (AnimationManager.CurPlaying) {
553
+ case TYVoiceChatState.Idle:
554
+ this.idle.update(state);
555
+ break;
556
+ case TYVoiceChatState.Listening:
557
+ this.listen.update(state);
558
+ break;
559
+ case TYVoiceChatState.Thinking:
560
+ this.think.update(state);
561
+ break;
562
+ case TYVoiceChatState.Responding:
563
+ this.speak.update(state);
564
+ break;
565
+ default:
566
+ this.idle.update(state);
567
+ break;
568
+ }
569
+ }
570
+ }
571
+ }
572
+
573
+ export { State, Hello, Idle, Listen, Think, Speak };
574
+ export default AnimationManager;
@@ -0,0 +1,101 @@
1
+ /**
2
+ * AppConstants
3
+ *
4
+ * Derived from gaussian-splat-renderer-for-lam
5
+ * Animation state constants and ARKit blendshape mappings.
6
+ */
7
+
8
+ /**
9
+ * Voice chat state enumeration
10
+ * Controls the rendering and behavior modes of the avatar
11
+ */
12
+ export const TYVoiceChatState = {
13
+ Idle: 'Idle', // Idle/waiting state
14
+ Listening: 'Listening', // Listening to user input
15
+ Responding: 'Responding', // Speaking/responding animation
16
+ Thinking: 'Thinking' // Processing/thinking animation
17
+ };
18
+
19
+ /**
20
+ * ARKit blendshape names (52 expressions)
21
+ * Used for facial expression mapping from ARKit face tracking
22
+ */
23
+ export const ARKitBlendshapes = [
24
+ 'browDownLeft',
25
+ 'browDownRight',
26
+ 'browInnerUp',
27
+ 'browOuterUpLeft',
28
+ 'browOuterUpRight',
29
+ 'cheekPuff',
30
+ 'cheekSquintLeft',
31
+ 'cheekSquintRight',
32
+ 'eyeBlinkLeft',
33
+ 'eyeBlinkRight',
34
+ 'eyeLookDownLeft',
35
+ 'eyeLookDownRight',
36
+ 'eyeLookInLeft',
37
+ 'eyeLookInRight',
38
+ 'eyeLookOutLeft',
39
+ 'eyeLookOutRight',
40
+ 'eyeLookUpLeft',
41
+ 'eyeLookUpRight',
42
+ 'eyeSquintLeft',
43
+ 'eyeSquintRight',
44
+ 'eyeWideLeft',
45
+ 'eyeWideRight',
46
+ 'jawForward',
47
+ 'jawLeft',
48
+ 'jawOpen',
49
+ 'jawRight',
50
+ 'mouthClose',
51
+ 'mouthDimpleLeft',
52
+ 'mouthDimpleRight',
53
+ 'mouthFrownLeft',
54
+ 'mouthFrownRight',
55
+ 'mouthFunnel',
56
+ 'mouthLeft',
57
+ 'mouthLowerDownLeft',
58
+ 'mouthLowerDownRight',
59
+ 'mouthPressLeft',
60
+ 'mouthPressRight',
61
+ 'mouthPucker',
62
+ 'mouthRight',
63
+ 'mouthRollLower',
64
+ 'mouthRollUpper',
65
+ 'mouthShrugLower',
66
+ 'mouthShrugUpper',
67
+ 'mouthSmileLeft',
68
+ 'mouthSmileRight',
69
+ 'mouthStretchLeft',
70
+ 'mouthStretchRight',
71
+ 'mouthUpperUpLeft',
72
+ 'mouthUpperUpRight',
73
+ 'noseSneerLeft',
74
+ 'noseSneerRight',
75
+ 'tongueOut'
76
+ ];
77
+
78
+ /**
79
+ * FLAME model bone names
80
+ */
81
+ export const FlameBoneNames = [
82
+ 'root',
83
+ 'neck',
84
+ 'jaw',
85
+ 'leftEye',
86
+ 'rightEye'
87
+ ];
88
+
89
+ /**
90
+ * Constants derived from the arrays
91
+ */
92
+ export const ARKIT_BLENDSHAPES_COUNT = ARKitBlendshapes.length;
93
+ export const FLAME_BONES_COUNT = FlameBoneNames.length;
94
+
95
+ export default {
96
+ TYVoiceChatState,
97
+ ARKitBlendshapes,
98
+ FlameBoneNames,
99
+ ARKIT_BLENDSHAPES_COUNT,
100
+ FLAME_BONES_COUNT
101
+ };