fix(camera): retail-faithful jump-tracking via contact-plane projection
Original symptom: jumping made the camera swing around the player
vertically — the basis tilted up/down with the player's Z velocity.
Root cause: ComputeHeading used the raw 3D velocity vector as the
heading direction. During a jump, velocity has a substantial Z
component (vy ≈ jump speed), and `normalize((vx, vy, vz))` produced
a heading pointing up. The basis tilted accordingly and the camera
went under/over the player.
Retail's actual ALIGN_WITH_PLANE algorithm (decomp at
acclient_2013_pseudo_c.txt:95644-95795) is different:
1. Velocity is only used as a gate. If |vx| AND |vy| > epsilon
(player is moving in XY), proceed; otherwise fall back to the
LOOK_IN_DIRECTION path (player's facing direction unchanged).
2. The base heading is `localtoglobalvec(player, (0, 1, 0))` —
the player's local +Y axis in world space, which in our
convention is `(cos yaw, sin yaw, 0)`.
3. Pick a surface normal:
grounded: contact_plane.N
airborne: (0, 0, 1) [world up]
4. Project the base heading onto the plane perpendicular to that
normal: projected = forward - normal * dot(forward, normal).
5. Normalize. Fall back to the base if projection collapses.
Behaviorally:
* Standing jump (vx≈0, vy≈0): gate fails → base heading. Camera
doesn't move with the jump.
* Running jump (vx, vy, vz all nonzero, airborne): projects onto
world up → no-op since base is already horizontal. Camera basis
stays horizontal; player visibly rises in frame.
* Walking uphill (grounded, slope normal tilted): projection
adds a Z component matching the slope angle. Camera basis tilts
with the terrain.
* Walking on flat ground: projection is a no-op. Camera basis
horizontal.
Surface changes:
* RetailChaseCamera.ComputeHeading gains `isOnGround` and
`contactPlaneNormal` parameters.
* RetailChaseCamera.Update gains the same two parameters and
threads them through.
* GameWindow's two Update call sites pass `result.IsOnGround` and
`_playerController.ContactPlane.Normal` (already exposed on
PlayerMovementController — no plumbing change there).
* Tests: 2 existing heading tests reshaped (Moving* and Uphill);
2 new tests added (AirborneJumping straight-up + running-jump);
1 renamed (SlopeAlignDisabled). Net 25 → 27 tests in
RetailChaseCameraTests; full AcDream.App.Tests: 39 → 41.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8f30e13317
commit
b7e954e50b
3 changed files with 188 additions and 34 deletions
|
|
@ -4757,7 +4757,11 @@ public sealed class GameWindow : IDisposable
|
|||
|
||||
// 4. Recenter chase camera on the new position.
|
||||
_chaseCamera?.Update(snappedPos, _playerController.Yaw);
|
||||
_retailChaseCamera?.Update(snappedPos, _playerController.Yaw, System.Numerics.Vector3.Zero, dt: 1f / 60f);
|
||||
_retailChaseCamera?.Update(snappedPos, _playerController.Yaw,
|
||||
playerVelocity: System.Numerics.Vector3.Zero,
|
||||
isOnGround: true,
|
||||
contactPlaneNormal: System.Numerics.Vector3.UnitZ,
|
||||
dt: 1f / 60f);
|
||||
|
||||
// 5. Return to InWorld.
|
||||
_playerController.State = AcDream.App.Input.PlayerState.InWorld;
|
||||
|
|
@ -6429,10 +6433,18 @@ public sealed class GameWindow : IDisposable
|
|||
_chaseCamera.Update(result.RenderPosition, _playerController.Yaw,
|
||||
isOnGround: result.IsOnGround,
|
||||
dt: (float)dt);
|
||||
// RetailChaseCamera: takes world velocity for slope-aligned heading;
|
||||
// jump-feedback falls out of damping naturally, no isOnGround needed.
|
||||
// RetailChaseCamera: heading is the player's facing direction
|
||||
// projected onto the contact plane when grounded, or the
|
||||
// world XY plane when airborne. The contact plane normal
|
||||
// tilts the camera basis with terrain; the airborne
|
||||
// fallback keeps the basis horizontal during jumps so the
|
||||
// player visibly rises in frame without the camera
|
||||
// swinging vertically (was the symptom of using raw
|
||||
// velocity-vector heading).
|
||||
_retailChaseCamera!.Update(result.RenderPosition, _playerController.Yaw,
|
||||
playerVelocity: _playerController.BodyVelocity,
|
||||
playerVelocity: _playerController.BodyVelocity,
|
||||
isOnGround: result.IsOnGround,
|
||||
contactPlaneNormal: _playerController.ContactPlane.Normal,
|
||||
dt: (float)dt);
|
||||
|
||||
// Send outbound movement messages to the live server.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue