#include <ComposableCameraNamespaces.h>

Public Attributes

Return Name Description
UComposableCameraModifierBase * Modifier
UComposableCameraNodeModifierDataAsset * Asset

Modifier

UComposableCameraModifierBase * Modifier

Asset

UComposableCameraNodeModifierDataAsset * Asset

Public Methods

Return Name Description
bool operator== const inline
bool operator!= const inline

operator==

const inline

inline bool operator==(const FModifierEntry & Other) const

operator!=

const inline

inline bool operator!=(const FModifierEntry & Other) const

ShotSolver

Composition Solver for [FComposableCameraShot](FComposableCameraShot.md#fcomposablecamerashot) — the heart of the Shot-Based Keyframing runtime. Three-layer pipeline (Placement → Aim → Lens) plus an independent Focus pass and a final Roll composition.

  1. Placement → camera Position AnchorOrbit — spherical placement around PlacementAnchor, plus lateral shift to realize Placement.ScreenPosition. FixedWorldPosition — camera at an explicit world point.

  2. Aim → camera Rotation LookAtAnchor — closed-form rotation that lands AimAnchor at Aim.ScreenPosition (pre-rotated by -Roll so the constraint holds after the final Roll composition).

  3. Lens → FOV + Aperture Manual — direct passthrough. SolvedFromBoundsFit — Weight-scaled Perceptual Union Box on Targets' bounds (BlackEye-derived; spec §4.5).

  4. Focus → focus distance (independent) Manual / FollowPlacementAnchor / FollowAimAnchor / FollowCustomAnchor.

  5. Roll composed onto output rotation as the final operation.

Pose-time only: consumes target world transforms at the moment of evaluation, no prediction.

All step functions are public so they can be unit-tested independently. The top-level orchestrator is [SolveShot()](#solveshot).

Design notes:

  • Header-inline. Optimizer benefits from seeing through the call boundaries; cold-enough that we don't care about code-size duplication. Same convention as [Math/ComposableCameraMath.h](#composablecameramathh).

  • No Blueprint-callable surface (per spec §1.4 "no runtime BP API for mutating Shot data"). Solver consumers are C++ only — [UComposableCameraCompositionFramingNode](../nodes/UComposableCameraCompositionFramingNode.md#ucomposablecameracompositionframingnode) and the unit tests.

  • Chicken-and-egg with FOV: the rotation-solve projection uses the previous frame's FOV (passed in via [FShotSolveContext](FShotSolveContext.md#fshotsolvecontext)). When FOVMode == Manual, the manual value is used instead. When SolvedFromBoundsFit is active, the solver converges in 1-2 frames after a Shot transition.

  • Coupling between Placement.ScreenPosition (lateral camera shift) and Aim.ScreenPosition (rotation) is intentionally one-way: Placement determines Position with a TENTATIVE look-at-PlacementAnchor rotation; Aim then OVERRIDES rotation. So when AimAnchor != PlacementAnchor, the placement anchor's final projected screen position drifts from Placement.ScreenPosition. Document this and let designers set both equal in the typical AimAnchor==PlacementAnchor case.

Classes

Name Description
FShotPriorPose Optional prior camera pose handed to SolveShot so the zone path has a base to project anchors through. Lightweight (Pos + Rot, FOV reuses Context.PreviousFrameFOV) — the Solver header stays free of [FComposableCameraPose](FComposableCameraPose.md#fcomposablecamerapose) (which lives one module-folder away in [Cameras/ComposableCameraCameraBase.h](#composablecameracamerabaseh)).
FShotSolveContext Per-frame inputs the solver needs from the runtime — viewport state + the previous frame's FOV (used as the projection FOV when the Shot is in SolvedFromBoundsFit mode).
FShotSolveResult Solver output. bValid == false when an essential anchor cannot be resolved (placement / aim) — caller should preserve upstream pose for the frame.

Functions

Return Name Description
FQuat ResolvePlacementBasis inline Resolves the basis quat for the Placement layer's LocalCameraDirection.
FVector SolveAnchorOrbitPosition inline Camera position for AnchorOrbit mode:
bool ClampAuthoredScreenPosition inline Clamp an authored screen position to [-0.49, +0.49]². Returns true iff a clamp actually fired (caller logs a warning so the designer sees that their input was modified).
FVector2D PreRotateScreenForRoll inline Pre-rotates an authored screen position by -Roll so that, after the final pose has Roll composed onto its rotation, the world point still projects to the authored (un-pre-rotated) screen coords.
FVector2D ApplyScreenZones inline Compute the effective screen-space target an anchor should be solved toward this frame, given:
FVector2D ResolveEffectiveScreenTarget inline Resolve the effective screen-space target for a single anchor given a prior camera pose. Convenience wrapper that handles the "anchor unresolvable" failure path (returns the authored screen position so the V1 hard-constraint solver still has something sensible to chew on — caller will likely fail on anchor resolve downstream anyway).
bool SolveAnchorAtScreenPos inline Closed-form Position pass for [EShotPlacementMode::AnchorAtScreen](#ComposableCameraShot_8h_1ad8bb9ef9d1aefe5e2b42410fc9908537a8da460d070406aab8e2caa882f9cbf2c).
bool SolvePlacement inline Computes camera Position based on the Shot's Placement layer. Returns false (CamPos unchanged) when an essential anchor can't resolve — caller handles the no-pose fallback.
FRotator SolveLookAtAnchorRotation inline Solves camera Rotation for LookAtAnchor mode: closed-form rotation that lands AimAnchor at Aim.ScreenPosition. Uses SolveCameraRotationForScreenTarget from [ComposableCameraMath.h](#composablecameramathh). Roll is pre-compensated so the screen constraint holds after the caller composes Roll onto the result.
bool SolveAim inline Computes camera Rotation based on the Shot's Aim layer + Roll. Returns false (OutRot unchanged) when AimAnchor can't resolve.
float DampAngleDeg inline Wrap-aware angle damping (degrees). Same FInterpTo Speed semantics as FMath::FInterpTo but operates on the shortest angular delta — a transition from +175° to -175° (visually +10°) takes the short way, not the long way. Returns the unwrapped degrees value (re-normalized into [-180, 180]) so caching the result keeps the authoring envelope. Speed <= 0 or DeltaTime <= 0 ⇒ return Target (instant snap), matching FInterpTo.
float SolvePerceptualUnionBoxFOV inline Solves FOV from per-target bounds via the Weight-scaled Perceptual Union Box algorithm. See spec §4.5 for the algorithm; ported from BlackEyeCameras' ULookAtComponent::GetTargetGroupViewportBoundingBox.
float SolveLens inline Computes FOV based on the Shot's Lens layer.
float SolveFocus inline Focus distance based on Shot.Focus.Mode:
FShotSolveResult SolveShot inline Runs the full pipeline. bValid == false when Placement or Aim fails to resolve — caller preserves upstream pose for the frame (spec §5.3).

ResolvePlacementBasis

inline

inline FQuat ResolvePlacementBasis(const FComposableCameraShot & Shot)

Resolves the basis quat for the Placement layer's LocalCameraDirection.

  • World basis → FQuat::Identity (always valid).

  • InheritFromActor basis → Targets[BasisActorIndex]'s basis quat, via FComposableCameraTargetInfo::ResolveBasisQuat (mesh-component quat for ACharacter-style targets when the per-target flag is set, actor quat otherwise; PIE-remap aware). Falls back to identity (with warning) when the index is out of range or the actor is unresolvable. Both warning paths dedupe so the log line fires at most once per (Shot pointer, distinct unresolvable Actor soft-path) — designers are told once when their basis assignment isn't taking effect, then the hot path stays quiet.


SolveAnchorOrbitPosition

inline

inline FVector SolveAnchorOrbitPosition(const FVector & AnchorPos, const FQuat & BasisQuat, float Distance, const FVector2D & LocalCameraDirection, const FVector2D & ScreenPosition, float TanHalfHOR, float AspectRatio)

Camera position for AnchorOrbit mode:

CamPos = AnchorPos + Distance · BasisQuat · UnitDir(Yaw, Pitch)

  • lateral shift to realize ScreenPosition

The lateral shift is along the camera's right / up axes, computed from the tentative look-at-anchor forward (which equals -BasisQuat · UnitDir(Yaw, Pitch)). This is independent of the Aim layer's eventual rotation: Position is fixed by Placement alone.

Distance is Euclidean — measured along the unit direction vector. Equals camera-frame depth in the typical case (AimAnchor == PlacementAnchor → camera looks at PlacementAnchor → depth = Euclidean distance).

Parameters

  • TanHalfHOR tan(FOV_h / 2) for the lateral shift's screen-to-world conversion. Pass the projection FOV (manual or previous-frame).

  • AspectRatio viewport aspect (width / height).


ClampAuthoredScreenPosition

inline

inline bool ClampAuthoredScreenPosition(FVector2D & InOutScreenPos)

Clamp an authored screen position to [-0.49, +0.49]². Returns true iff a clamp actually fired (caller logs a warning so the designer sees that their input was modified).


PreRotateScreenForRoll

inline

inline FVector2D PreRotateScreenForRoll(const FVector2D & Authored, float CosRoll, float SinRoll, float AspectRatio)

Pre-rotates an authored screen position by -Roll so that, after the final pose has Roll composed onto its rotation, the world point still projects to the authored (un-pre-rotated) screen coords.

Math (derivation in spec §4.8): under camera Roll R about forward, a fixed world point's projected coords transform anisotropically as Sx_R = Sx_0 · cosR - (Sy_0 / AR) · sinR Sy_R = AR · Sx_0 · sinR + Sy_0 · cosR To preserve post-Roll proj == authored ScreenPos, we solve the inverse: Sx_0 = Sx · cosR + (Sy / AR) · sinR Sy_0 = -AR · Sx · sinR + Sy · cosR Defined here (above the first caller, SolveLookAtAnchorRotation) so the order of inline definitions in this header matches the order of use — C++ requires inline functions to be defined before use within the same translation unit.


ApplyScreenZones

inline

inline FVector2D ApplyScreenZones(const FVector2D & CurrentScreen, const FVector2D & AuthoredScreenPos, const FShotScreenZones & Zones, float DeltaTime)

Compute the effective screen-space target an anchor should be solved toward this frame, given:

  • CurrentScreen — the anchor's current projected screen coordinate (read off the prior pose).

  • AuthoredScreenPos — the designer's authored target screen position (zone-rect center).

  • Zones — the dead/soft padding + damping config.

  • DeltaTime — frame delta seconds (for FInterpTo).

Algorithm (Cinemachine-style, asymmetric per-side):

err = CurrentScreen - AuthoredScreenPos

// Per-axis dead-zone subtraction. The dead zone is the rect // [-DeadLeft, +DeadRight] × [-DeadBottom, +DeadTop] around SP. // err > +Right → eff = err - Right (anchor right of dead, pull left) // err < -Left → eff = err + Left (anchor left of dead, pull right) // else → eff = 0 (anchor inside dead, hold) eff_after = FInterpTo(eff, 0, dt, Speed) // damping step = eff - eff_after new_err = err - step

// Soft-zone hard limit: clamp into the soft padding rect, no // damping on the clamp (Cinemachine's "HardLimits" semantics). new_err.x in [-SoftLeft, +SoftRight] new_err.y in [-SoftBottom, +SoftTop]

target = AuthoredScreenPos + new_err

Anchor inside the dead rect → eff = 0 → step = 0 → new_err = err → target = CurrentScreen → solver reproduces the prior pose. Damping Speed = 0 collapses eff_after to 0, i.e. step = eff, so the anchor snaps to the nearest dead-zone edge in one frame.

Soft padding is defensively >= dead padding per side; the drag handler enforces this on author, but the solver also clamps so an inverted authoring (Soft < Dead on a side) still yields sensible output rather than a degenerate clamp band.


ResolveEffectiveScreenTarget

inline

inline FVector2D ResolveEffectiveScreenTarget(const FComposableCameraAnchorSpec & Anchor, TConstArrayView< FComposableCameraShotTarget > Targets, const FVector2D & AuthoredScreenPos, const FShotScreenZones & Zones, const FShotPriorPose & PriorPose, float TanHalfHOR, float AspectRatio, float DeltaTime)

Resolve the effective screen-space target for a single anchor given a prior camera pose. Convenience wrapper that handles the "anchor unresolvable" failure path (returns the authored screen position so the V1 hard-constraint solver still has something sensible to chew on — caller will likely fail on anchor resolve downstream anyway).

Returns the authored ScreenPosition unchanged when:

  • Zones are disabled, OR

  • The anchor cannot resolve to a world point (zone math needs the projected CurrentScreen, which needs a world point), OR

  • The prior pose's rotation is degenerate (Forward dot AnchorDir near zero in ProjectWorldPointToScreen).

The projection uses the same TanHalfHOR / AspectRatio the rest of the SolveShot pipeline uses for the current frame — so the effective screen target is consistent with the projection the V1 solver will subsequently invert.


SolveAnchorAtScreenPos

inline

inline bool SolveAnchorAtScreenPos(const FVector & PlacementAnchorPos, FVector2D ScreenPos, float Distance, float TanHalfHOR, float AspectRatio, const FRotator & AssumedRot, FVector & OutCamPos)

Closed-form Position pass for [EShotPlacementMode::AnchorAtScreen](#ComposableCameraShot_8h_1ad8bb9ef9d1aefe5e2b42410fc9908537a8da460d070406aab8e2caa882f9cbf2c).

Computes a camera position that places PlacementAnchor at depth Distance and screen ScreenPos IN THE CAM FRAME OF AssumedRot. Algebraic, no iteration: cam_anchor = (D, sx · 2·TanH · D, sy · 2·TanV · D) // cam frame CamPos = PlacementAnchor - AssumedRot · cam_anchor AssumedRot is sourced from:

  • Aim NoOp: identity + Shot.Roll (Roll-only).

  • Aim LookAtAnchor + prior: prior frame’s camera rotation. Decoupling-drift is then per-frame O(rotation delta).

  • Aim LookAtAnchor + first-frame seed: rotation built from the (PlacementAnchor → AimAnchor) world direction + Shot.Roll. Matches the Aim pass’s eventual look-at-AimAnchor rotation closely enough that one frame of decoupling drift washes out inside the IIR damping window.

ScreenPos is consumed as-is (no -Roll pre-rotation): when AssumedRot already includes Shot.Roll, the cam-frame right/up axes are themselves rolled, so (sx · 2·TanH · D, sy · 2·TanV · D) lands the anchor at the authored ScreenPosition under the rolled view. Contrast with SolveLookAtAnchorRotation which DOES pre-rotate — it solves for the rotation, so it cannot pre-roll it.

Returns false (OutCamPos unchanged) iff Distance < 1cm. Authored ScreenPos outside [-0.49, +0.49]² is silently clamped to keep the cam-frame target inside the frustum-safe envelope.


SolvePlacement

inline

inline bool SolvePlacement(const FComposableCameraShot & Shot, float EffectiveDistance, float TanHalfHOR, float AspectRatio, FVector & OutCamPos)

Computes camera Position based on the Shot's Placement layer. Returns false (CamPos unchanged) when an essential anchor can't resolve — caller handles the no-pose fallback.

EffectiveDistance overrides Shot.Placement.Distance for the AnchorOrbit mode — SolveShot injects a damped distance here (V2.2 IIR via Shot.Placement.DistanceSpeed); decoupled callers pass Shot.Placement.Distance directly to keep V1 hard behavior. FixedWorldPosition ignores it; the solver expects callers to still pass a sensible value for symmetry.


SolveLookAtAnchorRotation

inline

inline FRotator SolveLookAtAnchorRotation(const FVector & CamPos, const FVector & AimAnchorPos, const FVector2D & AimScreenPosition, float RollRad, float TanHalfHOR, float AspectRatio)

Solves camera Rotation for LookAtAnchor mode: closed-form rotation that lands AimAnchor at Aim.ScreenPosition. Uses SolveCameraRotationForScreenTarget from [ComposableCameraMath.h](#composablecameramathh). Roll is pre-compensated so the screen constraint holds after the caller composes Roll onto the result.

Returns FRotator with Pitch / Yaw set, Roll left at zero — caller sets Roll = Shot.Roll afterwards.


SolveAim

inline

inline bool SolveAim(const FComposableCameraShot & Shot, const FVector & CamPos, const FVector2D & EffectiveAimScreenPos, float EffectiveRollDeg, float TanHalfHOR, float AspectRatio, FRotator & OutRot)

Computes camera Rotation based on the Shot's Aim layer + Roll. Returns false (OutRot unchanged) when AimAnchor can't resolve.

EffectiveAimScreenPos overrides Shot.Aim.ScreenPosition for LookAtAnchor mode — this is how SolveShot injects a zone-derived effective screen target (Cinemachine-style damped framing). Pass Shot.Aim.ScreenPosition directly to keep V1 hard-constraint behavior. NoOp ignores this argument (it has no screen constraint).

EffectiveRollDeg is the V2.2 damped Roll (computed in SolveShot from Shot.Roll, PriorPose->LastRoll, and Shot.RollSpeed). Pass Shot.Roll directly to keep V1 hard-constraint behavior.


DampAngleDeg

inline

inline float DampAngleDeg(float LastDeg, float TargetDeg, float DeltaTime, float Speed)

Wrap-aware angle damping (degrees). Same FInterpTo Speed semantics as FMath::FInterpTo but operates on the shortest angular delta — a transition from +175° to -175° (visually +10°) takes the short way, not the long way. Returns the unwrapped degrees value (re-normalized into [-180, 180]) so caching the result keeps the authoring envelope. Speed <= 0 or DeltaTime <= 0 ⇒ return Target (instant snap), matching FInterpTo.


SolvePerceptualUnionBoxFOV

inline

inline float SolvePerceptualUnionBoxFOV(const FVector & CameraPos, const FRotator & CameraRot, TConstArrayView< FComposableCameraShotTarget > Targets, float DesiredViewportFillRatio, float CurrentFOVDeg, float AspectRatio, const FFloatInterval & FOVClamp)

Solves FOV from per-target bounds via the Weight-scaled Perceptual Union Box algorithm. See spec §4.5 for the algorithm; ported from BlackEyeCameras' ULookAtComponent::GetTargetGroupViewportBoundingBox.

Edge cases:

  • No contributing bounds → keep CurrentFOV.

  • Any vertex of a target's BB behind camera → skip that target.

Parameters

  • CurrentFOV Used as projection FOV (consistent per-frame projection) AND as the basis for the closed-form atan inversion.

SolveLens

inline

inline float SolveLens(const FComposableCameraShot & Shot, const FVector & CamPos, const FRotator & CamRot, const FShotSolveContext & Context)

Computes FOV based on the Shot's Lens layer.


SolveFocus

inline

inline float SolveFocus(const FComposableCameraShot & Shot, const FVector & CamPos, const FRotator & CamRot)

Focus distance based on Shot.Focus.Mode:

  • Manual → Focus.ManualDistance.

  • FollowPlacementAnchor → camera-to-PlacementAnchor depth.

  • FollowAimAnchor → camera-to-AimAnchor depth.

  • FollowCustomAnchor → camera-to-FocusAnchor depth.

Depth is on-axis ((WorldPoint - CameraPos) · CameraForward), not Euclidean — same convention as FocusPullNode and what ApplyPhysicalCameraSettings consumes downstream. Falls back to Manual.ManualDistance when an anchor mode can't resolve its world point.


SolveShot

inline

inline FShotSolveResult SolveShot(const FComposableCameraShot & Shot, const FShotSolveContext & Context, const FShotPriorPose * PriorPose, float DeltaTime)

Runs the full pipeline. bValid == false when Placement or Aim fails to resolve — caller preserves upstream pose for the frame (spec §5.3).

PriorPose + DeltaTime enable Cinemachine-style screen-space framing zones. When non-null AND Aim.AimZones.bEnabled / Placement.PlacementZones.bEnabled is set, the solver projects the corresponding anchor through *PriorPose and substitutes a zone-derived effective screen target for the V1 hard ScreenPosition read. When null OR zones disabled, V1 hard-constraint behavior is preserved exactly — every existing call site continues to work with default arguments.

PlacementZones only meaningfully fires in AnchorAtScreen placement (the only mode that authors a Placement.ScreenPosition); AnchorOrbit and FixedWorldPosition ignore the placement zone configuration regardless of bEnabled.

Variables

Return Name Description
constexpr float ShotSolverScreenClampLimit Soft-clamp limit for authored screen positions in the joint-solve paths. The screen-coord convention is [-0.5, +0.5]² (= edge of frustum at the projection FOV). Authoring values at the edge make the iterative solver's SolveCameraRotationForScreenTarget saturate its \|T\| ≤ 1 clamp, which is correct math but loses convergence margin and tends to produce contorted poses. We clamp authored inputs to a slightly inset envelope (~ 98% of frustum width / height) before they enter the iteration so the solver always has headroom.

ShotSolverScreenClampLimit

constexpr float ShotSolverScreenClampLimit = 0.49f

Soft-clamp limit for authored screen positions in the joint-solve paths. The screen-coord convention is [-0.5, +0.5]² (= edge of frustum at the projection FOV). Authoring values at the edge make the iterative solver's SolveCameraRotationForScreenTarget saturate its |T| ≤ 1 clamp, which is correct math but loses convergence margin and tends to produce contorted poses. We clamp authored inputs to a slightly inset envelope (~ 98% of frustum width / height) before they enter the iteration so the solver always has headroom.

Designers authoring values outside this envelope see a one-shot warning per solve; the clamp is silent at the iteration's interior (pre-rotation by -Roll can push pre-rotated values outside the envelope, which is fine — that's an internal value, not user input).