@sage-rsc/talking-head-react 1.4.8 → 1.5.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.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { jsxs as Pe, jsx as ye } from "react/jsx-runtime";
2
- import { forwardRef as Me, useRef as W, useState as pe, useEffect as ce, useCallback as O, useImperativeHandle as Fe, useLayoutEffect as Xe } from "react";
2
+ import { forwardRef as Me, useRef as W, useState as pe, useEffect as ce, useCallback as O, useImperativeHandle as Fe, useLayoutEffect as Ze } from "react";
3
3
  import * as y from "three";
4
4
  import { OrbitControls as je } from "three/addons/controls/OrbitControls.js";
5
5
  import { GLTFLoader as Ye } from "three/addons/loaders/GLTFLoader.js";
@@ -2629,7 +2629,7 @@ const ct = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
2629
2629
  fr: rt,
2630
2630
  fi: ut,
2631
2631
  lt: ct
2632
- }, $ = new y.Quaternion(), Z = new y.Euler(), ve = new y.Vector3(), Re = new y.Vector3(), We = new y.Box3();
2632
+ }, $ = new y.Quaternion(), X = new y.Euler(), ve = new y.Vector3(), Re = new y.Vector3(), We = new y.Box3();
2633
2633
  new y.Matrix4();
2634
2634
  new y.Matrix4();
2635
2635
  new y.Vector3();
@@ -4418,16 +4418,16 @@ class Be {
4418
4418
  if (!this.armature) return;
4419
4419
  const t = this.armature.getObjectByName("LeftShoulder"), e = this.armature.getObjectByName("RightShoulder");
4420
4420
  if (!t || !e) return;
4421
- const n = new y.Euler(), i = new y.Quaternion(), s = 0.6, o = 0.7;
4421
+ const n = new y.Euler(), i = new y.Quaternion(), o = this.mixer && this.currentFBXAction && this.currentFBXAction.isRunning() ? 0.5 : 0.6, l = 0.7;
4422
4422
  if (t.quaternion) {
4423
4423
  n.setFromQuaternion(t.quaternion, "XYZ");
4424
- const l = n.x;
4425
- n.x > o ? n.x = s : n.x > s && (n.x = s + (n.x - s) * 0.2), Math.abs(n.x - l) > 0.01 && (i.setFromEuler(n, "XYZ"), t.quaternion.copy(i), t.updateMatrixWorld(!0));
4424
+ const h = n.x;
4425
+ n.x > l ? n.x = l + (n.x - l) * (1 - o) : n.x > 0.5 && (n.x = n.x * (1 - o * 0.5)), Math.abs(n.x - h) > 0.01 && (i.setFromEuler(n, "XYZ"), t.quaternion.copy(i), t.updateMatrixWorld(!0));
4426
4426
  }
4427
4427
  if (e.quaternion) {
4428
4428
  n.setFromQuaternion(e.quaternion, "XYZ");
4429
- const l = n.x;
4430
- n.x > o ? n.x = s : n.x > s && (n.x = s + (n.x - s) * 0.2), Math.abs(n.x - l) > 0.01 && (i.setFromEuler(n, "XYZ"), e.quaternion.copy(i), e.updateMatrixWorld(!0));
4429
+ const h = n.x;
4430
+ n.x > l ? n.x = l + (n.x - l) * (1 - o) : n.x > 0.5 && (n.x = n.x * (1 - o * 0.5)), Math.abs(n.x - h) > 0.01 && (i.setFromEuler(n, "XYZ"), e.quaternion.copy(i), e.updateMatrixWorld(!0));
4431
4431
  }
4432
4432
  }
4433
4433
  /**
@@ -4436,9 +4436,9 @@ class Be {
4436
4436
  updatePoseDelta() {
4437
4437
  for (const [t, e] of Object.entries(this.poseDelta.props)) {
4438
4438
  if (e.x === 0 && e.y === 0 && e.z === 0) continue;
4439
- Z.set(e.x, e.y, e.z);
4439
+ X.set(e.x, e.y, e.z);
4440
4440
  const n = this.poseAvatar.props[t];
4441
- n.isQuaternion ? ($.setFromEuler(Z), n.multiply($)) : n.isVector3 && n.add(Z);
4441
+ n.isQuaternion ? ($.setFromEuler(X), n.multiply($)) : n.isVector3 && n.add(X);
4442
4442
  }
4443
4443
  }
4444
4444
  /**
@@ -5210,7 +5210,7 @@ class Be {
5210
5210
  }, i.x ? new y.Vector3(i.x, i.y, i.z) : null, !0, i.d);
5211
5211
  break;
5212
5212
  }
5213
- if ((h || r) && (Z.setFromQuaternion(this.poseAvatar.props["Head.quaternion"]), Z.x = Math.max(-0.9, Math.min(0.9, 2 * Z.x - 0.5)), Z.y = Math.max(-0.9, Math.min(0.9, -2.5 * Z.y)), h ? (Object.assign(this.mtAvatar.eyesLookDown, { system: Z.x < 0 ? -Z.x : 0, needsUpdate: !0 }), Object.assign(this.mtAvatar.eyesLookUp, { system: Z.x < 0 ? 0 : Z.x, needsUpdate: !0 }), Object.assign(this.mtAvatar.eyeLookInLeft, { system: Z.y < 0 ? -Z.y : 0, needsUpdate: !0 }), Object.assign(this.mtAvatar.eyeLookOutLeft, { system: Z.y < 0 ? 0 : Z.y, needsUpdate: !0 }), Object.assign(this.mtAvatar.eyeLookInRight, { system: Z.y < 0 ? 0 : Z.y, needsUpdate: !0 }), Object.assign(this.mtAvatar.eyeLookOutRight, { system: Z.y < 0 ? -Z.y : 0, needsUpdate: !0 }), r && (n = -this.mtAvatar.bodyRotateY.value, i = this.gaussianRandom(-0.2, 0.2), this.animQueue.push(this.animFactory({
5213
+ if ((h || r) && (X.setFromQuaternion(this.poseAvatar.props["Head.quaternion"]), X.x = Math.max(-0.9, Math.min(0.9, 2 * X.x - 0.5)), X.y = Math.max(-0.9, Math.min(0.9, -2.5 * X.y)), h ? (Object.assign(this.mtAvatar.eyesLookDown, { system: X.x < 0 ? -X.x : 0, needsUpdate: !0 }), Object.assign(this.mtAvatar.eyesLookUp, { system: X.x < 0 ? 0 : X.x, needsUpdate: !0 }), Object.assign(this.mtAvatar.eyeLookInLeft, { system: X.y < 0 ? -X.y : 0, needsUpdate: !0 }), Object.assign(this.mtAvatar.eyeLookOutLeft, { system: X.y < 0 ? 0 : X.y, needsUpdate: !0 }), Object.assign(this.mtAvatar.eyeLookInRight, { system: X.y < 0 ? 0 : X.y, needsUpdate: !0 }), Object.assign(this.mtAvatar.eyeLookOutRight, { system: X.y < 0 ? -X.y : 0, needsUpdate: !0 }), r && (n = -this.mtAvatar.bodyRotateY.value, i = this.gaussianRandom(-0.2, 0.2), this.animQueue.push(this.animFactory({
5214
5214
  name: "headmove",
5215
5215
  dt: [[1e3, 2e3], [1e3, 2e3, 1, 2], [1e3, 2e3], [1e3, 2e3, 1, 2]],
5216
5216
  vs: {
@@ -6199,10 +6199,10 @@ class Be {
6199
6199
  }
6200
6200
  this.objectLeftEye.updateMatrixWorld(!0), this.objectRightEye.updateMatrixWorld(!0), ve.setFromMatrixPosition(this.objectLeftEye.matrixWorld), Re.setFromMatrixPosition(this.objectRightEye.matrixWorld), ve.add(Re).divideScalar(2), $.copy(this.armature.quaternion), $.multiply(this.poseTarget.props["Hips.quaternion"]), $.multiply(this.poseTarget.props["Spine.quaternion"]), $.multiply(this.poseTarget.props["Spine1.quaternion"]), $.multiply(this.poseTarget.props["Spine2.quaternion"]), $.multiply(this.poseTarget.props["Neck.quaternion"]), $.multiply(this.poseTarget.props["Head.quaternion"]);
6201
6201
  const n = new y.Vector3().subVectors(e, ve).normalize(), i = Math.atan2(n.x, n.z), s = Math.asin(-n.y);
6202
- Z.set(s, i, 0, "YXZ");
6203
- const l = new y.Quaternion().setFromEuler(Z), h = new y.Quaternion().copy(l).multiply($.clone().invert());
6204
- Z.setFromQuaternion(h, "YXZ");
6205
- let r = Z.x / (40 / 24) + 0.2, u = Z.y / (9 / 4), a = Math.min(0.6, Math.max(-0.3, r)), d = Math.min(0.8, Math.max(-0.8, u)), c = (Math.random() - 0.5) / 4, g = (Math.random() - 0.5) / 4;
6202
+ X.set(s, i, 0, "YXZ");
6203
+ const l = new y.Quaternion().setFromEuler(X), h = new y.Quaternion().copy(l).multiply($.clone().invert());
6204
+ X.setFromQuaternion(h, "YXZ");
6205
+ let r = X.x / (40 / 24) + 0.2, u = X.y / (9 / 4), a = Math.min(0.6, Math.max(-0.3, r)), d = Math.min(0.8, Math.max(-0.8, u)), c = (Math.random() - 0.5) / 4, g = (Math.random() - 0.5) / 4;
6206
6206
  if (t) {
6207
6207
  let x = this.animQueue.findIndex((k) => k.template.name === "lookat");
6208
6208
  x !== -1 && this.animQueue.splice(x, 1);
@@ -6237,8 +6237,8 @@ class Be {
6237
6237
  const s = new y.Vector3().setFromMatrixPosition(this.objectLeftEye.matrixWorld), o = new y.Vector3().setFromMatrixPosition(this.objectRightEye.matrixWorld), l = new y.Vector3().addVectors(s, o).divideScalar(2);
6238
6238
  l.project(this.camera);
6239
6239
  let h = (l.x + 1) / 2 * i.width + i.left, r = -(l.y - 1) / 2 * i.height + i.top;
6240
- t === null && (t = h), e === null && (e = r), $.copy(this.armature.quaternion), $.multiply(this.poseTarget.props["Hips.quaternion"]), $.multiply(this.poseTarget.props["Spine.quaternion"]), $.multiply(this.poseTarget.props["Spine1.quaternion"]), $.multiply(this.poseTarget.props["Spine2.quaternion"]), $.multiply(this.poseTarget.props["Neck.quaternion"]), $.multiply(this.poseTarget.props["Head.quaternion"]), Z.setFromQuaternion($);
6241
- let u = Z.x / (40 / 24), a = Z.y / (9 / 4), d = Math.min(0.4, Math.max(-0.4, this.camera.rotation.x)), c = Math.min(0.4, Math.max(-0.4, this.camera.rotation.y)), g = Math.max(window.innerWidth - h, h), x = Math.max(window.innerHeight - r, r), f = this.convertRange(e, [r - x, r + x], [-0.3, 0.6]) - u + d, k = this.convertRange(t, [h - g, h + g], [-0.8, 0.8]) - a + c;
6240
+ t === null && (t = h), e === null && (e = r), $.copy(this.armature.quaternion), $.multiply(this.poseTarget.props["Hips.quaternion"]), $.multiply(this.poseTarget.props["Spine.quaternion"]), $.multiply(this.poseTarget.props["Spine1.quaternion"]), $.multiply(this.poseTarget.props["Spine2.quaternion"]), $.multiply(this.poseTarget.props["Neck.quaternion"]), $.multiply(this.poseTarget.props["Head.quaternion"]), X.setFromQuaternion($);
6241
+ let u = X.x / (40 / 24), a = X.y / (9 / 4), d = Math.min(0.4, Math.max(-0.4, this.camera.rotation.x)), c = Math.min(0.4, Math.max(-0.4, this.camera.rotation.y)), g = Math.max(window.innerWidth - h, h), x = Math.max(window.innerHeight - r, r), f = this.convertRange(e, [r - x, r + x], [-0.3, 0.6]) - u + d, k = this.convertRange(t, [h - g, h + g], [-0.8, 0.8]) - a + c;
6242
6242
  f = Math.min(0.6, Math.max(-0.3, f)), k = Math.min(0.8, Math.max(-0.8, k));
6243
6243
  let D = (Math.random() - 0.5) / 4, p = (Math.random() - 0.5) / 4;
6244
6244
  if (n) {
@@ -6581,8 +6581,8 @@ class Be {
6581
6581
  if (d.tracks.forEach((L) => {
6582
6582
  const z = L.name.replaceAll("mixamorig", "").split("."), Y = z[0], S = z[1], E = x(Y);
6583
6583
  if (E && S) {
6584
- const j = `${E}.${S}`, X = L.clone();
6585
- X.name = j, p.push(X), Y !== E && g.set(Y, E);
6584
+ const j = `${E}.${S}`, Z = L.clone();
6585
+ Z.name = j, p.push(Z), Y !== E && g.set(Y, E);
6586
6586
  } else
6587
6587
  B.add(Y), (Y.toLowerCase().includes("arm") || Y.toLowerCase().includes("hand") || Y.toLowerCase().includes("shoulder")) && console.warn(`⚠️ Arm bone "${Y}" could not be mapped to avatar skeleton`);
6588
6588
  }), B.size > 0 && console.warn(`⚠️ ${B.size} bone(s) could not be mapped:`, Array.from(B).sort().join(", ")), p.length > 0) {
@@ -6853,7 +6853,7 @@ const Ve = Me(({
6853
6853
  style: x = {},
6854
6854
  animations: f = {}
6855
6855
  }, k) => {
6856
- const D = W(null), p = W(null), B = W(r), T = W(null), R = W(null), L = W(!1), I = W({ remainingText: null, originalText: null, options: null }), z = W([]), Y = W(0), [S, E] = pe(!0), [j, X] = pe(null), [oe, le] = pe(!1), [ge, de] = pe(!1);
6856
+ const D = W(null), p = W(null), B = W(r), T = W(null), R = W(null), L = W(!1), I = W({ remainingText: null, originalText: null, options: null }), z = W([]), Y = W(0), [S, E] = pe(!0), [j, Z] = pe(null), [oe, le] = pe(!1), [ge, de] = pe(!1);
6857
6857
  ce(() => {
6858
6858
  L.current = ge;
6859
6859
  }, [ge]), ce(() => {
@@ -6902,7 +6902,7 @@ const Ve = Me(({
6902
6902
  }, F = O(async () => {
6903
6903
  if (!(!D.current || p.current))
6904
6904
  try {
6905
- if (E(!0), X(null), p.current = new Be(D.current, v), p.current.controls && (p.current.controls.enableRotate = !1, p.current.controls.enableZoom = !1, p.current.controls.enablePan = !1, p.current.controls.enableDamping = !1), f && Object.keys(f).length > 0 && (p.current.customAnimations = f), await p.current.showAvatar(b, (K) => {
6905
+ if (E(!0), Z(null), p.current = new Be(D.current, v), p.current.controls && (p.current.controls.enableRotate = !1, p.current.controls.enableZoom = !1, p.current.controls.enablePan = !1, p.current.controls.enableDamping = !1), f && Object.keys(f).length > 0 && (p.current.customAnimations = f), await p.current.showAvatar(b, (K) => {
6906
6906
  if (K.lengthComputable) {
6907
6907
  const ne = Math.min(100, Math.round(K.loaded / K.total * 100));
6908
6908
  d(ne);
@@ -6926,7 +6926,7 @@ const Ve = Me(({
6926
6926
  document.removeEventListener("visibilitychange", U);
6927
6927
  };
6928
6928
  } catch (w) {
6929
- console.error("Error initializing TalkingHead:", w), X(w.message || "Failed to initialize avatar"), E(!1), c(w);
6929
+ console.error("Error initializing TalkingHead:", w), Z(w.message || "Failed to initialize avatar"), E(!1), c(w);
6930
6930
  }
6931
6931
  }, [V, t, e, n, i, s, o, r, l, h, u]);
6932
6932
  ce(() => (F(), () => {
@@ -6987,8 +6987,8 @@ const Ve = Me(({
6987
6987
  ie = !0, H && (clearInterval(H), H = null, R.current = null);
6988
6988
  try {
6989
6989
  U.onSpeechEnd();
6990
- } catch (Ze) {
6991
- console.error("Error in onSpeechEnd callback:", Ze);
6990
+ } catch (Xe) {
6991
+ console.error("Error in onSpeechEnd callback:", Xe);
6992
6992
  }
6993
6993
  }
6994
6994
  }, 100);
@@ -6998,7 +6998,7 @@ const Ve = Me(({
6998
6998
  await P(), p.current && p.current.lipsync && (p.current.setSlowdownRate && p.current.setSlowdownRate(1.05), p.current.speakText(w, me));
6999
6999
  }, 100);
7000
7000
  } catch (K) {
7001
- console.error("Error speaking text:", K), X(K.message || "Failed to speak text");
7001
+ console.error("Error speaking text:", K), Z(K.message || "Failed to speak text");
7002
7002
  }
7003
7003
  }, [oe, P, b.lipsyncLang]), te = O(() => {
7004
7004
  p.current && (p.current.stopSpeaking(), p.current.setSlowdownRate && p.current.setSlowdownRate(1), T.current = null, de(!1));
@@ -7255,20 +7255,20 @@ const pt = Me(({
7255
7255
  try {
7256
7256
  if (a(!0), c(null), r.current = new Be(h.current, B), await r.current.showAvatar(p, (j) => {
7257
7257
  if (j.lengthComputable) {
7258
- const X = Math.min(100, Math.round(j.loaded / j.total * 100));
7259
- t(X);
7258
+ const Z = Math.min(100, Math.round(j.loaded / j.total * 100));
7259
+ t(Z);
7260
7260
  }
7261
7261
  }), r.current.morphs && r.current.morphs.length > 0) {
7262
7262
  const j = r.current.morphs[0].morphTargetDictionary;
7263
7263
  console.log("Available morph targets:", Object.keys(j));
7264
- const X = Object.keys(j).filter((oe) => oe.startsWith("viseme_"));
7265
- console.log("Viseme morph targets found:", X), X.length === 0 && (console.warn("No viseme morph targets found! Lip-sync will not work properly."), console.log("Expected viseme targets: viseme_aa, viseme_E, viseme_I, viseme_O, viseme_U, viseme_PP, viseme_SS, viseme_TH, viseme_DD, viseme_FF, viseme_kk, viseme_nn, viseme_RR, viseme_CH, viseme_sil"));
7264
+ const Z = Object.keys(j).filter((oe) => oe.startsWith("viseme_"));
7265
+ console.log("Viseme morph targets found:", Z), Z.length === 0 && (console.warn("No viseme morph targets found! Lip-sync will not work properly."), console.log("Expected viseme targets: viseme_aa, viseme_E, viseme_I, viseme_O, viseme_U, viseme_PP, viseme_SS, viseme_TH, viseme_DD, viseme_FF, viseme_kk, viseme_nn, viseme_RR, viseme_CH, viseme_sil"));
7266
7266
  }
7267
7267
  if (await new Promise((j) => {
7268
- const X = () => {
7269
- r.current.lipsync && Object.keys(r.current.lipsync).length > 0 ? (console.log("Lip-sync modules loaded:", Object.keys(r.current.lipsync)), j()) : (console.log("Waiting for lip-sync modules to load..."), setTimeout(X, 100));
7268
+ const Z = () => {
7269
+ r.current.lipsync && Object.keys(r.current.lipsync).length > 0 ? (console.log("Lip-sync modules loaded:", Object.keys(r.current.lipsync)), j()) : (console.log("Waiting for lip-sync modules to load..."), setTimeout(Z, 100));
7270
7270
  };
7271
- X();
7271
+ Z();
7272
7272
  }), r.current && r.current.setShowFullAvatar)
7273
7273
  try {
7274
7274
  r.current.setShowFullAvatar(!0), console.log("Avatar initialized in full body mode");
@@ -7311,14 +7311,14 @@ const pt = Me(({
7311
7311
  if (r.current.setShowFullAvatar)
7312
7312
  try {
7313
7313
  r.current.setShowFullAvatar(!0);
7314
- } catch (X) {
7315
- console.warn("Error setting full body mode:", X);
7314
+ } catch (Z) {
7315
+ console.warn("Error setting full body mode:", Z);
7316
7316
  }
7317
7317
  if (S.includes("."))
7318
7318
  try {
7319
7319
  r.current.playAnimation(S, null, 10, 0, 0.01, E), console.log("Playing animation:", S);
7320
- } catch (X) {
7321
- console.log(`Failed to play ${S}:`, X);
7320
+ } catch (Z) {
7321
+ console.log(`Failed to play ${S}:`, Z);
7322
7322
  try {
7323
7323
  r.current.setBodyMovement("idle"), console.log("Fallback to idle animation");
7324
7324
  } catch (oe) {
@@ -7326,9 +7326,9 @@ const pt = Me(({
7326
7326
  }
7327
7327
  }
7328
7328
  else {
7329
- const X = [".fbx", ".glb", ".gltf"];
7329
+ const Z = [".fbx", ".glb", ".gltf"];
7330
7330
  let oe = !1;
7331
- for (const le of X)
7331
+ for (const le of Z)
7332
7332
  try {
7333
7333
  r.current.playAnimation(S + le, null, 10, 0, 0.01, E), console.log("Playing animation:", S + le), oe = !0;
7334
7334
  break;
@@ -7497,7 +7497,7 @@ const yt = Me(({
7497
7497
  // e.g., "idle" - will randomly select from this group when idle
7498
7498
  autoSpeak: T = !1
7499
7499
  }, R) => {
7500
- const L = W(null), I = W(null), z = W(u), Y = W(null), S = W(null), E = W(!1), j = W({ remainingText: null, originalText: null, options: null }), X = W([]), [oe, le] = pe(!0), [ge, de] = pe(null), [ee, be] = pe(!1), [Q, b] = pe(!1), [v, F] = pe(D), P = W(null);
7500
+ const L = W(null), I = W(null), z = W(u), Y = W(null), S = W(null), E = W(!1), j = W({ remainingText: null, originalText: null, options: null }), Z = W([]), [oe, le] = pe(!0), [ge, de] = pe(null), [ee, be] = pe(!1), [Q, b] = pe(!1), [v, F] = pe(D), P = W(null);
7501
7501
  ce(() => {
7502
7502
  E.current = Q;
7503
7503
  }, [Q]), ce(() => {
@@ -7637,9 +7637,9 @@ const yt = Me(({
7637
7637
  }
7638
7638
  await Le();
7639
7639
  const G = H.animationGroup || p;
7640
- G && !H.skipAnimation ? (console.log(`🎬 Attempting to play animation from group: "${G}"`), console.log(`📊 Current avatarBody: "${e}", loadedAnimations:`, v), w(G)) : console.log(`⏭️ Skipping animation (group: ${G}, skipAnimation: ${H.skipAnimation})`), j.current = { remainingText: null, originalText: null, options: null }, X.current = [], Y.current = { text: A, options: H }, S.current && (clearInterval(S.current), S.current = null), b(!1), E.current = !1;
7640
+ G && !H.skipAnimation ? (console.log(`🎬 Attempting to play animation from group: "${G}"`), console.log(`📊 Current avatarBody: "${e}", loadedAnimations:`, v), w(G)) : console.log(`⏭️ Skipping animation (group: ${G}, skipAnimation: ${H.skipAnimation})`), j.current = { remainingText: null, originalText: null, options: null }, Z.current = [], Y.current = { text: A, options: H }, S.current && (clearInterval(S.current), S.current = null), b(!1), E.current = !1;
7641
7641
  const J = A.split(/[.!?]+/).filter((se) => se.trim().length > 0);
7642
- X.current = J;
7642
+ Z.current = J;
7643
7643
  const ie = {
7644
7644
  lipsyncLang: H.lipsyncLang || "en",
7645
7645
  onSpeechEnd: () => {
@@ -8021,7 +8021,7 @@ const ft = Me(({
8021
8021
  }), u.current && (u.current.setMood("happy"), u.current.setBodyMovement("idle"));
8022
8022
  } else
8023
8023
  k.current && k.current();
8024
- }, []), X = O(() => {
8024
+ }, []), Z = O(() => {
8025
8025
  const b = R();
8026
8026
  let v = null;
8027
8027
  if (b?.avatar_script && b?.body) {
@@ -8242,11 +8242,11 @@ const ft = Me(({
8242
8242
  c.current && c.current();
8243
8243
  }, 10);
8244
8244
  }, [h, R]);
8245
- Xe(() => {
8246
- c.current = X, g.current = j, x.current = z, f.current = E, k.current = Y, D.current = S, p.current = oe;
8245
+ Ze(() => {
8246
+ c.current = Z, g.current = j, x.current = z, f.current = E, k.current = Y, D.current = S, p.current = oe;
8247
8247
  }), Fe(r, () => ({
8248
8248
  // Curriculum control methods
8249
- startTeaching: X,
8249
+ startTeaching: Z,
8250
8250
  startQuestions: S,
8251
8251
  handleAnswerSelect: oe,
8252
8252
  handleCodeTestResult: le,
@@ -8310,7 +8310,7 @@ const ft = Me(({
8310
8310
  handleResize: () => u.current?.handleResize(),
8311
8311
  // Avatar readiness check (always returns current value)
8312
8312
  isAvatarReady: () => u.current?.isReady || !1
8313
- }), [X, S, oe, le, E, j, z, Y, ee, L, R]);
8313
+ }), [Z, S, oe, le, E, j, z, Y, ee, L, R]);
8314
8314
  const Q = T.current || {
8315
8315
  avatarUrl: "/avatars/brunette.glb",
8316
8316
  avatarBody: "F",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sage-rsc/talking-head-react",
3
- "version": "1.4.8",
3
+ "version": "1.5.0",
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",
@@ -1667,28 +1667,32 @@ class TalkingHead {
1667
1667
  const tempEuler = new THREE.Euler();
1668
1668
  const tempQuaternion = new THREE.Quaternion();
1669
1669
 
1670
- // Research-based relaxed shoulder rotation values
1671
- // Natural relaxed shoulders: X rotation ~0.5-0.7 radians (much lower for natural look)
1672
- // High/stiff shoulders: X rotation ~1.5-1.8 radians
1673
- // We aggressively clamp to a very relaxed position
1674
- const targetX = 0.6; // Target X rotation for relaxed, natural shoulders (radians) - lowered significantly
1675
- const maxX = 0.7; // Maximum allowed X rotation - lowered significantly
1670
+ // Check if FBX animation is playing
1671
+ const isFBXPlaying = this.mixer && this.currentFBXAction && this.currentFBXAction.isRunning();
1672
+
1673
+ // Instead of forcing to a fixed value, reduce X rotation proportionally
1674
+ // This maintains the relative relationship between shoulders and arms
1675
+ // Natural relaxed shoulders: X rotation typically 0.3-0.6 radians
1676
+ // High shoulders: X rotation > 0.8 radians
1677
+ // We reduce high rotations by 40-50% to bring them down while maintaining arm relationships
1678
+ const reductionFactor = isFBXPlaying ? 0.5 : 0.6; // Reduce by 50% during FBX, 40% otherwise
1679
+ const maxAllowedX = 0.7; // Maximum allowed X rotation
1676
1680
 
1677
1681
  // Adjust left shoulder bone directly
1678
1682
  if (leftShoulderBone.quaternion) {
1679
1683
  tempEuler.setFromQuaternion(leftShoulderBone.quaternion, 'XYZ');
1680
1684
  const originalX = tempEuler.x;
1681
1685
 
1682
- // Aggressively clamp X rotation to relaxed position
1683
- if (tempEuler.x > maxX) {
1684
- // Force to target relaxed position
1685
- tempEuler.x = targetX;
1686
- } else if (tempEuler.x > targetX) {
1687
- // Smoothly reduce if slightly above target
1688
- tempEuler.x = targetX + (tempEuler.x - targetX) * 0.2; // More aggressive reduction
1686
+ // Reduce X rotation proportionally if it's too high
1687
+ if (tempEuler.x > maxAllowedX) {
1688
+ // Reduce by percentage to maintain arm-shoulder relationship
1689
+ tempEuler.x = maxAllowedX + (tempEuler.x - maxAllowedX) * (1 - reductionFactor);
1690
+ } else if (tempEuler.x > 0.5) {
1691
+ // Slight reduction for moderately high shoulders
1692
+ tempEuler.x = tempEuler.x * (1 - reductionFactor * 0.5);
1689
1693
  }
1690
1694
 
1691
- // Only update if we actually changed something
1695
+ // Only update if we changed something significantly
1692
1696
  if (Math.abs(tempEuler.x - originalX) > 0.01) {
1693
1697
  tempQuaternion.setFromEuler(tempEuler, 'XYZ');
1694
1698
  leftShoulderBone.quaternion.copy(tempQuaternion);
@@ -1701,16 +1705,16 @@ class TalkingHead {
1701
1705
  tempEuler.setFromQuaternion(rightShoulderBone.quaternion, 'XYZ');
1702
1706
  const originalX = tempEuler.x;
1703
1707
 
1704
- // Aggressively clamp X rotation to relaxed position
1705
- if (tempEuler.x > maxX) {
1706
- // Force to target relaxed position
1707
- tempEuler.x = targetX;
1708
- } else if (tempEuler.x > targetX) {
1709
- // Smoothly reduce if slightly above target
1710
- tempEuler.x = targetX + (tempEuler.x - targetX) * 0.2; // More aggressive reduction
1708
+ // Reduce X rotation proportionally if it's too high
1709
+ if (tempEuler.x > maxAllowedX) {
1710
+ // Reduce by percentage to maintain arm-shoulder relationship
1711
+ tempEuler.x = maxAllowedX + (tempEuler.x - maxAllowedX) * (1 - reductionFactor);
1712
+ } else if (tempEuler.x > 0.5) {
1713
+ // Slight reduction for moderately high shoulders
1714
+ tempEuler.x = tempEuler.x * (1 - reductionFactor * 0.5);
1711
1715
  }
1712
1716
 
1713
- // Only update if we actually changed something
1717
+ // Only update if we changed something significantly
1714
1718
  if (Math.abs(tempEuler.x - originalX) > 0.01) {
1715
1719
  tempQuaternion.setFromEuler(tempEuler, 'XYZ');
1716
1720
  rightShoulderBone.quaternion.copy(tempQuaternion);