Dancers follow the music clock, not the visual-speed slider

The global speed control multiplies the visual time, which froze the
dance clips at low values (only max speed showed motion). Clip playback,
the dance director and the procedural dance now run on a real-time
clock — only the preset variant speed scales the moves. The translation
unit-rescale also samples the peak of the first keyframes (robust when a
clip starts near the origin) and clamps the factor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
lucianoandClaude Fable 5 committed 2026-07-31 10:41:38 +02:00
1 parent 14e5b48176
commit 616e70a34d
1 file changed
+10 -4
+10 -4
View File
@@ -562,9 +562,12 @@ class ModelSim {
if (ch._k === undefined) { if (ch._k === undefined) {
const rest = this.skel.nodes[ch.node].t; const rest = this.skel.nodes[ch.node].t;
const rl = Math.hypot(rest[0], rest[1], rest[2]); const rl = Math.hypot(rest[0], rest[1], rest[2]);
const v0 = Math.hypot(V[0], V[1], V[2]); // peak over the first keys: robust when the clip starts near zero
let v0 = 0;
for (let s = 0; s < Math.min(V.length, 24); s += 3)
v0 = Math.max(v0, Math.hypot(V[s], V[s+1], V[s+2]));
const ratio = rl > 1e-6 && v0 > 1e-6 ? v0/rl : 1; const ratio = rl > 1e-6 && v0 > 1e-6 ? v0/rl : 1;
ch._k = (ratio > 3 || ratio < 0.33) ? rl/v0 : 1; ch._k = (ratio > 3 || ratio < 0.33) ? Math.min(1000, Math.max(0.001, rl/v0)) : 1;
} }
const k = ch._k; const k = ch._k;
o.t = [(V[i3] + (V[j3]-V[i3])*f)*k, (V[i3+1] + (V[j3+1]-V[i3+1])*f)*k, o.t = [(V[i3] + (V[j3]-V[i3])*f)*k, (V[i3+1] + (V[j3+1]-V[i3+1])*f)*k,
@@ -920,12 +923,15 @@ class ModelSim {
const dist = this.radius*2.6; const dist = this.radius*2.6;
// live body-driven skinning? else: animation clip or procedural dance // live body-driven skinning? else: animation clip or procedural dance
const skinnedLive = this.skel && pose && pose.skinTargets; const skinnedLive = this.skel && pose && pose.skinTargets;
// Dancing follows the MUSIC, not the global visual-speed slider: use a
// real-time clock (only the preset variant speed scales the moves).
const rt = performance.now()/1000;
let curJoints = null, danceX = 0, danceY = 0, danceZ = 0; let curJoints = null, danceX = 0, danceY = 0, danceZ = 0;
if (this.skel) { if (this.skel) {
if (skinnedLive) { if (skinnedLive) {
curJoints = this._computeJoints(pose.skinTargets); curJoints = this._computeJoints(pose.skinTargets);
} else if (this.anims) { } else if (this.anims) {
curJoints = this._computeJoints(null, this._danceDirector(timeSec, speed, beat)); curJoints = this._computeJoints(null, this._danceDirector(rt, speed, beat));
// keep the dancer framed: cancel the clip's root motion horizontally, // keep the dancer framed: cancel the clip's root motion horizontally,
// keep a taste of the vertical bounce // keep a taste of the vertical bounce
if (this._hipsRest && this._hipsW) { if (this._hipsRest && this._hipsW) {
@@ -934,7 +940,7 @@ class ModelSim {
danceZ = (this._hipsRest[2] - this._hipsW[2]); danceZ = (this._hipsRest[2] - this._hipsW[2]);
} }
} else { } else {
const dance = this._danceTargets(timeSec, beat, bass); const dance = this._danceTargets(rt, beat, bass);
curJoints = this._computeJoints(dance.targets); curJoints = this._computeJoints(dance.targets);
danceY = dance.bounceY*this.radius; danceY = dance.bounceY*this.radius;
} }