@sage-rsc/talking-head-react 1.1.5 → 1.1.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.
- package/README.md +2 -2
- package/dist/index.cjs +9 -2
- package/dist/index.js +729 -665
- package/package.json +1 -1
- package/src/lib/talkinghead.mjs +144 -12
package/package.json
CHANGED
package/src/lib/talkinghead.mjs
CHANGED
|
@@ -5407,12 +5407,24 @@ class TalkingHead {
|
|
|
5407
5407
|
'Spine01': 'Spine1',
|
|
5408
5408
|
'Spine02': 'Spine2',
|
|
5409
5409
|
'Spine03': 'Spine2',
|
|
5410
|
+
'Spine1': 'Spine1',
|
|
5411
|
+
'Spine2': 'Spine2',
|
|
5412
|
+
'Spine': 'Spine1',
|
|
5413
|
+
|
|
5414
|
+
// Head and neck
|
|
5415
|
+
'Head': 'Head',
|
|
5416
|
+
'Neck': 'Neck',
|
|
5417
|
+
'Neck1': 'Neck',
|
|
5418
|
+
'Neck2': 'Neck',
|
|
5410
5419
|
|
|
5411
5420
|
// Left arm mapping
|
|
5412
5421
|
'L_Upperarm': 'LeftArm',
|
|
5413
5422
|
'L_Forearm': 'LeftForeArm',
|
|
5414
5423
|
'L_Hand': 'LeftHand',
|
|
5415
5424
|
'L_Shoulder': 'LeftShoulder',
|
|
5425
|
+
'L_Clavicle': 'LeftShoulder',
|
|
5426
|
+
'L_UpperArm': 'LeftArm',
|
|
5427
|
+
'L_ForeArm': 'LeftForeArm',
|
|
5416
5428
|
'L_Index1': 'LeftHandIndex1',
|
|
5417
5429
|
'L_Index2': 'LeftHandIndex2',
|
|
5418
5430
|
'L_Index3': 'LeftHandIndex3',
|
|
@@ -5437,6 +5449,9 @@ class TalkingHead {
|
|
|
5437
5449
|
'R_Forearm': 'RightForeArm',
|
|
5438
5450
|
'R_Hand': 'RightHand',
|
|
5439
5451
|
'R_Shoulder': 'RightShoulder',
|
|
5452
|
+
'R_Clavicle': 'RightShoulder',
|
|
5453
|
+
'R_UpperArm': 'RightArm',
|
|
5454
|
+
'R_ForeArm': 'RightForeArm',
|
|
5440
5455
|
'R_Index1': 'RightHandIndex1',
|
|
5441
5456
|
'R_Index2': 'RightHandIndex2',
|
|
5442
5457
|
'R_Index3': 'RightHandIndex3',
|
|
@@ -5460,9 +5475,18 @@ class TalkingHead {
|
|
|
5460
5475
|
'L_Thigh': 'LeftUpLeg',
|
|
5461
5476
|
'L_Calf': 'LeftLeg',
|
|
5462
5477
|
'L_Foot': 'LeftFoot',
|
|
5478
|
+
'L_UpLeg': 'LeftUpLeg',
|
|
5479
|
+
'L_Leg': 'LeftLeg',
|
|
5463
5480
|
'R_Thigh': 'RightUpLeg',
|
|
5464
5481
|
'R_Calf': 'RightLeg',
|
|
5465
5482
|
'R_Foot': 'RightFoot',
|
|
5483
|
+
'R_UpLeg': 'RightUpLeg',
|
|
5484
|
+
'R_Leg': 'RightLeg',
|
|
5485
|
+
|
|
5486
|
+
// Root/Hips
|
|
5487
|
+
'Hips': 'Hips',
|
|
5488
|
+
'Root': 'Hips',
|
|
5489
|
+
'Pelvis': 'Hips',
|
|
5466
5490
|
};
|
|
5467
5491
|
|
|
5468
5492
|
// Try mapping
|
|
@@ -5567,6 +5591,49 @@ class TalkingHead {
|
|
|
5567
5591
|
return candidate;
|
|
5568
5592
|
}
|
|
5569
5593
|
}
|
|
5594
|
+
|
|
5595
|
+
// Pattern: R_Clavicle -> RightShoulder
|
|
5596
|
+
if (lowerNormalized.match(/^[rl]_clavicle/)) {
|
|
5597
|
+
const side = lowerNormalized.startsWith('r') ? 'Right' : 'Left';
|
|
5598
|
+
const candidate = `${side}Shoulder`;
|
|
5599
|
+
if (availableBones.has(candidate)) {
|
|
5600
|
+
return candidate;
|
|
5601
|
+
}
|
|
5602
|
+
}
|
|
5603
|
+
|
|
5604
|
+
// Pattern: R_Shoulder -> RightShoulder
|
|
5605
|
+
if (lowerNormalized.match(/^[rl]_shoulder/)) {
|
|
5606
|
+
const side = lowerNormalized.startsWith('r') ? 'Right' : 'Left';
|
|
5607
|
+
const candidate = `${side}Shoulder`;
|
|
5608
|
+
if (availableBones.has(candidate)) {
|
|
5609
|
+
return candidate;
|
|
5610
|
+
}
|
|
5611
|
+
}
|
|
5612
|
+
|
|
5613
|
+
// Pattern: Spine01/02/03 -> Spine1/Spine2
|
|
5614
|
+
if (lowerNormalized.match(/^spine0?(\d+)$/)) {
|
|
5615
|
+
const spineNum = lowerNormalized.match(/^spine0?(\d+)$/)[1];
|
|
5616
|
+
if (spineNum === '1') {
|
|
5617
|
+
if (availableBones.has('Spine1')) return 'Spine1';
|
|
5618
|
+
} else if (spineNum === '2' || spineNum === '3') {
|
|
5619
|
+
if (availableBones.has('Spine2')) return 'Spine2';
|
|
5620
|
+
}
|
|
5621
|
+
}
|
|
5622
|
+
|
|
5623
|
+
// Pattern: Neck1/2 -> Neck
|
|
5624
|
+
if (lowerNormalized.match(/^neck\d*$/)) {
|
|
5625
|
+
if (availableBones.has('Neck')) return 'Neck';
|
|
5626
|
+
}
|
|
5627
|
+
|
|
5628
|
+
// Pattern: Head -> Head
|
|
5629
|
+
if (lowerNormalized === 'head') {
|
|
5630
|
+
if (availableBones.has('Head')) return 'Head';
|
|
5631
|
+
}
|
|
5632
|
+
|
|
5633
|
+
// Pattern: Hips/Pelvis/Root -> Hips
|
|
5634
|
+
if (lowerNormalized.match(/^(hips|pelvis|root)$/)) {
|
|
5635
|
+
if (availableBones.has('Hips')) return 'Hips';
|
|
5636
|
+
}
|
|
5570
5637
|
|
|
5571
5638
|
// Try case-insensitive exact match
|
|
5572
5639
|
for (const boneName of availableBones) {
|
|
@@ -5574,6 +5641,18 @@ class TalkingHead {
|
|
|
5574
5641
|
return boneName;
|
|
5575
5642
|
}
|
|
5576
5643
|
}
|
|
5644
|
+
|
|
5645
|
+
// Try partial match (contains)
|
|
5646
|
+
for (const boneName of availableBones) {
|
|
5647
|
+
const boneLower = boneName.toLowerCase();
|
|
5648
|
+
if (boneLower.includes(lowerNormalized) || lowerNormalized.includes(boneLower)) {
|
|
5649
|
+
// Prefer exact matches or very close matches
|
|
5650
|
+
if (boneLower === lowerNormalized ||
|
|
5651
|
+
boneLower.replace(/[^a-z0-9]/g, '') === lowerNormalized.replace(/[^a-z0-9]/g, '')) {
|
|
5652
|
+
return boneName;
|
|
5653
|
+
}
|
|
5654
|
+
}
|
|
5655
|
+
}
|
|
5577
5656
|
|
|
5578
5657
|
return null; // No mapping found
|
|
5579
5658
|
}
|
|
@@ -5589,14 +5668,25 @@ class TalkingHead {
|
|
|
5589
5668
|
const validTracks = [];
|
|
5590
5669
|
const missingBones = new Set();
|
|
5591
5670
|
const mappedBones = new Map(); // Track mappings for logging
|
|
5671
|
+
const fbxBoneNames = new Set(); // Track all unique FBX bone names
|
|
5592
5672
|
|
|
5593
5673
|
// Debug: Log available bones (first time only)
|
|
5594
5674
|
if (!this._loggedAvailableBones) {
|
|
5595
|
-
console.log('Available avatar bones:', Array.from(availableBones).sort().
|
|
5596
|
-
availableBones.size > 50 ? `... (${availableBones.size} total)` : '');
|
|
5675
|
+
console.log('Available avatar bones:', Array.from(availableBones).sort().join(', '));
|
|
5597
5676
|
this._loggedAvailableBones = true;
|
|
5598
5677
|
}
|
|
5599
5678
|
|
|
5679
|
+
// First pass: collect all unique FBX bone names
|
|
5680
|
+
clip.tracks.forEach(track => {
|
|
5681
|
+
const trackNameParts = track.name.split('.');
|
|
5682
|
+
const fbxBoneName = trackNameParts[0];
|
|
5683
|
+
fbxBoneNames.add(fbxBoneName);
|
|
5684
|
+
});
|
|
5685
|
+
|
|
5686
|
+
console.log(`\n=== FBX Animation "${clip.name}" Bone Analysis ===`);
|
|
5687
|
+
console.log('FBX bone names in animation:', Array.from(fbxBoneNames).sort().join(', '));
|
|
5688
|
+
console.log(`Total FBX bones: ${fbxBoneNames.size}, Avatar bones: ${availableBones.size}\n`);
|
|
5689
|
+
|
|
5600
5690
|
clip.tracks.forEach(track => {
|
|
5601
5691
|
// Extract bone name from track name (e.g., "CC_Base_R_Index3.position" -> "CC_Base_R_Index3")
|
|
5602
5692
|
const trackNameParts = track.name.split('.');
|
|
@@ -5691,22 +5781,64 @@ class TalkingHead {
|
|
|
5691
5781
|
}
|
|
5692
5782
|
});
|
|
5693
5783
|
|
|
5694
|
-
// Log results
|
|
5784
|
+
// Log results with detailed mapping information
|
|
5785
|
+
console.log(`\n=== Mapping Results for "${clip.name}" ===`);
|
|
5786
|
+
|
|
5787
|
+
// Show all mapped bones
|
|
5695
5788
|
if (mappedBones.size > 0) {
|
|
5696
|
-
console.info(
|
|
5697
|
-
|
|
5698
|
-
|
|
5789
|
+
console.info(`✓ Successfully mapped ${mappedBones.size} bone(s):`);
|
|
5790
|
+
Array.from(mappedBones.entries()).forEach(([from, to]) => {
|
|
5791
|
+
console.log(` ${from} → ${to}`);
|
|
5792
|
+
});
|
|
5699
5793
|
}
|
|
5700
5794
|
|
|
5795
|
+
// Show all missing bones with suggestions
|
|
5701
5796
|
if (missingBones.size > 0) {
|
|
5702
|
-
console.warn(
|
|
5703
|
-
|
|
5704
|
-
|
|
5797
|
+
console.warn(`\n✗ Could not map ${missingBones.size} bone(s):`);
|
|
5798
|
+
Array.from(missingBones).sort().forEach(boneName => {
|
|
5799
|
+
console.log(` - ${boneName}`);
|
|
5800
|
+
// Try to suggest a potential mapping
|
|
5801
|
+
const normalized = boneName.replace(/^(CC_Base_|mixamorig)/i, '').replace(/^[RL]_/, (match) => match.toLowerCase());
|
|
5802
|
+
const suggestions = [];
|
|
5803
|
+
for (const avatarBone of availableBones) {
|
|
5804
|
+
const avatarLower = avatarBone.toLowerCase();
|
|
5805
|
+
const normalizedLower = normalized.toLowerCase();
|
|
5806
|
+
if (avatarLower.includes(normalizedLower) || normalizedLower.includes(avatarLower)) {
|
|
5807
|
+
suggestions.push(avatarBone);
|
|
5808
|
+
}
|
|
5809
|
+
}
|
|
5810
|
+
if (suggestions.length > 0) {
|
|
5811
|
+
console.log(` → Possible matches: ${suggestions.slice(0, 3).join(', ')}`);
|
|
5812
|
+
}
|
|
5813
|
+
});
|
|
5705
5814
|
}
|
|
5706
5815
|
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5816
|
+
// Show bones that matched directly (no mapping needed)
|
|
5817
|
+
const directMatches = new Set();
|
|
5818
|
+
fbxBoneNames.forEach(fbxBone => {
|
|
5819
|
+
if (availableBones.has(fbxBone) && !mappedBones.has(fbxBone)) {
|
|
5820
|
+
directMatches.add(fbxBone);
|
|
5821
|
+
}
|
|
5822
|
+
});
|
|
5823
|
+
if (directMatches.size > 0) {
|
|
5824
|
+
console.info(`\n✓ Direct matches (no mapping needed): ${directMatches.size} bone(s)`);
|
|
5825
|
+
Array.from(directMatches).sort().slice(0, 10).forEach(bone => {
|
|
5826
|
+
console.log(` - ${bone}`);
|
|
5827
|
+
});
|
|
5828
|
+
if (directMatches.size > 10) {
|
|
5829
|
+
console.log(` ... and ${directMatches.size - 10} more`);
|
|
5830
|
+
}
|
|
5831
|
+
}
|
|
5832
|
+
|
|
5833
|
+
console.log(`\n=== Summary ===`);
|
|
5834
|
+
console.log(`Total FBX tracks: ${clip.tracks.length}`);
|
|
5835
|
+
console.log(`Valid tracks: ${validTracks.length}`);
|
|
5836
|
+
console.log(`Mapped bones: ${mappedBones.size}`);
|
|
5837
|
+
console.log(`Direct matches: ${directMatches.size}`);
|
|
5838
|
+
console.log(`Missing bones: ${missingBones.size}`);
|
|
5839
|
+
console.log(`========================================\n`);
|
|
5840
|
+
|
|
5841
|
+
if (validTracks.length === 0) {
|
|
5710
5842
|
console.error(`No valid tracks found for animation "${clip.name}". All bones are missing or couldn't be mapped.`);
|
|
5711
5843
|
}
|
|
5712
5844
|
|