@sage-rsc/talking-head-react 1.3.7 → 1.3.9

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.js CHANGED
@@ -4395,18 +4395,31 @@ class Be {
4395
4395
  }
4396
4396
  /**
4397
4397
  * Apply shoulder adjustment to lower shoulders to a more natural position
4398
+ * This is called from updatePoseBase for pose-based animations
4398
4399
  */
4399
4400
  applyShoulderAdjustment() {
4400
- const e = new y.Euler();
4401
+ const t = new y.Euler(), e = 1;
4401
4402
  if (this.poseAvatar.props["LeftShoulder.quaternion"]) {
4402
4403
  const n = this.poseAvatar.props["LeftShoulder.quaternion"];
4403
- e.setFromQuaternion(n, "XYZ"), e.x += -0.6, n.setFromEuler(e, "XYZ");
4404
+ t.setFromQuaternion(n, "XYZ"), t.x > e && (t.x = Math.max(e, t.x - 0.4)), n.setFromEuler(t, "XYZ");
4404
4405
  }
4405
4406
  if (this.poseAvatar.props["RightShoulder.quaternion"]) {
4406
4407
  const n = this.poseAvatar.props["RightShoulder.quaternion"];
4407
- e.setFromQuaternion(n, "XYZ"), e.x += -0.6, n.setFromEuler(e, "XYZ");
4408
+ t.setFromQuaternion(n, "XYZ"), t.x > e && (t.x = Math.max(e, t.x - 0.4)), n.setFromEuler(t, "XYZ");
4408
4409
  }
4409
4410
  }
4411
+ /**
4412
+ * Apply shoulder adjustment directly to bone objects
4413
+ * This is called AFTER FBX animations update to ensure shoulders stay relaxed
4414
+ * regardless of what the animation sets
4415
+ */
4416
+ applyShoulderAdjustmentToBones() {
4417
+ if (!this.armature) return;
4418
+ const t = this.armature.getObjectByName("LeftShoulder"), e = this.armature.getObjectByName("RightShoulder");
4419
+ if (!t || !e) return;
4420
+ const n = new y.Euler(), i = new y.Quaternion(), s = 1;
4421
+ t.quaternion && (n.setFromQuaternion(t.quaternion, "XYZ"), n.x > s && (n.x = Math.max(s, n.x - 0.4)), i.setFromEuler(n, "XYZ"), t.quaternion.copy(i), t.updateMatrixWorld(!0)), e.quaternion && (n.setFromQuaternion(e.quaternion, "XYZ"), n.x > s && (n.x = Math.max(s, n.x - 0.4)), i.setFromEuler(n, "XYZ"), e.quaternion.copy(i), e.updateMatrixWorld(!0));
4422
+ }
4410
4423
  /**
4411
4424
  * Update avatar pose deltas
4412
4425
  */
@@ -5208,7 +5221,7 @@ class Be {
5208
5221
  eyeLookOutRight: [null, 0],
5209
5222
  eyeContact: [0]
5210
5223
  }
5211
- })))), e > 2 * this.animFrameDur && (e = 2 * this.animFrameDur), (this.viewName !== "full" || this.isAvatarOnly) && (n = this.mtRandomized[Math.floor(Math.random() * this.mtRandomized.length)], i = this.mtAvatar[n], i.needsUpdate || Object.assign(i, { base: (this.mood.baseline[n] || 0) + (1 + l / 255) * Math.random() / 5, needsUpdate: !0 })), this.updatePoseBase(this.animClock), this.mixer && this.mixer.update(e / 1e3 * this.mixer.timeScale), this.updatePoseDelta(), (this.isSpeaking || this.isListening) && h ? l > this.volumeMax ? (this.volumeHeadBase = 0.05, Math.random() > 0.6 && (this.volumeHeadTarget = -0.05 - Math.random() / 15), this.volumeMax = l) : (this.volumeMax *= 0.92, this.volumeHeadTarget = this.volumeHeadBase - 0.9 * (this.volumeHeadBase - this.volumeHeadTarget)) : (this.volumeHeadTarget = 0, this.volumeMax = 0), n = this.volumeHeadTarget - this.volumeHeadCurrent, i = Math.abs(n), i > 1e-4 && (o = i * (this.volumeHeadEasing(Math.min(1, this.volumeHeadVelocity * e / 1e3 / i) / 2 + 0.5) - 0.5), this.volumeHeadCurrent += Math.sign(n) * Math.min(i, o)), Math.abs(this.volumeHeadCurrent) > 1e-4 && ($.setFromAxisAngle(mt, this.volumeHeadCurrent), this.objectNeck.quaternion.multiply($)), We.setFromObject(this.armature), this.objectLeftToeBase.getWorldPosition(ve), ve.sub(this.armature.position), this.objectRightToeBase.getWorldPosition(Re), Re.sub(this.armature.position), this.objectHips.position.y -= We.min.y / 2, this.objectHips.position.x -= (ve.x + Re.x) / 4, this.objectHips.position.z -= (ve.z + Re.z) / 2, this.dynamicbones.update(e), this.fbxAnimationLoader && this.fbxAnimationLoader.update(), this.opt.update && this.opt.update(e), this.updateMorphTargets(e), this.isAvatarOnly)
5224
+ })))), e > 2 * this.animFrameDur && (e = 2 * this.animFrameDur), (this.viewName !== "full" || this.isAvatarOnly) && (n = this.mtRandomized[Math.floor(Math.random() * this.mtRandomized.length)], i = this.mtAvatar[n], i.needsUpdate || Object.assign(i, { base: (this.mood.baseline[n] || 0) + (1 + l / 255) * Math.random() / 5, needsUpdate: !0 })), this.updatePoseBase(this.animClock), this.mixer && this.mixer.update(e / 1e3 * this.mixer.timeScale), this.updatePoseDelta(), (this.isSpeaking || this.isListening) && h ? l > this.volumeMax ? (this.volumeHeadBase = 0.05, Math.random() > 0.6 && (this.volumeHeadTarget = -0.05 - Math.random() / 15), this.volumeMax = l) : (this.volumeMax *= 0.92, this.volumeHeadTarget = this.volumeHeadBase - 0.9 * (this.volumeHeadBase - this.volumeHeadTarget)) : (this.volumeHeadTarget = 0, this.volumeMax = 0), n = this.volumeHeadTarget - this.volumeHeadCurrent, i = Math.abs(n), i > 1e-4 && (o = i * (this.volumeHeadEasing(Math.min(1, this.volumeHeadVelocity * e / 1e3 / i) / 2 + 0.5) - 0.5), this.volumeHeadCurrent += Math.sign(n) * Math.min(i, o)), Math.abs(this.volumeHeadCurrent) > 1e-4 && ($.setFromAxisAngle(mt, this.volumeHeadCurrent), this.objectNeck.quaternion.multiply($)), We.setFromObject(this.armature), this.objectLeftToeBase.getWorldPosition(ve), ve.sub(this.armature.position), this.objectRightToeBase.getWorldPosition(Re), Re.sub(this.armature.position), this.objectHips.position.y -= We.min.y / 2, this.objectHips.position.x -= (ve.x + Re.x) / 4, this.objectHips.position.z -= (ve.z + Re.z) / 2, this.dynamicbones.update(e), this.fbxAnimationLoader && this.fbxAnimationLoader.update(), this.applyShoulderAdjustmentToBones(), this.opt.update && this.opt.update(e), this.updateMorphTargets(e), this.isAvatarOnly)
5212
5225
  this.stats && this.stats.end();
5213
5226
  else {
5214
5227
  if (this.cameraClock !== null && this.cameraClock < 1e3) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sage-rsc/talking-head-react",
3
- "version": "1.3.7",
3
+ "version": "1.3.9",
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",
@@ -0,0 +1,51 @@
1
+ #!/bin/bash
2
+ # Script to help update TalkingHead while preserving customizations
3
+
4
+ set -e
5
+
6
+ echo "=== TalkingHead Update Helper ==="
7
+ echo ""
8
+ echo "This script helps identify customizations to preserve during update."
9
+ echo ""
10
+
11
+ # Colors for output
12
+ GREEN='\033[0;32m'
13
+ YELLOW='\033[1;33m'
14
+ NC='\033[0m' # No Color
15
+
16
+ echo -e "${GREEN}Step 1: Identifying custom methods...${NC}"
17
+ echo ""
18
+ echo "=== Position Locking Methods ==="
19
+ grep -n "lockAvatarPosition\|unlockAvatarPosition\|maintainLockedPosition" src/lib/talkinghead.mjs | head -20
20
+
21
+ echo ""
22
+ echo "=== Shoulder Adjustment Methods ==="
23
+ grep -n "applyShoulderAdjustment" src/lib/talkinghead.mjs | head -10
24
+
25
+ echo ""
26
+ echo "=== Position Locking Calls ==="
27
+ grep -n "lockAvatarPosition\|unlockAvatarPosition\|positionWasLocked\|lockedPosition" src/lib/talkinghead.mjs | head -20
28
+
29
+ echo ""
30
+ echo -e "${YELLOW}Step 2: Custom files to preserve (DO NOT REPLACE):${NC}"
31
+ echo "- src/components/TalkingHeadComponent.jsx"
32
+ echo "- src/components/TalkingHeadAvatar.jsx"
33
+ echo "- src/components/SimpleTalkingAvatar.jsx"
34
+ echo "- src/components/CurriculumLearning.jsx"
35
+ echo "- src/config/ttsConfig.js"
36
+ echo "- src/lib/enhancedFBXLoader.js"
37
+ echo "- src/lib/fbxAnimationLoader.js"
38
+ echo "- src/utils/animationLoader.js"
39
+ echo "- scripts/generate-animation-manifest.js"
40
+
41
+ echo ""
42
+ echo -e "${GREEN}Step 3: Next steps:${NC}"
43
+ echo "1. Download latest TalkingHead source"
44
+ echo "2. Compare talkinghead.mjs files"
45
+ echo "3. Merge changes while preserving custom methods above"
46
+ echo "4. Test thoroughly"
47
+ echo ""
48
+ echo "To compare files, use:"
49
+ echo " diff -u old-talkinghead.mjs src/lib/talkinghead.mjs > talkinghead.diff"
50
+ echo ""
51
+
@@ -1615,17 +1615,20 @@ class TalkingHead {
1615
1615
 
1616
1616
  /**
1617
1617
  * Apply shoulder adjustment to lower shoulders to a more natural position
1618
+ * This is called from updatePoseBase for pose-based animations
1618
1619
  */
1619
1620
  applyShoulderAdjustment() {
1620
- // Shoulder adjustment: reduce X-axis rotation by ~0.6 radians (34 degrees) to lower shoulders to a relaxed position
1621
- const shoulderAdjustment = -0.6; // Negative to lower shoulders (increased for more relaxed look)
1622
1621
  const tempEuler = new THREE.Euler();
1622
+ const maxX = 1.0; // Maximum X rotation for relaxed shoulders
1623
1623
 
1624
1624
  // Adjust left shoulder
1625
1625
  if (this.poseAvatar.props['LeftShoulder.quaternion']) {
1626
1626
  const leftShoulder = this.poseAvatar.props['LeftShoulder.quaternion'];
1627
1627
  tempEuler.setFromQuaternion(leftShoulder, 'XYZ');
1628
- tempEuler.x += shoulderAdjustment; // Reduce X rotation to lower shoulder
1628
+ // Reduce X rotation if too high for relaxed shoulders
1629
+ if (tempEuler.x > maxX) {
1630
+ tempEuler.x = Math.max(maxX, tempEuler.x - 0.4);
1631
+ }
1629
1632
  leftShoulder.setFromEuler(tempEuler, 'XYZ');
1630
1633
  }
1631
1634
 
@@ -1633,10 +1636,62 @@ class TalkingHead {
1633
1636
  if (this.poseAvatar.props['RightShoulder.quaternion']) {
1634
1637
  const rightShoulder = this.poseAvatar.props['RightShoulder.quaternion'];
1635
1638
  tempEuler.setFromQuaternion(rightShoulder, 'XYZ');
1636
- tempEuler.x += shoulderAdjustment; // Reduce X rotation to lower shoulder
1639
+ // Reduce X rotation if too high for relaxed shoulders
1640
+ if (tempEuler.x > maxX) {
1641
+ tempEuler.x = Math.max(maxX, tempEuler.x - 0.4);
1642
+ }
1637
1643
  rightShoulder.setFromEuler(tempEuler, 'XYZ');
1638
1644
  }
1639
1645
  }
1646
+
1647
+ /**
1648
+ * Apply shoulder adjustment directly to bone objects
1649
+ * This is called AFTER FBX animations update to ensure shoulders stay relaxed
1650
+ * regardless of what the animation sets
1651
+ */
1652
+ applyShoulderAdjustmentToBones() {
1653
+ if (!this.armature) return;
1654
+
1655
+ // Get shoulder bones directly from armature
1656
+ const leftShoulderBone = this.armature.getObjectByName('LeftShoulder');
1657
+ const rightShoulderBone = this.armature.getObjectByName('RightShoulder');
1658
+
1659
+ if (!leftShoulderBone || !rightShoulderBone) return;
1660
+
1661
+ const tempEuler = new THREE.Euler();
1662
+ const tempQuaternion = new THREE.Quaternion();
1663
+
1664
+ // Target relaxed shoulder rotation for natural appearance
1665
+ // Reduce X rotation (forward tilt) to lower shoulders
1666
+ // Typical values: 1.5-1.8 (high/stiff) -> 0.9-1.1 (relaxed)
1667
+ const maxX = 1.0; // Maximum X rotation for relaxed shoulders
1668
+
1669
+ // Adjust left shoulder bone directly
1670
+ if (leftShoulderBone.quaternion) {
1671
+ tempEuler.setFromQuaternion(leftShoulderBone.quaternion, 'XYZ');
1672
+ // Reduce X rotation if it's too high for relaxed shoulders
1673
+ if (tempEuler.x > maxX) {
1674
+ // Smooth interpolation towards relaxed position
1675
+ tempEuler.x = Math.max(maxX, tempEuler.x - 0.4);
1676
+ }
1677
+ tempQuaternion.setFromEuler(tempEuler, 'XYZ');
1678
+ leftShoulderBone.quaternion.copy(tempQuaternion);
1679
+ leftShoulderBone.updateMatrixWorld(true);
1680
+ }
1681
+
1682
+ // Adjust right shoulder bone directly
1683
+ if (rightShoulderBone.quaternion) {
1684
+ tempEuler.setFromQuaternion(rightShoulderBone.quaternion, 'XYZ');
1685
+ // Reduce X rotation if it's too high for relaxed shoulders
1686
+ if (tempEuler.x > maxX) {
1687
+ // Smooth interpolation towards relaxed position
1688
+ tempEuler.x = Math.max(maxX, tempEuler.x - 0.4);
1689
+ }
1690
+ tempQuaternion.setFromEuler(tempEuler, 'XYZ');
1691
+ rightShoulderBone.quaternion.copy(tempQuaternion);
1692
+ rightShoulderBone.updateMatrixWorld(true);
1693
+ }
1694
+ }
1640
1695
 
1641
1696
  /**
1642
1697
  * Update avatar pose deltas
@@ -3221,6 +3276,10 @@ class TalkingHead {
3221
3276
  if (this.fbxAnimationLoader) {
3222
3277
  this.fbxAnimationLoader.update();
3223
3278
  }
3279
+
3280
+ // Apply shoulder adjustment AFTER FBX animations to ensure relaxed shoulders
3281
+ // This overrides any shoulder positions set by animations
3282
+ this.applyShoulderAdjustmentToBones();
3224
3283
 
3225
3284
  // Custom update
3226
3285
  if ( this.opt.update ) {