@sage-rsc/talking-head-react 1.1.7 → 1.1.8
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 +951 -875
- package/package.json +1 -1
- package/src/lib/talkinghead.mjs +141 -0
package/package.json
CHANGED
package/src/lib/talkinghead.mjs
CHANGED
|
@@ -5471,6 +5471,147 @@ class TalkingHead {
|
|
|
5471
5471
|
if ( fbx && fbx.animations && fbx.animations[ndx] ) {
|
|
5472
5472
|
let anim = fbx.animations[ndx];
|
|
5473
5473
|
|
|
5474
|
+
// Get available bone names from avatar skeleton for mapping
|
|
5475
|
+
const availableBones = new Set();
|
|
5476
|
+
if (this.armature) {
|
|
5477
|
+
this.armature.traverse((child) => {
|
|
5478
|
+
if (child.isBone || child.type === 'Bone') {
|
|
5479
|
+
availableBones.add(child.name);
|
|
5480
|
+
}
|
|
5481
|
+
});
|
|
5482
|
+
}
|
|
5483
|
+
|
|
5484
|
+
// Map bone names from FBX to avatar skeleton
|
|
5485
|
+
const boneNameMap = new Map();
|
|
5486
|
+
const mapBoneName = (fbxBoneName) => {
|
|
5487
|
+
// Direct match
|
|
5488
|
+
if (availableBones.has(fbxBoneName)) {
|
|
5489
|
+
return fbxBoneName;
|
|
5490
|
+
}
|
|
5491
|
+
|
|
5492
|
+
// Remove mixamorig prefix if present
|
|
5493
|
+
let normalized = fbxBoneName.replace(/^mixamorig/i, '');
|
|
5494
|
+
if (availableBones.has(normalized)) {
|
|
5495
|
+
return normalized;
|
|
5496
|
+
}
|
|
5497
|
+
|
|
5498
|
+
// Common bone name mappings for Ready Player Me / Mixamo
|
|
5499
|
+
const mappings = {
|
|
5500
|
+
// Arm bones - handle common variations
|
|
5501
|
+
'LeftArm': 'LeftArm',
|
|
5502
|
+
'leftArm': 'LeftArm',
|
|
5503
|
+
'LEFTARM': 'LeftArm',
|
|
5504
|
+
'RightArm': 'RightArm',
|
|
5505
|
+
'rightArm': 'RightArm',
|
|
5506
|
+
'RIGHTARM': 'RightArm',
|
|
5507
|
+
'LeftForeArm': 'LeftForeArm',
|
|
5508
|
+
'leftForeArm': 'LeftForeArm',
|
|
5509
|
+
'leftForearm': 'LeftForeArm',
|
|
5510
|
+
'LeftForearm': 'LeftForeArm',
|
|
5511
|
+
'RightForeArm': 'RightForeArm',
|
|
5512
|
+
'rightForeArm': 'RightForeArm',
|
|
5513
|
+
'rightForearm': 'RightForeArm',
|
|
5514
|
+
'RightForearm': 'RightForeArm',
|
|
5515
|
+
'LeftHand': 'LeftHand',
|
|
5516
|
+
'leftHand': 'LeftHand',
|
|
5517
|
+
'RightHand': 'RightHand',
|
|
5518
|
+
'rightHand': 'RightHand',
|
|
5519
|
+
'LeftShoulder': 'LeftShoulder',
|
|
5520
|
+
'leftShoulder': 'LeftShoulder',
|
|
5521
|
+
'RightShoulder': 'RightShoulder',
|
|
5522
|
+
'rightShoulder': 'RightShoulder',
|
|
5523
|
+
// Spine
|
|
5524
|
+
'Spine': 'Spine1',
|
|
5525
|
+
'spine': 'Spine1',
|
|
5526
|
+
'Spine1': 'Spine1',
|
|
5527
|
+
'Spine2': 'Spine2',
|
|
5528
|
+
// Head/Neck
|
|
5529
|
+
'Head': 'Head',
|
|
5530
|
+
'head': 'Head',
|
|
5531
|
+
'Neck': 'Neck',
|
|
5532
|
+
'neck': 'Neck',
|
|
5533
|
+
// Hips
|
|
5534
|
+
'Hips': 'Hips',
|
|
5535
|
+
'hips': 'Hips',
|
|
5536
|
+
'Root': 'Hips',
|
|
5537
|
+
'root': 'Hips',
|
|
5538
|
+
};
|
|
5539
|
+
|
|
5540
|
+
if (mappings[normalized]) {
|
|
5541
|
+
const mapped = mappings[normalized];
|
|
5542
|
+
if (availableBones.has(mapped)) {
|
|
5543
|
+
return mapped;
|
|
5544
|
+
}
|
|
5545
|
+
}
|
|
5546
|
+
|
|
5547
|
+
// Try case-insensitive match
|
|
5548
|
+
for (const boneName of availableBones) {
|
|
5549
|
+
if (boneName.toLowerCase() === normalized.toLowerCase()) {
|
|
5550
|
+
return boneName;
|
|
5551
|
+
}
|
|
5552
|
+
}
|
|
5553
|
+
|
|
5554
|
+
return null; // No mapping found
|
|
5555
|
+
};
|
|
5556
|
+
|
|
5557
|
+
// Debug: Log FBX bone names and avatar bone names for comparison
|
|
5558
|
+
const fbxBoneNames = new Set();
|
|
5559
|
+
anim.tracks.forEach(track => {
|
|
5560
|
+
const trackParts = track.name.split('.');
|
|
5561
|
+
fbxBoneNames.add(trackParts[0]);
|
|
5562
|
+
});
|
|
5563
|
+
|
|
5564
|
+
console.log('Ready Player Me FBX bone names:', Array.from(fbxBoneNames).sort().join(', '));
|
|
5565
|
+
console.log('Avatar skeleton bone names:', Array.from(availableBones).sort().join(', '));
|
|
5566
|
+
|
|
5567
|
+
// Filter and map animation tracks
|
|
5568
|
+
const mappedTracks = [];
|
|
5569
|
+
const unmappedBones = new Set();
|
|
5570
|
+
anim.tracks.forEach(track => {
|
|
5571
|
+
// Remove mixamorig prefix first
|
|
5572
|
+
let trackName = track.name.replaceAll('mixamorig', '');
|
|
5573
|
+
const trackParts = trackName.split('.');
|
|
5574
|
+
const fbxBoneName = trackParts[0];
|
|
5575
|
+
const property = trackParts[1];
|
|
5576
|
+
|
|
5577
|
+
// Map bone name to avatar skeleton
|
|
5578
|
+
const mappedBoneName = mapBoneName(fbxBoneName);
|
|
5579
|
+
|
|
5580
|
+
if (mappedBoneName && property) {
|
|
5581
|
+
// Create new track with mapped bone name
|
|
5582
|
+
const newTrackName = `${mappedBoneName}.${property}`;
|
|
5583
|
+
const newTrack = track.clone();
|
|
5584
|
+
newTrack.name = newTrackName;
|
|
5585
|
+
mappedTracks.push(newTrack);
|
|
5586
|
+
|
|
5587
|
+
// Store mapping for logging
|
|
5588
|
+
if (fbxBoneName !== mappedBoneName) {
|
|
5589
|
+
boneNameMap.set(fbxBoneName, mappedBoneName);
|
|
5590
|
+
}
|
|
5591
|
+
} else {
|
|
5592
|
+
unmappedBones.add(fbxBoneName);
|
|
5593
|
+
// Log unmapped bones (especially arm bones)
|
|
5594
|
+
if (fbxBoneName.toLowerCase().includes('arm') ||
|
|
5595
|
+
fbxBoneName.toLowerCase().includes('hand') ||
|
|
5596
|
+
fbxBoneName.toLowerCase().includes('shoulder')) {
|
|
5597
|
+
console.warn(`⚠️ Arm bone "${fbxBoneName}" could not be mapped to avatar skeleton`);
|
|
5598
|
+
}
|
|
5599
|
+
}
|
|
5600
|
+
});
|
|
5601
|
+
|
|
5602
|
+
if (unmappedBones.size > 0) {
|
|
5603
|
+
console.warn(`⚠️ ${unmappedBones.size} bone(s) could not be mapped:`, Array.from(unmappedBones).sort().join(', '));
|
|
5604
|
+
}
|
|
5605
|
+
|
|
5606
|
+
// Use mapped tracks if we have any, otherwise use original
|
|
5607
|
+
if (mappedTracks.length > 0) {
|
|
5608
|
+
anim = new THREE.AnimationClip(anim.name, anim.duration, mappedTracks);
|
|
5609
|
+
if (boneNameMap.size > 0) {
|
|
5610
|
+
console.log(`Mapped ${boneNameMap.size} bone(s) for Ready Player Me animation:`,
|
|
5611
|
+
Array.from(boneNameMap.entries()).slice(0, 5).map(([from, to]) => `${from}→${to}`).join(', '));
|
|
5612
|
+
}
|
|
5613
|
+
}
|
|
5614
|
+
|
|
5474
5615
|
// Rename and scale Mixamo tracks, create a pose
|
|
5475
5616
|
const props = {};
|
|
5476
5617
|
anim.tracks.forEach( t => {
|