From 06ae7bc422a9e75c2dfee88996e1df00f2b49b49 Mon Sep 17 00:00:00 2001 From: luciano Date: Fri, 31 Jul 2026 11:46:22 +0200 Subject: [PATCH] Smooth hips: no horizontal root motion, tanh-saturated bounce MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hard clamp made out-of-scale clips ride its boundary and jerk the model left/right. Hips translations now stay pinned horizontally (the framing compensation cancelled that motion anyway — the moves live in the legs and torso) and the vertical bounce saturates smoothly via tanh at 0.8x the rest length. Same-rig baked clips render identically since their root motion was already being compensated. Co-Authored-By: Claude Fable 5 --- src/model3d.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/model3d.js b/src/model3d.js index 4d5d831..e32bd85 100644 --- a/src/model3d.js +++ b/src/model3d.js @@ -664,10 +664,19 @@ class ModelSim { const k = ch._k; let tx = (V[i3] + (V[j3]-V[i3])*f)*k, ty = (V[i3+1] + (V[j3+1]-V[i3+1])*f)*k, tz = (V[i3+2] + (V[j3+2]-V[i3+2])*f)*k; - // safety net: the offset from the rest position may never exceed - // 1.2x the bone's rest length, whatever the source clip does — this - // keeps foreign clips from throwing the model out of the frame - if (ch._rl > 1e-6) { + if (ch._hips === undefined) { + const nn = this.skel.nodes[ch.node]; + ch._hips = nn.name.split(':').pop().split('.').pop() === 'Hips'; + } + if (ch._hips && ch._rl > 1e-6) { + // Hips: kill the horizontal root motion entirely (it was cancelled + // by the framing compensation anyway, and a hard clamp made it + // jerk left/right); the vertical bounce saturates smoothly. + const max = ch._rl*0.8; + tx = ch._rest[0]; tz = ch._rest[2]; + ty = ch._rest[1] + max*Math.tanh((ty - ch._rest[1])/max); + } else if (ch._rl > 1e-6) { + // other bones (same-rig clips only): sane hard limit const dx = tx - ch._rest[0], dy = ty - ch._rest[1], dz = tz - ch._rest[2]; const d = Math.hypot(dx, dy, dz), max = ch._rl*1.2; if (d > max) {