@sage-rsc/talking-head-react 1.4.9 → 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.cjs +1 -1
- package/dist/index.js +51 -42
- package/package.json +1 -1
- package/src/lib/talkinghead.mjs +36 -43
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
|
|
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(),
|
|
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,8 +4418,17 @@ 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(),
|
|
4422
|
-
|
|
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
|
+
if (t.quaternion) {
|
|
4423
|
+
n.setFromQuaternion(t.quaternion, "XYZ");
|
|
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
|
+
}
|
|
4427
|
+
if (e.quaternion) {
|
|
4428
|
+
n.setFromQuaternion(e.quaternion, "XYZ");
|
|
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
|
+
}
|
|
4423
4432
|
}
|
|
4424
4433
|
/**
|
|
4425
4434
|
* Update avatar pose deltas
|
|
@@ -4427,9 +4436,9 @@ class Be {
|
|
|
4427
4436
|
updatePoseDelta() {
|
|
4428
4437
|
for (const [t, e] of Object.entries(this.poseDelta.props)) {
|
|
4429
4438
|
if (e.x === 0 && e.y === 0 && e.z === 0) continue;
|
|
4430
|
-
|
|
4439
|
+
X.set(e.x, e.y, e.z);
|
|
4431
4440
|
const n = this.poseAvatar.props[t];
|
|
4432
|
-
n.isQuaternion ? ($.setFromEuler(
|
|
4441
|
+
n.isQuaternion ? ($.setFromEuler(X), n.multiply($)) : n.isVector3 && n.add(X);
|
|
4433
4442
|
}
|
|
4434
4443
|
}
|
|
4435
4444
|
/**
|
|
@@ -5201,7 +5210,7 @@ class Be {
|
|
|
5201
5210
|
}, i.x ? new y.Vector3(i.x, i.y, i.z) : null, !0, i.d);
|
|
5202
5211
|
break;
|
|
5203
5212
|
}
|
|
5204
|
-
if ((h || r) && (
|
|
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({
|
|
5205
5214
|
name: "headmove",
|
|
5206
5215
|
dt: [[1e3, 2e3], [1e3, 2e3, 1, 2], [1e3, 2e3], [1e3, 2e3, 1, 2]],
|
|
5207
5216
|
vs: {
|
|
@@ -6190,10 +6199,10 @@ class Be {
|
|
|
6190
6199
|
}
|
|
6191
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"]);
|
|
6192
6201
|
const n = new y.Vector3().subVectors(e, ve).normalize(), i = Math.atan2(n.x, n.z), s = Math.asin(-n.y);
|
|
6193
|
-
|
|
6194
|
-
const l = new y.Quaternion().setFromEuler(
|
|
6195
|
-
|
|
6196
|
-
let r =
|
|
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;
|
|
6197
6206
|
if (t) {
|
|
6198
6207
|
let x = this.animQueue.findIndex((k) => k.template.name === "lookat");
|
|
6199
6208
|
x !== -1 && this.animQueue.splice(x, 1);
|
|
@@ -6228,8 +6237,8 @@ class Be {
|
|
|
6228
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);
|
|
6229
6238
|
l.project(this.camera);
|
|
6230
6239
|
let h = (l.x + 1) / 2 * i.width + i.left, r = -(l.y - 1) / 2 * i.height + i.top;
|
|
6231
|
-
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"]),
|
|
6232
|
-
let u =
|
|
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;
|
|
6233
6242
|
f = Math.min(0.6, Math.max(-0.3, f)), k = Math.min(0.8, Math.max(-0.8, k));
|
|
6234
6243
|
let D = (Math.random() - 0.5) / 4, p = (Math.random() - 0.5) / 4;
|
|
6235
6244
|
if (n) {
|
|
@@ -6572,8 +6581,8 @@ class Be {
|
|
|
6572
6581
|
if (d.tracks.forEach((L) => {
|
|
6573
6582
|
const z = L.name.replaceAll("mixamorig", "").split("."), Y = z[0], S = z[1], E = x(Y);
|
|
6574
6583
|
if (E && S) {
|
|
6575
|
-
const j = `${E}.${S}`,
|
|
6576
|
-
|
|
6584
|
+
const j = `${E}.${S}`, Z = L.clone();
|
|
6585
|
+
Z.name = j, p.push(Z), Y !== E && g.set(Y, E);
|
|
6577
6586
|
} else
|
|
6578
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`);
|
|
6579
6588
|
}), B.size > 0 && console.warn(`⚠️ ${B.size} bone(s) could not be mapped:`, Array.from(B).sort().join(", ")), p.length > 0) {
|
|
@@ -6844,7 +6853,7 @@ const Ve = Me(({
|
|
|
6844
6853
|
style: x = {},
|
|
6845
6854
|
animations: f = {}
|
|
6846
6855
|
}, k) => {
|
|
6847
|
-
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,
|
|
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);
|
|
6848
6857
|
ce(() => {
|
|
6849
6858
|
L.current = ge;
|
|
6850
6859
|
}, [ge]), ce(() => {
|
|
@@ -6893,7 +6902,7 @@ const Ve = Me(({
|
|
|
6893
6902
|
}, F = O(async () => {
|
|
6894
6903
|
if (!(!D.current || p.current))
|
|
6895
6904
|
try {
|
|
6896
|
-
if (E(!0),
|
|
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) => {
|
|
6897
6906
|
if (K.lengthComputable) {
|
|
6898
6907
|
const ne = Math.min(100, Math.round(K.loaded / K.total * 100));
|
|
6899
6908
|
d(ne);
|
|
@@ -6917,7 +6926,7 @@ const Ve = Me(({
|
|
|
6917
6926
|
document.removeEventListener("visibilitychange", U);
|
|
6918
6927
|
};
|
|
6919
6928
|
} catch (w) {
|
|
6920
|
-
console.error("Error initializing TalkingHead:", w),
|
|
6929
|
+
console.error("Error initializing TalkingHead:", w), Z(w.message || "Failed to initialize avatar"), E(!1), c(w);
|
|
6921
6930
|
}
|
|
6922
6931
|
}, [V, t, e, n, i, s, o, r, l, h, u]);
|
|
6923
6932
|
ce(() => (F(), () => {
|
|
@@ -6978,8 +6987,8 @@ const Ve = Me(({
|
|
|
6978
6987
|
ie = !0, H && (clearInterval(H), H = null, R.current = null);
|
|
6979
6988
|
try {
|
|
6980
6989
|
U.onSpeechEnd();
|
|
6981
|
-
} catch (
|
|
6982
|
-
console.error("Error in onSpeechEnd callback:",
|
|
6990
|
+
} catch (Xe) {
|
|
6991
|
+
console.error("Error in onSpeechEnd callback:", Xe);
|
|
6983
6992
|
}
|
|
6984
6993
|
}
|
|
6985
6994
|
}, 100);
|
|
@@ -6989,7 +6998,7 @@ const Ve = Me(({
|
|
|
6989
6998
|
await P(), p.current && p.current.lipsync && (p.current.setSlowdownRate && p.current.setSlowdownRate(1.05), p.current.speakText(w, me));
|
|
6990
6999
|
}, 100);
|
|
6991
7000
|
} catch (K) {
|
|
6992
|
-
console.error("Error speaking text:", K),
|
|
7001
|
+
console.error("Error speaking text:", K), Z(K.message || "Failed to speak text");
|
|
6993
7002
|
}
|
|
6994
7003
|
}, [oe, P, b.lipsyncLang]), te = O(() => {
|
|
6995
7004
|
p.current && (p.current.stopSpeaking(), p.current.setSlowdownRate && p.current.setSlowdownRate(1), T.current = null, de(!1));
|
|
@@ -7246,20 +7255,20 @@ const pt = Me(({
|
|
|
7246
7255
|
try {
|
|
7247
7256
|
if (a(!0), c(null), r.current = new Be(h.current, B), await r.current.showAvatar(p, (j) => {
|
|
7248
7257
|
if (j.lengthComputable) {
|
|
7249
|
-
const
|
|
7250
|
-
t(
|
|
7258
|
+
const Z = Math.min(100, Math.round(j.loaded / j.total * 100));
|
|
7259
|
+
t(Z);
|
|
7251
7260
|
}
|
|
7252
7261
|
}), r.current.morphs && r.current.morphs.length > 0) {
|
|
7253
7262
|
const j = r.current.morphs[0].morphTargetDictionary;
|
|
7254
7263
|
console.log("Available morph targets:", Object.keys(j));
|
|
7255
|
-
const
|
|
7256
|
-
console.log("Viseme morph targets found:",
|
|
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"));
|
|
7257
7266
|
}
|
|
7258
7267
|
if (await new Promise((j) => {
|
|
7259
|
-
const
|
|
7260
|
-
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(
|
|
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));
|
|
7261
7270
|
};
|
|
7262
|
-
|
|
7271
|
+
Z();
|
|
7263
7272
|
}), r.current && r.current.setShowFullAvatar)
|
|
7264
7273
|
try {
|
|
7265
7274
|
r.current.setShowFullAvatar(!0), console.log("Avatar initialized in full body mode");
|
|
@@ -7302,14 +7311,14 @@ const pt = Me(({
|
|
|
7302
7311
|
if (r.current.setShowFullAvatar)
|
|
7303
7312
|
try {
|
|
7304
7313
|
r.current.setShowFullAvatar(!0);
|
|
7305
|
-
} catch (
|
|
7306
|
-
console.warn("Error setting full body mode:",
|
|
7314
|
+
} catch (Z) {
|
|
7315
|
+
console.warn("Error setting full body mode:", Z);
|
|
7307
7316
|
}
|
|
7308
7317
|
if (S.includes("."))
|
|
7309
7318
|
try {
|
|
7310
7319
|
r.current.playAnimation(S, null, 10, 0, 0.01, E), console.log("Playing animation:", S);
|
|
7311
|
-
} catch (
|
|
7312
|
-
console.log(`Failed to play ${S}:`,
|
|
7320
|
+
} catch (Z) {
|
|
7321
|
+
console.log(`Failed to play ${S}:`, Z);
|
|
7313
7322
|
try {
|
|
7314
7323
|
r.current.setBodyMovement("idle"), console.log("Fallback to idle animation");
|
|
7315
7324
|
} catch (oe) {
|
|
@@ -7317,9 +7326,9 @@ const pt = Me(({
|
|
|
7317
7326
|
}
|
|
7318
7327
|
}
|
|
7319
7328
|
else {
|
|
7320
|
-
const
|
|
7329
|
+
const Z = [".fbx", ".glb", ".gltf"];
|
|
7321
7330
|
let oe = !1;
|
|
7322
|
-
for (const le of
|
|
7331
|
+
for (const le of Z)
|
|
7323
7332
|
try {
|
|
7324
7333
|
r.current.playAnimation(S + le, null, 10, 0, 0.01, E), console.log("Playing animation:", S + le), oe = !0;
|
|
7325
7334
|
break;
|
|
@@ -7488,7 +7497,7 @@ const yt = Me(({
|
|
|
7488
7497
|
// e.g., "idle" - will randomly select from this group when idle
|
|
7489
7498
|
autoSpeak: T = !1
|
|
7490
7499
|
}, R) => {
|
|
7491
|
-
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 }),
|
|
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);
|
|
7492
7501
|
ce(() => {
|
|
7493
7502
|
E.current = Q;
|
|
7494
7503
|
}, [Q]), ce(() => {
|
|
@@ -7628,9 +7637,9 @@ const yt = Me(({
|
|
|
7628
7637
|
}
|
|
7629
7638
|
await Le();
|
|
7630
7639
|
const G = H.animationGroup || p;
|
|
7631
|
-
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 },
|
|
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;
|
|
7632
7641
|
const J = A.split(/[.!?]+/).filter((se) => se.trim().length > 0);
|
|
7633
|
-
|
|
7642
|
+
Z.current = J;
|
|
7634
7643
|
const ie = {
|
|
7635
7644
|
lipsyncLang: H.lipsyncLang || "en",
|
|
7636
7645
|
onSpeechEnd: () => {
|
|
@@ -8012,7 +8021,7 @@ const ft = Me(({
|
|
|
8012
8021
|
}), u.current && (u.current.setMood("happy"), u.current.setBodyMovement("idle"));
|
|
8013
8022
|
} else
|
|
8014
8023
|
k.current && k.current();
|
|
8015
|
-
}, []),
|
|
8024
|
+
}, []), Z = O(() => {
|
|
8016
8025
|
const b = R();
|
|
8017
8026
|
let v = null;
|
|
8018
8027
|
if (b?.avatar_script && b?.body) {
|
|
@@ -8233,11 +8242,11 @@ const ft = Me(({
|
|
|
8233
8242
|
c.current && c.current();
|
|
8234
8243
|
}, 10);
|
|
8235
8244
|
}, [h, R]);
|
|
8236
|
-
|
|
8237
|
-
c.current =
|
|
8245
|
+
Ze(() => {
|
|
8246
|
+
c.current = Z, g.current = j, x.current = z, f.current = E, k.current = Y, D.current = S, p.current = oe;
|
|
8238
8247
|
}), Fe(r, () => ({
|
|
8239
8248
|
// Curriculum control methods
|
|
8240
|
-
startTeaching:
|
|
8249
|
+
startTeaching: Z,
|
|
8241
8250
|
startQuestions: S,
|
|
8242
8251
|
handleAnswerSelect: oe,
|
|
8243
8252
|
handleCodeTestResult: le,
|
|
@@ -8301,7 +8310,7 @@ const ft = Me(({
|
|
|
8301
8310
|
handleResize: () => u.current?.handleResize(),
|
|
8302
8311
|
// Avatar readiness check (always returns current value)
|
|
8303
8312
|
isAvatarReady: () => u.current?.isReady || !1
|
|
8304
|
-
}), [
|
|
8313
|
+
}), [Z, S, oe, le, E, j, z, Y, ee, L, R]);
|
|
8305
8314
|
const Q = T.current || {
|
|
8306
8315
|
avatarUrl: "/avatars/brunette.glb",
|
|
8307
8316
|
avatarBody: "F",
|
package/package.json
CHANGED
package/src/lib/talkinghead.mjs
CHANGED
|
@@ -1667,66 +1667,59 @@ class TalkingHead {
|
|
|
1667
1667
|
const tempEuler = new THREE.Euler();
|
|
1668
1668
|
const tempQuaternion = new THREE.Quaternion();
|
|
1669
1669
|
|
|
1670
|
-
// Check if FBX animation is playing
|
|
1670
|
+
// Check if FBX animation is playing
|
|
1671
1671
|
const isFBXPlaying = this.mixer && this.currentFBXAction && this.currentFBXAction.isRunning();
|
|
1672
1672
|
|
|
1673
|
-
//
|
|
1674
|
-
//
|
|
1675
|
-
//
|
|
1676
|
-
//
|
|
1677
|
-
|
|
1678
|
-
const
|
|
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
|
|
1679
1680
|
|
|
1680
1681
|
// Adjust left shoulder bone directly
|
|
1681
1682
|
if (leftShoulderBone.quaternion) {
|
|
1682
1683
|
tempEuler.setFromQuaternion(leftShoulderBone.quaternion, 'XYZ');
|
|
1684
|
+
const originalX = tempEuler.x;
|
|
1683
1685
|
|
|
1684
|
-
//
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
if (isFBXPlaying) {
|
|
1692
|
-
// During FBX animations, force to target immediately
|
|
1693
|
-
tempEuler.x = targetX;
|
|
1694
|
-
} else {
|
|
1695
|
-
// Otherwise, reduce smoothly but still aggressively
|
|
1696
|
-
tempEuler.x = targetX + (tempEuler.x - targetX) * 0.1;
|
|
1697
|
-
}
|
|
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);
|
|
1698
1693
|
}
|
|
1699
1694
|
|
|
1700
|
-
//
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1695
|
+
// Only update if we changed something significantly
|
|
1696
|
+
if (Math.abs(tempEuler.x - originalX) > 0.01) {
|
|
1697
|
+
tempQuaternion.setFromEuler(tempEuler, 'XYZ');
|
|
1698
|
+
leftShoulderBone.quaternion.copy(tempQuaternion);
|
|
1699
|
+
leftShoulderBone.updateMatrixWorld(true);
|
|
1700
|
+
}
|
|
1704
1701
|
}
|
|
1705
1702
|
|
|
1706
1703
|
// Adjust right shoulder bone directly
|
|
1707
1704
|
if (rightShoulderBone.quaternion) {
|
|
1708
1705
|
tempEuler.setFromQuaternion(rightShoulderBone.quaternion, 'XYZ');
|
|
1706
|
+
const originalX = tempEuler.x;
|
|
1709
1707
|
|
|
1710
|
-
//
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
if (isFBXPlaying) {
|
|
1718
|
-
// During FBX animations, force to target immediately
|
|
1719
|
-
tempEuler.x = targetX;
|
|
1720
|
-
} else {
|
|
1721
|
-
// Otherwise, reduce smoothly but still aggressively
|
|
1722
|
-
tempEuler.x = targetX + (tempEuler.x - targetX) * 0.1;
|
|
1723
|
-
}
|
|
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);
|
|
1724
1715
|
}
|
|
1725
1716
|
|
|
1726
|
-
//
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1717
|
+
// Only update if we changed something significantly
|
|
1718
|
+
if (Math.abs(tempEuler.x - originalX) > 0.01) {
|
|
1719
|
+
tempQuaternion.setFromEuler(tempEuler, 'XYZ');
|
|
1720
|
+
rightShoulderBone.quaternion.copy(tempQuaternion);
|
|
1721
|
+
rightShoulderBone.updateMatrixWorld(true);
|
|
1722
|
+
}
|
|
1730
1723
|
}
|
|
1731
1724
|
}
|
|
1732
1725
|
|