@sage-rsc/talking-head-react 1.0.84 → 1.1.0
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 +2 -2
- package/dist/index.js +585 -556
- package/package.json +1 -1
- package/src/lib/talkinghead.mjs +69 -0
package/package.json
CHANGED
package/src/lib/talkinghead.mjs
CHANGED
|
@@ -5344,6 +5344,61 @@ class TalkingHead {
|
|
|
5344
5344
|
* @param {number} [ndx=0] Index of the clip
|
|
5345
5345
|
* @param {number} [scale=0.01] Position scale factor
|
|
5346
5346
|
*/
|
|
5347
|
+
/**
|
|
5348
|
+
* Get all bone names from the avatar's armature
|
|
5349
|
+
* @returns {Set<string>} Set of bone names
|
|
5350
|
+
*/
|
|
5351
|
+
getAvailableBoneNames() {
|
|
5352
|
+
const boneNames = new Set();
|
|
5353
|
+
if (!this.armature) return boneNames;
|
|
5354
|
+
|
|
5355
|
+
this.armature.traverse((child) => {
|
|
5356
|
+
if (child.isBone || child.type === 'Bone') {
|
|
5357
|
+
boneNames.add(child.name);
|
|
5358
|
+
}
|
|
5359
|
+
});
|
|
5360
|
+
|
|
5361
|
+
return boneNames;
|
|
5362
|
+
}
|
|
5363
|
+
|
|
5364
|
+
/**
|
|
5365
|
+
* Filter animation tracks to only include bones that exist in the avatar
|
|
5366
|
+
* @param {THREE.AnimationClip} clip - Animation clip to filter
|
|
5367
|
+
* @param {Set<string>} availableBones - Set of available bone names
|
|
5368
|
+
* @returns {THREE.AnimationClip} Filtered animation clip
|
|
5369
|
+
*/
|
|
5370
|
+
filterAnimationTracks(clip, availableBones) {
|
|
5371
|
+
const validTracks = [];
|
|
5372
|
+
const missingBones = new Set();
|
|
5373
|
+
|
|
5374
|
+
clip.tracks.forEach(track => {
|
|
5375
|
+
// Extract bone name from track name (e.g., "CC_Base_R_Index3.position" -> "CC_Base_R_Index3")
|
|
5376
|
+
const trackNameParts = track.name.split('.');
|
|
5377
|
+
const boneName = trackNameParts[0];
|
|
5378
|
+
|
|
5379
|
+
if (availableBones.has(boneName)) {
|
|
5380
|
+
validTracks.push(track);
|
|
5381
|
+
} else {
|
|
5382
|
+
missingBones.add(boneName);
|
|
5383
|
+
}
|
|
5384
|
+
});
|
|
5385
|
+
|
|
5386
|
+
if (missingBones.size > 0) {
|
|
5387
|
+
console.warn(`FBX animation "${clip.name}" contains tracks for ${missingBones.size} bone(s) not found in avatar skeleton:`, Array.from(missingBones).slice(0, 10).join(', '), missingBones.size > 10 ? '...' : '');
|
|
5388
|
+
console.info(`Filtered ${clip.tracks.length} tracks down to ${validTracks.length} valid tracks`);
|
|
5389
|
+
} else if (validTracks.length > 0) {
|
|
5390
|
+
console.info(`FBX animation "${clip.name}" is fully compatible: all ${validTracks.length} tracks match avatar skeleton`);
|
|
5391
|
+
}
|
|
5392
|
+
|
|
5393
|
+
// Create a new clip with only valid tracks
|
|
5394
|
+
if (validTracks.length === 0) {
|
|
5395
|
+
console.error(`No valid tracks found for animation "${clip.name}". All bones are missing from avatar skeleton.`);
|
|
5396
|
+
return null;
|
|
5397
|
+
}
|
|
5398
|
+
|
|
5399
|
+
return new THREE.AnimationClip(clip.name, clip.duration, validTracks);
|
|
5400
|
+
}
|
|
5401
|
+
|
|
5347
5402
|
async playAnimation(url, onprogress=null, dur=10, ndx=0, scale=0.01, disablePositionLock=false) {
|
|
5348
5403
|
if ( !this.armature ) return;
|
|
5349
5404
|
|
|
@@ -5491,6 +5546,20 @@ class TalkingHead {
|
|
|
5491
5546
|
if ( fbx && fbx.animations && fbx.animations[ndx] ) {
|
|
5492
5547
|
let anim = fbx.animations[ndx];
|
|
5493
5548
|
|
|
5549
|
+
// Get available bone names from avatar skeleton
|
|
5550
|
+
const availableBones = this.getAvailableBoneNames();
|
|
5551
|
+
|
|
5552
|
+
// Filter animation tracks to only include bones that exist
|
|
5553
|
+
const filteredAnim = this.filterAnimationTracks(anim, availableBones);
|
|
5554
|
+
|
|
5555
|
+
if (!filteredAnim) {
|
|
5556
|
+
console.error(`Cannot play FBX animation "${url}": No compatible bones found.`);
|
|
5557
|
+
return;
|
|
5558
|
+
}
|
|
5559
|
+
|
|
5560
|
+
// Use the filtered animation instead of the original
|
|
5561
|
+
anim = filteredAnim;
|
|
5562
|
+
|
|
5494
5563
|
// Rename and scale Mixamo tracks, create a pose
|
|
5495
5564
|
const props = {};
|
|
5496
5565
|
anim.tracks.forEach( t => {
|