isle-portable/extensions/docs/thirdpersoncamera/roi-direction-conventions.md
2026-04-03 15:41:57 -07:00

92 lines
3.9 KiB
Markdown

# ROI Direction Conventions & Third-Person Camera
## Background: The Two Z-Axis Conventions
The game engine represents an actor's facing direction via the z-axis of its ROI
(Real-time Object Instance) local-to-world transform. Two opposite conventions
exist throughout the codebase:
| Convention | ROI z-axis points... | Used by |
|----------------|--------------------------|--------------------------------------------------------|
| **forward-z** | Toward visual forward | `PlaceActor` (with `m_cameraFlag=TRUE`), cam anim end |
| **backward-z** | Away from visual forward | After `Enter()`'s `TurnAround()`, vehicle ROIs |
Toggling between conventions is done by `IslePathActor::TurnAround` (or the
local `FlipMatrixDirection` helper): negate the z-axis and recompute the right
vector.
## Design Choice: Forward-Z
The third-person orbit camera uses **forward-z**, matching the convention that
`PlaceActor` naturally produces. This eliminates the need to flip the ROI
direction after every `PlaceActor` call.
`ComputeOrbitVectors` treats local Z+ as the character's visual forward and
places the camera at local -Z (behind the character), looking toward +Z.
## Engine Behavior
The engine's actor lifecycle:
```
Enter()
-> ResetWorldTransform(TRUE) sets m_cameraFlag = TRUE
-> TurnAround() flips to backward-z
-> TransformPointOfView() sets 1st-person camera
PlaceActor() resets to forward-z <- what we use
```
`PlaceActor` always produces forward-z (when `m_cameraFlag=TRUE`), which is
exactly what the orbit camera expects. No direction correction is needed after
`PlaceActor` runs.
## World Transition Timing
The one remaining complexity is timing during world transitions. The event order
is:
1. `OnWorldEnabled` fires (from `LegoWorld::Enable`, BEFORE `SpawnPlayer`)
2. `ReinitForCharacter` sets up the display ROI and marks `m_active = true`
3. `Enter()` fires `OnActorEnter` -- ROI is at **stale position** from previous session
4. `PlaceActor` sets ROI to correct spawn position
5. First `Tick` -- `ApplyOrbitCamera` sets the camera at the correct position
Between steps 3 and 5, the ROI position is stale. If we set up the orbit camera
in step 3, the stale view would freeze on screen during the ~500ms world load.
The `m_pendingWorldTransition` flag handles this: set in `OnWorldEnabled`,
it causes `OnActorEnter` and `ReinitForCharacter` to skip camera setup.
Cleared in the first `Tick` after `PlaceActor`, where `ApplyOrbitCamera`
naturally handles the camera. The orbit state (yaw, pitch, distance) is also
reset to defaults in `OnWorldEnabled`.
## Display Clone Direction
The native actor ROI is invisible in 3rd-person mode. A display clone renders
the character model instead. Character meshes face -z, so the clone needs
backward-z to look correct. When syncing the clone's transform from the native
ROI (which is in forward-z), `Tick` negates the z-axis and recomputes the right
vector -- the same operation as `TurnAround`.
This also affects the right vector (X-axis): forward-z and backward-z produce
opposite right vectors. The orbit yaw input is negated to compensate, keeping
drag-right = camera-moves-right.
## Cam Anim Interaction
While a cam anim locks the player (`GetActorState() == c_disabled`), two
things protect the orbit camera:
1. **Tick guard**: `ApplyOrbitCamera` is skipped so it doesn't fight the cam
anim's `TransformPointOfView`. Without this, the cam anim end handler would
read our elevated orbit camera position and place the actor in the air.
2. **`OnCamAnimEnd`**: When the cam anim releases the player (first space bar
interruption or natural end), this callback calls `SetupCamera` to restore
the orbit camera.
After the first interruption, the actor state resets to `c_initial` and the
orbit camera resumes immediately -- even if `m_animRunning` is still true for
background animations playing in the world.