Exact camera-rotation replay: capture forward vector instead of Euler
- Dominant language
- C++
- Stars
- 10
- Forks
- 4
- Avg merge
- 1d 10h
- Merged PRs (30d)
- 17
Description
Follow-up to the camera free-cam replay work (commits bee4716, 89b0800). **Position** driving is exact; **rotation** capture via Euler is not reliable.
## What works now
- `camera freecam {on}` + `camera drive {x,y,z,pitch,yaw}` set the FreeCameraState transform.
- `camera get` reports `camX/Y/Z`, `camPitch/camYaw`, `freeCam`.
- **Position drive is exact at any distance** (verified: drove `(28022,-585,-932)` → read back delta `(0,0,0)`).
- For **A/B benchmarking** the drive is already useful: it's deterministic, so both A and B runs render the identical viewpoint even before absolute rotation is perfect.
## The rotation problem
Driving a pitch/yaw and reading back the camera's world Euler (via `NiMatrix3::ToEulerAnglesXYZ`):
| drove pitch | read | drove yaw | read |
|---|---|---|---|
| -0.4 | +0.400 | -1.0 | -1.000 |
| -0.2 | +0.200 | -0.5 | **+0.500** |
| 0.0 | -0.000 | 0.0 | -0.000 |
| +0.2 | -0.200 | +0.5 | **-0.500** |
| +0.4 | -0.400 | +1.0 | +1.000 |
| +0.6 | -0.600 | +2.0 | +2.000 |
- **Pitch**: exact magnitude, **sign-flipped** — `freecam pitch = -worldEuler.x`. Easy.
- **Yaw**: flips sign at ±0.5 but not ±1/±2 — `ToEulerAnglesXYZ` is **multivalued** (a rotation matrix has several XYZ decompositions; the extractor jumps branches). Capturing yaw this way is unstable and would make the replayed camera **jitter** between samples.
## Plan
1. **Capture the camera forward vector** (unambiguous) from `cameraRoot->world.rotate` instead of Euler angles, per sample.
2. At replay, convert forward → free-cam pitch/yaw: `yaw = atan2(fwd.x, fwd.y)` (confirm Skyrim convention), `pitch = -asin(fwd.z)` (sign per the table above). Nail the convention with a drive→read sweep.
3. Then wire per-sample `camera drive` into BuildScenario/BuildReplaySteps under a `driveCamera` replay flag (the `drive` tool already no-ops when not in free cam, so steps are inert on a normal replay; `driveCamera` prepends `freecam on`, skips `setPov`, appends `freecam off`).
4. Optional: RE `FreeCameraState::GetRotation` (Ghidra) to confirm the exact rotation/`zUpDown` convention rather than fitting empirically.
Until then: position-exact drive is landed; replay does not auto-drive the camera (POV mode + body pose remain the replay path; first-person is exact).
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.