fix(physics): keep interpolation in sync on teleport for high refresh rates#8915
Conversation
… rates A dynamic body's teleport (syncEntityToBody) only set its world transform. On frames that run zero fixed sub-steps (dt < fixedTimeStep, e.g. high refresh-rate displays), Bullet fills the motion state by extrapolating from the stale interpolation transform, so the entity read back its pre-teleport pose (#4277, #7822). Reset the interpolation transform and linear/angular velocity for dynamic bodies, feature-detected so older ammo builds (lacking the bindings) no-op. Bundled ammo updated to the build exposing the btCollisionObject interpolation accessors (kripken/ammo.js#446). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Hmm, this doesn't seem correct. I mean, we are changing the body transforms and linear angular velocities to correspond to some fraction of a fixed timestep. I don't see that fraction being used anywhere. On the other hand, in most cases that will be a very small delta, so I guess it is ok? I think Bullet is using something like this utility to get the correct transform and velocities for a given timestep: |
…exact Per review: copying the body's velocity into the interpolation state left the read-back within velocity*fixedTimeStep of the target (Bullet extrapolates from the interpolation transform by the leftover sub-step fraction). Zeroing the interpolation velocities makes getPosition() return exactly the teleport target on every frame, including for a moving body. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks — you're right to push on this. To the "where's the fraction used" point: it's applied by Bullet, not us. But that's exactly why the velocity matters, and you're right about it. Measured on a body teleported to x=10 carrying vx=6, stepping zero-substep (dt=1/240) frames:
(this build has So copying the body's velocity left the read-back within ~v/60 of the target but not exactly it; zeroing the interpolation velocity makes |
Description
Fixes the unreliable
teleport()behaviour on high refresh-rate displays.On a dynamic rigid body,
teleport()(viasyncEntityToBody) only set the body's world transform. On frames that run zero fixed sub-steps —dt < fixedTimeStep, i.e. high refresh-rate displays — Bullet fills the motion state by extrapolating from the body's interpolation world transform, whichsetWorldTransformdoesn't touch._updateDynamicthen reads that stale transform back into the entity, so the teleport appears not to take andentity.getPosition()returns the pre-teleport pose. Being frame-rate dependent is why it surfaced as "breaks at 120 Hz".For dynamic bodies, this also resets the interpolation world transform and interpolation linear/angular velocities, guarded by
body.setInterpolationWorldTransformfeature-detection so projects on an older ammo build are unaffected (teleport behaves exactly as before).This needs the
btCollisionObjectinterpolation accessors added upstream in kripken/ammo.js#446, so the bundled ammo build underexamples/assets/wasm/ammo/is updated to that build. The bulk of the diff is the regenerated (minified) ammo glue +.wasm— the actual engine change is the 7-line branch incomponent.js.Verification — headless: teleport a dynamic body, then step one
dt = 1/240frame (zero sub-steps). Before: motion-state read-back is stale (x ≈ 0.4, the pre-teleport pose). After: read-back is the teleport target (x = 10.0).Fixes #4277
Fixes #7822
Checklist