@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sage-rsc/talking-head-react",
3
- "version": "1.1.5",
3
+ "version": "1.1.6",
4
4
  "description": "A reusable React component for 3D talking avatars with lip-sync and text-to-speech",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",
@@ -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().slice(0, 50).join(', '),
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(`FBX animation "${clip.name}": Mapped ${mappedBones.size} bone(s) to avatar skeleton:`,
5697
- Array.from(mappedBones.entries()).slice(0, 5).map(([from, to]) => `${from} → ${to}`).join(', '),
5698
- mappedBones.size > 5 ? '...' : '');
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(`FBX animation "${clip.name}" contains tracks for ${missingBones.size} bone(s) that couldn't be mapped:`,
5703
- Array.from(missingBones).slice(0, 10).join(', '),
5704
- missingBones.size > 10 ? '...' : '');
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
- if (validTracks.length > 0) {
5708
- console.info(`Filtered ${clip.tracks.length} tracks down to ${validTracks.length} valid tracks (${mappedBones.size} mapped)`);
5709
- } else {
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