fix: freeze chase-cam heading during rolls and kicks
Browse filesThe trunk tumbles through a roll (and shakes during kicks), so the yaw
extracted from its quaternion spins wildly and whipped the chase camera
around. The chase now lazily latches the last healthy yaw and holds it
for the whole one-shot; position/target easing still follows the trunk.
No timers: both one-shots deterministically hand back to walk, where
live yaw tracking resumes and the position lerp absorbs the small
heading correction. Measured over a full roll: 180 deg trunk-yaw
excursion, 0.2 deg camera azimuth swing.
Cache-buster bumped to 28.
Co-authored-by: Cursor <cursoragent@cursor.com>
- index.html +1 -1
- rl.js +18 -4
index.html
CHANGED
|
@@ -476,7 +476,7 @@
|
|
| 476 |
appended: a plain src attribute can 401 when the browser blocks the
|
| 477 |
*.static.hf.space auth cookie inside the hub iframe. -->
|
| 478 |
<script type="module">
|
| 479 |
-
const V = "
|
| 480 |
const sign = new URLSearchParams(location.search).get("__sign");
|
| 481 |
const suffix = sign ? `&__sign=${encodeURIComponent(sign)}` : "";
|
| 482 |
import(`./rl.js?v=${V}${suffix}`);
|
|
|
|
| 476 |
appended: a plain src attribute can 401 when the browser blocks the
|
| 477 |
*.static.hf.space auth cookie inside the hub iframe. -->
|
| 478 |
<script type="module">
|
| 479 |
+
const V = "28"; // bump on rl.js changes to defeat heuristic caching
|
| 480 |
const sign = new URLSearchParams(location.search).get("__sign");
|
| 481 |
const suffix = sign ? `&__sign=${encodeURIComponent(sign)}` : "";
|
| 482 |
import(`./rl.js?v=${V}${suffix}`);
|
rl.js
CHANGED
|
@@ -627,13 +627,27 @@ const CHASE_PITCH = 0.42; // rad above horizontal, keeps the floor in view
|
|
| 627 |
const CHASE_EASE = 0.05; // exponential ease, cinematic on turns
|
| 628 |
const _chasePos = new THREE.Vector3();
|
| 629 |
const _chaseDir = new THREE.Vector3();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 630 |
function updateChaseCam() {
|
| 631 |
if (!chaseCam) return;
|
| 632 |
const qpos = data.qpos;
|
| 633 |
-
|
| 634 |
-
|
| 635 |
-
|
| 636 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 637 |
const dist = camera.position.distanceTo(controls.target);
|
| 638 |
const horiz = dist * Math.cos(CHASE_PITCH);
|
| 639 |
const vert = dist * Math.sin(CHASE_PITCH);
|
|
|
|
| 627 |
const CHASE_EASE = 0.05; // exponential ease, cinematic on turns
|
| 628 |
const _chasePos = new THREE.Vector3();
|
| 629 |
const _chaseDir = new THREE.Vector3();
|
| 630 |
+
// During one-shot rolls and kicks the trunk tumbles, so the yaw read from
|
| 631 |
+
// its quaternion spins wildly and would whip the camera around. Lazily
|
| 632 |
+
// latch the last healthy yaw while walking/sitting and hold it for the
|
| 633 |
+
// whole one-shot; the position/target easing keeps following the trunk.
|
| 634 |
+
// No timers needed: both one-shots deterministically hand back to walk,
|
| 635 |
+
// and the camera position lerp absorbs the small heading correction when
|
| 636 |
+
// live tracking resumes (rolls end facing roughly the same way).
|
| 637 |
+
let chaseHeldYaw = 0;
|
| 638 |
function updateChaseCam() {
|
| 639 |
if (!chaseCam) return;
|
| 640 |
const qpos = data.qpos;
|
| 641 |
+
let yaw;
|
| 642 |
+
if (mode === "roll" || isKick()) {
|
| 643 |
+
yaw = chaseHeldYaw;
|
| 644 |
+
} else {
|
| 645 |
+
yaw = Math.atan2(
|
| 646 |
+
2 * (qpos[3] * qpos[6] + qpos[4] * qpos[5]),
|
| 647 |
+
1 - 2 * (qpos[5] * qpos[5] + qpos[6] * qpos[6]),
|
| 648 |
+
);
|
| 649 |
+
chaseHeldYaw = yaw;
|
| 650 |
+
}
|
| 651 |
const dist = camera.position.distanceTo(controls.target);
|
| 652 |
const horiz = dist * Math.cos(CHASE_PITCH);
|
| 653 |
const vert = dist * Math.sin(CHASE_PITCH);
|