fix: VRM 0.x voice avatars rest with their arms down (#32270)

This commit is contained in:
G30 2026-10-12 15:32:27 -04:00 • committed by GitHub
parent e4cd58f19e
commit 80ae7f620a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -57,6 +57,7 @@ export async function createAvatarRenderer(
const camera = new THREE.PerspectiveCamera(30, 1, 0.01, 100);
const bone = (name: Parameters<typeof vrm.humanoid.getNormalizedBoneNode>[0]) =>
vrm.humanoid.getNormalizedBoneNode(name);
const mirror = vrm.meta.metaVersion === '0' ? -1 : 1;
const head = bone('head');
const spine = bone('spine');
const chest = bone('chest') ?? bone('upperChest');
@ -82,7 +83,6 @@ export async function createAvatarRenderer(
];
});
// Start all built-in states from a relaxed, slightly asymmetric stance.
// Normalized VRM bones share T-pose axes across VRM 0 and 1.
const restingRotations: Partial<Record<Parameters<typeof bone>[0], [number, number, number]>> = {
hips: [0, 0, 0.025],
spine: [0.025, 0.015, -0.035],
@ -104,7 +104,11 @@ export async function createAvatarRenderer(
rightFoot: [-0.045, -0.035, 0.025]
};
for (const [name, rotation] of Object.entries(restingRotations))
bone(name as Parameters<typeof bone>[0])?.rotation.set(...rotation);
bone(name as Parameters<typeof bone>[0])?.rotation.set(
rotation[0] * mirror,
rotation[1],
rotation[2] * mirror
);
// Resting fingers curl mainly at the first two joints, with a softer index finger.
for (const side of ['left', 'right'] as const) {
const sign = side === 'left' ? -1 : 1;
@ -116,7 +120,7 @@ export async function createAvatarRenderer(
['Distal', 0.04]
] as const) {
const node = bone(`${side}${finger}${joint}`);
if (node) node.rotation.z = sign * amount;
if (node) node.rotation.z = sign * amount * mirror;
}
}
}
@ -326,25 +330,26 @@ export async function createAvatarRenderer(
const breath = Math.sin(elapsed * 1.1) * 0.9 + Math.sin(elapsed * 0.47) * 0.1;
const sway = amount * (0.01 * Math.sin(elapsed * 0.43) + 0.003 * Math.sin(elapsed * 0.71));
if (spine) {
spine.rotation.x += amount * (0.006 * breath + 0.015 * listening);
spine.rotation.x += mirror * amount * (0.006 * breath + 0.015 * listening);
spine.rotation.y += amount * speaking * 0.008 * Math.sin(elapsed * 0.6);
spine.rotation.z += sway;
spine.rotation.z += mirror * sway;
}
if (chest) {
chest.rotation.x -= amount * 0.008 * breath;
chest.rotation.z -= sway * 0.35;
chest.rotation.x -= mirror * amount * 0.008 * breath;
chest.rotation.z -= mirror * sway * 0.35;
}
if (head) {
head.rotation.x +=
mirror *
amount *
(0.006 * breath + speaking * 0.012 * Math.sin(elapsed * 0.85) + listening * 0.025);
head.rotation.y +=
amount * (0.012 * Math.sin(elapsed * 0.32) + speaking * 0.008 * Math.sin(elapsed * 0.6));
head.rotation.z +=
amount * (0.006 * Math.sin(elapsed * 0.27) + listening * 0.025) - sway * 0.5;
mirror * (amount * (0.006 * Math.sin(elapsed * 0.27) + listening * 0.025) - sway * 0.5);
}
if (leftArm) leftArm.rotation.z += amount * 0.004 * breath;
if (rightArm) rightArm.rotation.z -= amount * 0.003 * Math.sin(elapsed * 1.1 - 0.2);
if (leftArm) leftArm.rotation.z += mirror * amount * 0.004 * breath;
if (rightArm) rightArm.rotation.z -= mirror * amount * 0.003 * Math.sin(elapsed * 1.1 - 0.2);
if (leftElbow) leftElbow.rotation.y -= amount * 0.006 * Math.sin(elapsed * 1.1 - 0.3);
if (rightElbow) rightElbow.rotation.y += amount * 0.004 * Math.sin(elapsed * 1.1 - 0.5);
if (current && (bodyWeight > 0 || authored)) {