Aim Stage Variants

Aim stage catalog — DefaultLookAtAim, ClampedLookAim. Aim-origin convention, decoupled ideal rotation, extension points.

The Aim stage owns state.rotation for each tick. Two variants ship with GS_PhantomCam; other gems (notably gs_performer) register additional variants through the same IAimStage interface. All variants share a target-routing front-end via the CamTargetMode enum documented in Stage Pipeline.

Aim does not implement state inheritance by design — inheritance is body-only. The destination cam’s aim runs fresh on the inherited body pose.

Aim stage slot on the Phantom Camera component

 

Contents


Aim-Origin Convention

A subtle but important rule: every Aim variant uses ctx.camInitialTM.GetTranslation() as the aim origin — the cam’s current world position at the start of the tick — not state.position (which the Body just wrote this tick).

Aiming from a position the cam hasn’t moved to yet would produce a frame-delayed look-at vector. Using camInitialTM keeps the aim ray geometry consistent with where the cam is actually rendered from.

Author your own aim variants the same way.


Decoupled Ideal Rotation

Same convention as the Body stage. Aim variants hold an internal m_idealRotation slerp working value, separate from state.rotation. Downstream rotation-modifying additives (Noise, Impulse, Tug aim listener) can perturb state.rotation without poisoning next tick’s slerp trajectory.

A m_lastValidRotation field preserves the last non-degenerate rotation for zero-forward recovery (the case where the cam ends up coincident with its look-at target and the forward vector collapses).


DefaultLookAtAim

Straightforward look-at. Aims the camera at a target (with optional offset), slerp-damped.

Kinematic model

lookAtPoint = targetTM.translation + ApplyOffset(offset, targetTM, isRelative)
state.lookAtPoint = lookAtPoint        // sidecar — for downstream Reposition consumers

aimOrigin = ctx.camInitialTM.translation     // NOT state.position
forward   = (lookAtPoint - aimOrigin).GetNormalizedSafe()

if (forward.GetLengthSq() < epsilon):
    targetRot = m_lastValidRotation          // zero-forward recovery
else:
    targetRot = Quaternion::CreateLookAt(forward, +Z)
    m_lastValidRotation = targetRot

if (ctx.snapThisFrame || !m_hasIdeal):
    m_idealRotation = targetRot
    m_hasIdeal      = true
else:
    alpha = HalflifeAlpha(m_halflife, dt)
    m_idealRotation = m_idealRotation.Slerp(targetRot, alpha)

state.rotation = m_idealRotation

Authored fields

FieldDefaultPurpose
m_targetModeTransformLook-at target routing.
m_overrideEntityWhen m_targetMode == Override.
m_groupTargetName""When m_targetMode == GroupTarget.
m_offset(0, 0, 0)Offset applied to look-at point.
m_offsetIsRelativefalseWorld axes (false) or target-relative basis (true).
m_halflife0.1Slerp halflife.

ClampedLookAim

Aims at a target clamped within a pitch / yaw envelope relative to a starting forward captured on first evaluation. Replacement for the retired ClampedLook_PhantomCamComponent.

Used for first-person and constrained-look scenarios — e.g. the cam can rotate ±45° pitch and ±90° yaw from its initial facing, but not beyond.

Two clamp modes

bool m_localSpace;
ModeBehavior
false (world-space, default)Yaw clamps around world +Z, pitch clamps in the world-vertical plane. Yaw / pitch reconstruction from quaternion.
true (local-space pivot)Pivot relative to m_startingForward. Useful when the cam should rotate within a local envelope independent of world axes.

Kinematic model (world-space mode)

On first Evaluate or ctx.snapThisFrame:
    m_startingForward = ctx.camInitialTM.GetRotation()   // capture once

lookAtPoint = targetTM.translation + offset
forward     = (lookAtPoint - ctx.camInitialTM.translation).GetNormalizedSafe()

// Convert forward to yaw / pitch.
yaw   = atan2(forward.y, forward.x)
pitch = asin(forward.z)

// Clamp to envelope (degrees  radians for comparison).
yaw   = clamp(yaw,   degToRad(m_minRelClamp.z), degToRad(m_maxRelClamp.z))
pitch = clamp(pitch, degToRad(m_minRelClamp.x), degToRad(m_maxRelClamp.x))

// Reconstruct quaternion from clamped angles.
clampedForward = (cos(pitch)*cos(yaw), cos(pitch)*sin(yaw), sin(pitch))
targetRot      = LookAtQuaternion(clampedForward, +Z)

// Slerp damping with decoupled m_idealRotation.
state.rotation = Slerp(m_idealRotation, targetRot, HalflifeAlpha(m_halflife, dt))

Authored fields

FieldDefaultPurpose
m_targetModeTransformLook-at target routing.
m_overrideEntity / m_groupTargetNameMode-dependent.
m_offset(0, 0, 0)Look-at offset.
m_offsetIsRelativefalseBasis.
m_minRelClamp(-45, 0, -90)Lower bound — x = pitch min, z = yaw min. (Layout mirrors the legacy component for scene-data migration.)
m_maxRelClamp(45, 0, 90)Upper bound — x = pitch max, z = yaw max.
m_localSpacefalseClamp mode (world-space or local-space pivot).
m_halflife0.1Slerp halflife.

Runtime state

FieldPurpose
m_startingForwardCaptured on first Evaluate. The “origin” relative to which the clamp envelope is anchored.
m_hasStartingForwardTrue after first capture.
m_idealRotationDecoupled slerp working value.
m_hasIdealTrue after first ideal write.
m_lastValidRotationLast non-degenerate rotation, used for zero-forward recovery.

Extension Surface

Other gems can ship additional aim stage variants. The editor’s type-picker on the cam’s m_aimSlot enumerates all derived IAimStage classes registered through SerializeContext. gs_performer, for example, registers aim stages that read performer head / eye tracking state.

Convention for authoring an aim variant in another gem:

  • Header in Code/Include/<GemName>/Stages/Variants/<MyAim>.h.
  • Source in Code/Source/Stages/Variants/<MyAim>.cpp.
  • Use AZ_RTTI(<MyAim>, "{UUID}", IAimStage) so the picker finds it.
  • Reflect with the FindClassData guard.
  • Honor the aim-origin convention — use ctx.camInitialTM.translation, not state.position.
  • Use the decoupled ideal rotation pattern if you want downstream Noise / Impulse / Tug aim listener to behave well.

See Also


Get GS_PhantomCam

GS_PhantomCam — Explore this gem on the product page and add it to your project.