@sage-rsc/talking-head-react 1.6.3 → 1.6.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.
- package/dist/index.cjs +5 -5
- package/dist/index.js +1836 -1913
- package/package.json +1 -1
- package/src/lib/talkinghead.mjs +4 -126
package/package.json
CHANGED
package/src/lib/talkinghead.mjs
CHANGED
|
@@ -1425,7 +1425,6 @@ class TalkingHead {
|
|
|
1425
1425
|
// Use setPoseFromTemplate which properly handles pose transitions without loops
|
|
1426
1426
|
this.poseName = 'wide';
|
|
1427
1427
|
this.setPoseFromTemplate(this.poseTemplates['wide'], 0); // 0ms = instant, no transition
|
|
1428
|
-
console.log('Set initial male-appropriate pose: wide');
|
|
1429
1428
|
}
|
|
1430
1429
|
|
|
1431
1430
|
// Initialize FBX animation loader
|
|
@@ -2238,7 +2237,6 @@ class TalkingHead {
|
|
|
2238
2237
|
// Dynamic import to avoid loading issues
|
|
2239
2238
|
const { FBXAnimationLoader } = await import('./fbxAnimationLoader.js');
|
|
2240
2239
|
this.fbxAnimationLoader = new FBXAnimationLoader(this.armature);
|
|
2241
|
-
console.log('FBX Animation Loader initialized');
|
|
2242
2240
|
} catch (error) {
|
|
2243
2241
|
console.warn('FBX Animation Loader not available:', error);
|
|
2244
2242
|
this.fbxAnimationLoader = null;
|
|
@@ -2257,7 +2255,6 @@ class TalkingHead {
|
|
|
2257
2255
|
this.avatar.bodyMovement = movement;
|
|
2258
2256
|
}
|
|
2259
2257
|
|
|
2260
|
-
console.log('Body movement set to:', movement);
|
|
2261
2258
|
|
|
2262
2259
|
// Respect the current showFullAvatar setting instead of forcing it to true
|
|
2263
2260
|
// Only unlock position when returning to idle
|
|
@@ -2278,18 +2275,11 @@ class TalkingHead {
|
|
|
2278
2275
|
async applyBodyMovementAnimation() {
|
|
2279
2276
|
// Check if avatar is ready
|
|
2280
2277
|
if (!this.armature || !this.animQueue) {
|
|
2281
|
-
console.log('Avatar not ready for body movement animations');
|
|
2282
2278
|
return;
|
|
2283
2279
|
}
|
|
2284
2280
|
|
|
2285
|
-
console.log('Avatar is running:', this.isRunning);
|
|
2286
|
-
console.log('Animation queue exists:', !!this.animQueue);
|
|
2287
|
-
|
|
2288
2281
|
// Remove existing body movement animations
|
|
2289
|
-
const beforeLength = this.animQueue.length;
|
|
2290
2282
|
this.animQueue = this.animQueue.filter(anim => !anim.template.name.startsWith('bodyMovement'));
|
|
2291
|
-
const afterLength = this.animQueue.length;
|
|
2292
|
-
console.log(`Filtered animation queue: ${beforeLength} -> ${afterLength} animations`);
|
|
2293
2283
|
|
|
2294
2284
|
if (this.bodyMovement === 'idle') {
|
|
2295
2285
|
// Stop FBX animations if any
|
|
@@ -2303,16 +2293,14 @@ class TalkingHead {
|
|
|
2303
2293
|
if (this.fbxAnimationLoader) {
|
|
2304
2294
|
try {
|
|
2305
2295
|
await this.fbxAnimationLoader.playGestureAnimation(this.bodyMovement, this.movementIntensity);
|
|
2306
|
-
console.log('Applied FBX body movement animation:', this.bodyMovement);
|
|
2307
2296
|
return; // Successfully applied FBX animation
|
|
2308
2297
|
} catch (error) {
|
|
2309
|
-
|
|
2298
|
+
// Silent fallback
|
|
2310
2299
|
}
|
|
2311
2300
|
}
|
|
2312
2301
|
|
|
2313
2302
|
// Fallback to code-based animations
|
|
2314
2303
|
const movementAnim = this.createBodyMovementAnimation(this.bodyMovement);
|
|
2315
|
-
console.log('Created movement animation:', movementAnim);
|
|
2316
2304
|
if (movementAnim) {
|
|
2317
2305
|
try {
|
|
2318
2306
|
// Use animFactory to create proper animation object
|
|
@@ -2321,9 +2309,6 @@ class TalkingHead {
|
|
|
2321
2309
|
// Validate the animation object before adding
|
|
2322
2310
|
if (animObj && animObj.ts && animObj.ts.length > 0) {
|
|
2323
2311
|
this.animQueue.push(animObj);
|
|
2324
|
-
console.log('Applied code-based body movement animation:', this.bodyMovement);
|
|
2325
|
-
console.log('Animation queue length:', this.animQueue.length);
|
|
2326
|
-
console.log('Animation object:', animObj);
|
|
2327
2312
|
} else {
|
|
2328
2313
|
console.error('Invalid animation object created for:', this.bodyMovement);
|
|
2329
2314
|
console.error('Animation object:', animObj);
|
|
@@ -2350,7 +2335,6 @@ class TalkingHead {
|
|
|
2350
2335
|
y: this.armature.position.y,
|
|
2351
2336
|
z: this.armature.position.z
|
|
2352
2337
|
};
|
|
2353
|
-
console.log('Original position stored:', this.originalPosition);
|
|
2354
2338
|
}
|
|
2355
2339
|
|
|
2356
2340
|
// Lock the avatar at its CURRENT position (don't move it)
|
|
@@ -2360,7 +2344,6 @@ class TalkingHead {
|
|
|
2360
2344
|
z: this.armature.position.z
|
|
2361
2345
|
};
|
|
2362
2346
|
|
|
2363
|
-
console.log('Avatar position locked at current position:', this.lockedPosition);
|
|
2364
2347
|
}
|
|
2365
2348
|
|
|
2366
2349
|
/**
|
|
@@ -2374,15 +2357,12 @@ class TalkingHead {
|
|
|
2374
2357
|
this.originalPosition.y,
|
|
2375
2358
|
this.originalPosition.z
|
|
2376
2359
|
);
|
|
2377
|
-
console.log('Avatar position restored to original:', this.originalPosition);
|
|
2378
2360
|
} else if (this.armature) {
|
|
2379
2361
|
// Fallback: reset to center if no original position was stored
|
|
2380
2362
|
this.armature.position.set(0, 0, 0);
|
|
2381
|
-
console.log('Avatar position reset to center (0,0,0)');
|
|
2382
2363
|
}
|
|
2383
2364
|
this.lockedPosition = null;
|
|
2384
2365
|
this.originalPosition = null; // Clear original position after unlock
|
|
2385
|
-
console.log('Avatar position unlocked');
|
|
2386
2366
|
}
|
|
2387
2367
|
|
|
2388
2368
|
/**
|
|
@@ -2629,7 +2609,6 @@ class TalkingHead {
|
|
|
2629
2609
|
this.avatar.movementIntensity = this.movementIntensity;
|
|
2630
2610
|
}
|
|
2631
2611
|
|
|
2632
|
-
console.log('Movement intensity set to:', this.movementIntensity);
|
|
2633
2612
|
|
|
2634
2613
|
// Update FBX animation intensity if available
|
|
2635
2614
|
if (this.fbxAnimationLoader) {
|
|
@@ -2652,18 +2631,14 @@ class TalkingHead {
|
|
|
2652
2631
|
this.avatar.showFullAvatar = show;
|
|
2653
2632
|
}
|
|
2654
2633
|
|
|
2655
|
-
console.log('Show full avatar set to:', show);
|
|
2656
2634
|
|
|
2657
2635
|
// Only change camera view if it's not already set to the desired view
|
|
2658
2636
|
// This prevents the avatar from sliding down when starting animations
|
|
2659
2637
|
if (show && this.viewName !== 'full') {
|
|
2660
|
-
console.log('Changing camera view to full');
|
|
2661
2638
|
this.setView('full');
|
|
2662
2639
|
} else if (!show && this.viewName !== 'upper') {
|
|
2663
|
-
console.log('Changing camera view to upper');
|
|
2664
2640
|
this.setView('upper');
|
|
2665
2641
|
} else {
|
|
2666
|
-
console.log('Camera view already set to:', this.viewName);
|
|
2667
2642
|
}
|
|
2668
2643
|
}
|
|
2669
2644
|
|
|
@@ -2790,7 +2765,6 @@ class TalkingHead {
|
|
|
2790
2765
|
if ( a.hasOwnProperty(this.stateName) ) {
|
|
2791
2766
|
// Debug: Log state selection
|
|
2792
2767
|
if (this.stateName === 'speaking' || this.stateName === 'idle') {
|
|
2793
|
-
console.log('Selected state:', this.stateName, 'for avatar body:', this.avatar?.body);
|
|
2794
2768
|
}
|
|
2795
2769
|
a = a[this.stateName];
|
|
2796
2770
|
} else if ( a.hasOwnProperty(this.moodName) ) {
|
|
@@ -2801,7 +2775,6 @@ class TalkingHead {
|
|
|
2801
2775
|
a = a[this.viewName];
|
|
2802
2776
|
} else if ( this.avatar && this.avatar.body && a.hasOwnProperty(this.avatar.body) ) {
|
|
2803
2777
|
// Debug: Log gender-specific override
|
|
2804
|
-
console.log('Applying gender-specific override:', this.avatar.body, 'for state:', this.stateName, 'keys:', Object.keys(a));
|
|
2805
2778
|
a = a[this.avatar.body];
|
|
2806
2779
|
} else if ( a.hasOwnProperty('alt') ) {
|
|
2807
2780
|
|
|
@@ -2823,7 +2796,6 @@ class TalkingHead {
|
|
|
2823
2796
|
a = b;
|
|
2824
2797
|
// Debug: Log selected alternative and check for gender override
|
|
2825
2798
|
if (this.avatar && this.avatar.body && a.hasOwnProperty(this.avatar.body)) {
|
|
2826
|
-
console.log('Found gender override in selected alternative:', this.avatar.body, 'keys:', Object.keys(a));
|
|
2827
2799
|
}
|
|
2828
2800
|
// Continue loop to check for gender-specific override after selecting alternative
|
|
2829
2801
|
continue;
|
|
@@ -2859,7 +2831,6 @@ class TalkingHead {
|
|
|
2859
2831
|
// Values
|
|
2860
2832
|
// Debug: Log pose selection
|
|
2861
2833
|
if (a.vs && a.vs.pose) {
|
|
2862
|
-
console.log('Pose being selected from vs.pose:', a.vs.pose, 'for avatar body:', this.avatar?.body);
|
|
2863
2834
|
}
|
|
2864
2835
|
for( let [mt,vs] of Object.entries(a.vs) ) {
|
|
2865
2836
|
const base = this.getBaselineValue(mt);
|
|
@@ -2872,7 +2843,6 @@ class TalkingHead {
|
|
|
2872
2843
|
} else if ( typeof x === 'string' || x instanceof String ) {
|
|
2873
2844
|
// Intercept pose values and override 'hip' and 'side' to 'wide' for male avatars
|
|
2874
2845
|
if (mt === 'pose' && this.avatar && this.avatar.body === 'M' && (x === 'hip' || x === 'side')) {
|
|
2875
|
-
console.log('Intercepting pose', x, 'in animation factory, overriding to wide for male avatar');
|
|
2876
2846
|
return 'wide'; // Always use 'wide' for male avatars, never 'side' or 'hip'
|
|
2877
2847
|
}
|
|
2878
2848
|
return x.slice();
|
|
@@ -3171,12 +3141,10 @@ class TalkingHead {
|
|
|
3171
3141
|
// Always override 'hip' and 'side' to 'wide' for male avatars
|
|
3172
3142
|
if (this.poseTemplates['wide']) {
|
|
3173
3143
|
j = 'wide';
|
|
3174
|
-
console.log('Overriding pose', j === 'hip' ? 'hip' : 'side', 'to wide for male avatar');
|
|
3175
3144
|
}
|
|
3176
3145
|
}
|
|
3177
3146
|
}
|
|
3178
3147
|
this.poseName = j;
|
|
3179
|
-
console.log('Setting pose to:', this.poseName, 'for avatar body:', this.avatar?.body, 'state:', this.stateName);
|
|
3180
3148
|
this.setPoseFromTemplate( this.poseTemplates[ this.poseName ] );
|
|
3181
3149
|
break;
|
|
3182
3150
|
|
|
@@ -3423,7 +3391,6 @@ class TalkingHead {
|
|
|
3423
3391
|
|
|
3424
3392
|
if (module && module[className]) {
|
|
3425
3393
|
this.lipsync[lang] = new module[className];
|
|
3426
|
-
console.log(`Loaded lip-sync module for ${lang}`);
|
|
3427
3394
|
} else {
|
|
3428
3395
|
console.warn(`Lip-sync module for ${lang} not found. Available modules:`, Object.keys(LIPSYNC_MODULES));
|
|
3429
3396
|
}
|
|
@@ -3946,15 +3913,6 @@ class TalkingHead {
|
|
|
3946
3913
|
const processedText = this.lipsyncPreProcessText(text, lipsyncLang);
|
|
3947
3914
|
const lipsyncData = this.lipsyncWordsToVisemes(processedText, lipsyncLang);
|
|
3948
3915
|
|
|
3949
|
-
console.log('Browser TTS Lip-sync Debug:', {
|
|
3950
|
-
text,
|
|
3951
|
-
lipsyncLang,
|
|
3952
|
-
processedText,
|
|
3953
|
-
lipsyncData,
|
|
3954
|
-
hasVisemes: lipsyncData && lipsyncData.visemes && lipsyncData.visemes.length > 0,
|
|
3955
|
-
estimatedDuration
|
|
3956
|
-
});
|
|
3957
|
-
|
|
3958
3916
|
// Generate lip-sync animation from the viseme data
|
|
3959
3917
|
const lipsyncAnim = [];
|
|
3960
3918
|
if (lipsyncData && lipsyncData.visemes && lipsyncData.visemes.length > 0) {
|
|
@@ -4044,7 +4002,6 @@ class TalkingHead {
|
|
|
4044
4002
|
const audioBuffer = await this.audioCtx.decodeAudioData(audioArrayBuffer);
|
|
4045
4003
|
|
|
4046
4004
|
// Use text-based lip-sync with proper error handling
|
|
4047
|
-
console.log('Using text-based lip-sync for debugging...');
|
|
4048
4005
|
const lipsyncLang = this.avatar.lipsyncLang || this.opt.lipsyncLang || 'en';
|
|
4049
4006
|
|
|
4050
4007
|
let audioAnalysis;
|
|
@@ -4117,22 +4074,9 @@ class TalkingHead {
|
|
|
4117
4074
|
};
|
|
4118
4075
|
}
|
|
4119
4076
|
|
|
4120
|
-
console.log('ElevenLabs TTS Audio Analysis:', {
|
|
4121
|
-
text,
|
|
4122
|
-
audioDuration: audioBuffer.duration,
|
|
4123
|
-
visemeCount: audioAnalysis.visemes ? audioAnalysis.visemes.length : 0,
|
|
4124
|
-
wordCount: audioAnalysis.words ? audioAnalysis.words.length : 0,
|
|
4125
|
-
features: {
|
|
4126
|
-
onsets: audioAnalysis.features && audioAnalysis.features.onsets ? audioAnalysis.features.onsets.length : 0,
|
|
4127
|
-
boundaries: audioAnalysis.features && audioAnalysis.features.phonemeBoundaries ? audioAnalysis.features.phonemeBoundaries.length : 0
|
|
4128
|
-
},
|
|
4129
|
-
visemes: audioAnalysis.visemes ? audioAnalysis.visemes.slice(0, 3) : [] // Show first 3 visemes for debugging
|
|
4130
|
-
});
|
|
4131
|
-
|
|
4132
4077
|
// Generate precise lip-sync animation from audio analysis
|
|
4133
4078
|
const lipsyncAnim = [];
|
|
4134
4079
|
if (audioAnalysis.visemes && audioAnalysis.visemes.length > 0) {
|
|
4135
|
-
console.log('ElevenLabs: Generating lip-sync animation from', audioAnalysis.visemes.length, 'visemes');
|
|
4136
4080
|
for (let i = 0; i < audioAnalysis.visemes.length; i++) {
|
|
4137
4081
|
const visemeData = audioAnalysis.visemes[i];
|
|
4138
4082
|
const time = visemeData.startTime * 1000; // Convert to milliseconds
|
|
@@ -4147,14 +4091,12 @@ class TalkingHead {
|
|
|
4147
4091
|
}
|
|
4148
4092
|
});
|
|
4149
4093
|
}
|
|
4150
|
-
console.log('ElevenLabs: Generated', lipsyncAnim.length, 'lip-sync animation frames');
|
|
4151
4094
|
} else {
|
|
4152
4095
|
console.warn('ElevenLabs: No visemes available for lip-sync animation');
|
|
4153
4096
|
}
|
|
4154
4097
|
|
|
4155
4098
|
// Combine original animation with lip-sync animation
|
|
4156
4099
|
const combinedAnim = [...line.anim, ...lipsyncAnim];
|
|
4157
|
-
console.log('ElevenLabs: Combined animation frames:', combinedAnim.length, '(original:', line.anim.length, '+ lipsync:', lipsyncAnim.length, ')');
|
|
4158
4100
|
|
|
4159
4101
|
// Add to playlist
|
|
4160
4102
|
this.audioPlaylist.push({ anim: combinedAnim, audio: audioBuffer });
|
|
@@ -4197,7 +4139,6 @@ class TalkingHead {
|
|
|
4197
4139
|
const audioBuffer = await this.audioCtx.decodeAudioData(audioArrayBuffer);
|
|
4198
4140
|
|
|
4199
4141
|
// Use text-based lip-sync with proper error handling
|
|
4200
|
-
console.log('Using text-based lip-sync for Deepgram...');
|
|
4201
4142
|
const lipsyncLang = this.avatar.lipsyncLang || this.opt.lipsyncLang || 'en';
|
|
4202
4143
|
|
|
4203
4144
|
let audioAnalysis;
|
|
@@ -4270,22 +4211,9 @@ class TalkingHead {
|
|
|
4270
4211
|
};
|
|
4271
4212
|
}
|
|
4272
4213
|
|
|
4273
|
-
console.log('Deepgram TTS Audio Analysis:', {
|
|
4274
|
-
text,
|
|
4275
|
-
audioDuration: audioBuffer.duration,
|
|
4276
|
-
visemeCount: audioAnalysis.visemes ? audioAnalysis.visemes.length : 0,
|
|
4277
|
-
wordCount: audioAnalysis.words ? audioAnalysis.words.length : 0,
|
|
4278
|
-
features: {
|
|
4279
|
-
onsets: audioAnalysis.features && audioAnalysis.features.onsets ? audioAnalysis.features.onsets.length : 0,
|
|
4280
|
-
boundaries: audioAnalysis.features && audioAnalysis.features.phonemeBoundaries ? audioAnalysis.features.phonemeBoundaries.length : 0
|
|
4281
|
-
},
|
|
4282
|
-
visemes: audioAnalysis.visemes ? audioAnalysis.visemes.slice(0, 3) : [] // Show first 3 visemes for debugging
|
|
4283
|
-
});
|
|
4284
|
-
|
|
4285
4214
|
// Generate precise lip-sync animation from audio analysis
|
|
4286
4215
|
const lipsyncAnim = [];
|
|
4287
4216
|
if (audioAnalysis.visemes && audioAnalysis.visemes.length > 0) {
|
|
4288
|
-
console.log('Deepgram: Generating lip-sync animation from', audioAnalysis.visemes.length, 'visemes');
|
|
4289
4217
|
for (let i = 0; i < audioAnalysis.visemes.length; i++) {
|
|
4290
4218
|
const visemeData = audioAnalysis.visemes[i];
|
|
4291
4219
|
const time = visemeData.startTime * 1000; // Convert to milliseconds
|
|
@@ -4300,14 +4228,12 @@ class TalkingHead {
|
|
|
4300
4228
|
}
|
|
4301
4229
|
});
|
|
4302
4230
|
}
|
|
4303
|
-
console.log('Deepgram: Generated', lipsyncAnim.length, 'lip-sync animation frames');
|
|
4304
4231
|
} else {
|
|
4305
4232
|
console.warn('Deepgram: No visemes available for lip-sync animation');
|
|
4306
4233
|
}
|
|
4307
4234
|
|
|
4308
4235
|
// Combine original animation with lip-sync animation
|
|
4309
4236
|
const combinedAnim = [...line.anim, ...lipsyncAnim];
|
|
4310
|
-
console.log('Deepgram: Combined animation frames:', combinedAnim.length, '(original:', line.anim.length, '+ lipsync:', lipsyncAnim.length, ')');
|
|
4311
4237
|
|
|
4312
4238
|
// Add to playlist
|
|
4313
4239
|
this.audioPlaylist.push({ anim: combinedAnim, audio: audioBuffer });
|
|
@@ -4354,19 +4280,9 @@ class TalkingHead {
|
|
|
4354
4280
|
const audioBuffer = await this.audioCtx.decodeAudioData(audioArrayBuffer);
|
|
4355
4281
|
|
|
4356
4282
|
// Analyze audio for precise lip-sync timing
|
|
4357
|
-
console.log('Analyzing audio for precise lip-sync...');
|
|
4358
4283
|
const audioAnalysis = await this.audioAnalyzer.analyzeAudio(audioBuffer, text);
|
|
4359
4284
|
|
|
4360
|
-
|
|
4361
|
-
text,
|
|
4362
|
-
audioDuration: audioBuffer.duration,
|
|
4363
|
-
visemeCount: audioAnalysis.visemes.length,
|
|
4364
|
-
wordCount: audioAnalysis.words.length,
|
|
4365
|
-
features: {
|
|
4366
|
-
onsets: audioAnalysis.features.onsets.length,
|
|
4367
|
-
boundaries: audioAnalysis.features.phonemeBoundaries.length
|
|
4368
|
-
}
|
|
4369
|
-
});
|
|
4285
|
+
// Azure TTS (debug disabled - removed verbose logs)
|
|
4370
4286
|
|
|
4371
4287
|
// Generate precise lip-sync animation from audio analysis
|
|
4372
4288
|
const lipsyncAnim = [];
|
|
@@ -5476,9 +5392,6 @@ class TalkingHead {
|
|
|
5476
5392
|
// Lock position IMMEDIATELY to prevent any position changes (unless disabled)
|
|
5477
5393
|
if (!disablePositionLock) {
|
|
5478
5394
|
this.lockAvatarPosition();
|
|
5479
|
-
console.log('Position locked immediately before FBX animation:', url);
|
|
5480
|
-
} else {
|
|
5481
|
-
console.log('Position locking disabled for FBX animation:', url);
|
|
5482
5395
|
}
|
|
5483
5396
|
|
|
5484
5397
|
let item = this.animClips.find( x => x.url === url+'-'+ndx );
|
|
@@ -5501,9 +5414,7 @@ class TalkingHead {
|
|
|
5501
5414
|
// Use existing mixer or create new one if none exists
|
|
5502
5415
|
if (!this.mixer) {
|
|
5503
5416
|
this.mixer = new THREE.AnimationMixer(this.armature);
|
|
5504
|
-
console.log('Created new mixer for FBX animation');
|
|
5505
5417
|
} else {
|
|
5506
|
-
console.log('Using existing mixer for FBX animation, preserving morph targets');
|
|
5507
5418
|
}
|
|
5508
5419
|
|
|
5509
5420
|
// Store callback for when animation finishes
|
|
@@ -5799,11 +5710,7 @@ class TalkingHead {
|
|
|
5799
5710
|
fbxBoneNames.add(trackParts[0]);
|
|
5800
5711
|
});
|
|
5801
5712
|
|
|
5802
|
-
|
|
5803
|
-
console.log('FBX bone names:', Array.from(fbxBoneNames).sort().join(', '));
|
|
5804
|
-
console.log('Avatar skeleton bone names:', Array.from(availableBones).sort().join(', '));
|
|
5805
|
-
|
|
5806
|
-
// Check for arm bones specifically
|
|
5713
|
+
// Check for arm bones specifically (for mapping)
|
|
5807
5714
|
const fbxArmBones = Array.from(fbxBoneNames).filter(b =>
|
|
5808
5715
|
b.toLowerCase().includes('arm') ||
|
|
5809
5716
|
b.toLowerCase().includes('hand') ||
|
|
@@ -5814,8 +5721,6 @@ class TalkingHead {
|
|
|
5814
5721
|
b.includes('Hand') ||
|
|
5815
5722
|
b.includes('Shoulder')
|
|
5816
5723
|
);
|
|
5817
|
-
console.log('FBX arm/hand/shoulder bones:', fbxArmBones.sort().join(', '));
|
|
5818
|
-
console.log('Avatar arm/hand/shoulder bones:', avatarArmBones.sort().join(', '));
|
|
5819
5724
|
|
|
5820
5725
|
// Filter and map animation tracks
|
|
5821
5726
|
const mappedTracks = [];
|
|
@@ -5866,34 +5771,11 @@ class TalkingHead {
|
|
|
5866
5771
|
}
|
|
5867
5772
|
});
|
|
5868
5773
|
|
|
5869
|
-
if (filteredShoulderTracks > 0) {
|
|
5870
|
-
console.log(`✓ Filtered out ${filteredShoulderTracks} shoulder rotation track(s) to prevent high shoulders`);
|
|
5871
|
-
}
|
|
5872
|
-
|
|
5873
|
-
if (unmappedBones.size > 0) {
|
|
5874
|
-
console.warn(`⚠️ ${unmappedBones.size} bone(s) could not be mapped:`, Array.from(unmappedBones).sort().join(', '));
|
|
5875
|
-
}
|
|
5876
|
-
|
|
5877
5774
|
// Use mapped tracks if we have any, otherwise use original
|
|
5878
5775
|
if (mappedTracks.length > 0) {
|
|
5879
5776
|
anim = new THREE.AnimationClip(anim.name, anim.duration, mappedTracks);
|
|
5880
|
-
console.log(`✓ Created animation with ${mappedTracks.length} mapped tracks (from ${anim.tracks.length} original tracks)`);
|
|
5881
|
-
if (boneNameMap.size > 0) {
|
|
5882
|
-
console.log(`✓ Mapped ${boneNameMap.size} bone(s):`,
|
|
5883
|
-
Array.from(boneNameMap.entries()).map(([from, to]) => `${from}→${to}`).join(', '));
|
|
5884
|
-
}
|
|
5885
|
-
|
|
5886
|
-
// Check if arm bones were mapped
|
|
5887
|
-
const mappedArmBones = Array.from(boneNameMap.values()).filter(b =>
|
|
5888
|
-
b.includes('Arm') || b.includes('Hand') || b.includes('Shoulder')
|
|
5889
|
-
);
|
|
5890
|
-
if (mappedArmBones.length > 0) {
|
|
5891
|
-
console.log(`✓ Arm bones mapped: ${mappedArmBones.join(', ')}`);
|
|
5892
|
-
} else {
|
|
5893
|
-
console.warn('⚠️ No arm bones were mapped! This may cause arm rigging issues.');
|
|
5894
|
-
}
|
|
5895
5777
|
} else {
|
|
5896
|
-
console.error('
|
|
5778
|
+
console.error('No tracks could be mapped! Animation may not work correctly.');
|
|
5897
5779
|
}
|
|
5898
5780
|
|
|
5899
5781
|
// Rename and scale Mixamo tracks, create a pose
|
|
@@ -5955,22 +5837,18 @@ class TalkingHead {
|
|
|
5955
5837
|
if (this.currentFBXAction) {
|
|
5956
5838
|
this.currentFBXAction.stop();
|
|
5957
5839
|
this.currentFBXAction = null;
|
|
5958
|
-
console.log('FBX animation action stopped, mixer preserved for lip-sync');
|
|
5959
5840
|
}
|
|
5960
5841
|
|
|
5961
5842
|
// Only destroy mixer if no other animations are running
|
|
5962
5843
|
// This allows morph target animations (lip-sync) to continue
|
|
5963
5844
|
if (this.mixer && this.mixer._actions.length === 0) {
|
|
5964
5845
|
this.mixer = null;
|
|
5965
|
-
console.log('Mixer destroyed as no actions remain');
|
|
5966
5846
|
}
|
|
5967
5847
|
|
|
5968
5848
|
// Unlock position when animation stops (only if it was locked)
|
|
5969
5849
|
if (this.positionWasLocked) {
|
|
5970
5850
|
this.unlockAvatarPosition();
|
|
5971
|
-
console.log('Position unlocked after FBX animation stopped');
|
|
5972
5851
|
} else {
|
|
5973
|
-
console.log('Position was not locked, no unlock needed');
|
|
5974
5852
|
}
|
|
5975
5853
|
|
|
5976
5854
|
// Restart gesture
|