#include <ComposableCameraCompositionFramingNode.h>

Inherits: UComposableCameraCameraNodeBase

Camera node that wraps the Composition Solver — produces a complete camera pose (Position + Rotation + FOV + Focus + Aperture) from an authored [FComposableCameraShot](../structs/FComposableCameraShot.md#fcomposablecamerashot) each tick.

This is the consumer that connects the Phase B Shot data model to the camera evaluation pipeline. See Docs/ShotBasedKeyframing.md §3-4 for the data model + solver pipeline; TechDoc §3.25 for the solver internals.

──** Authoring model (V1)**

The Shot is authored fully in this node's Details panel inside the camera type asset. The struct contains a TArray<FShotTarget> which violates the pin data block's POD constraint (TechDoc §3.2), so it is NOT pin-exposable. Phase E's LS Shot Section integration will push Shot data via a separate runtime API (not pin wiring). GetPinDeclarations accordingly returns no pins — the node has no inputs to wire and no outputs to pipe; it OWNS the camera pose.

──** Behavior**

Pose-overwriting node. Position + Rotation + FieldOfView + Aperture + FocusDistance are unconditionally written from the solver result when it succeeds. If you want upstream nodes to contribute, place them DOWNSTREAM (after the Composition node), not upstream.

PhysicalCameraBlendWeight is forced to 1.0 so the Aperture + FocusDistance written by the solver are actually consumed by the renderer's DoF system. If you don't want physical-camera DoF, follow this node with a downstream node (e.g. a custom one) that sets PhysicalCameraBlendWeight back to 0.

FocalLength is set to -1 to put the pose in "FOV is authoritative" mode (matches LensNode::bOverrideFieldOfViewFromFocalLength = true semantics). Downstream LensNode may override.

When Shot.Placement.PlacementAnchor or Shot.Aim.AimAnchor is unresolvable (spec §5.3 — invalid index, all weights zero, etc.), the node passes the upstream pose through unmodified and the solver logs a warning. Camera doesn't snap; previous frame's pose effectively persists.

──** Patch compatibility**

Incompatible. The node's whole purpose is to overwrite the pose; layering a Patch on top of it has no defined semantics. Same classification as RelativeFixedPoseNode / MixingCameraNode.

──** Bounds-cache lifecycle**

OnInitialize refreshes the AutoFromComponentBounds cache for every target with that BoundsShape — covers StaticSnapshot policy entirely and seeds Periodic / Live so their first tick uses fresh data.

OnTickNode then refreshes per-target according to policy:

  • Live: refresh every frame.

  • Periodic: refresh when LocalFrameCounter % Interval == 0.

  • StaticSnapshot: never refresh after OnInitialize.

Public Attributes

Return Name Description
FComposableCameraShot Shot Authored Shot — drives the Composition Solver each tick. Edited in the node's Details panel. NOT pin-exposable: [FComposableCameraShot](../structs/FComposableCameraShot.md#fcomposablecamerashot) contains a TArray<FShotTarget> which violates the pin data block's POD constraint (TechDoc §3.2). Runtime pushes (e.g. from LS Shot Sections in Phase E) mutate this struct via the SetActiveShotsFromSequencer API.

Shot

FComposableCameraShot Shot

Authored Shot — drives the Composition Solver each tick. Edited in the node's Details panel. NOT pin-exposable: [FComposableCameraShot](../structs/FComposableCameraShot.md#fcomposablecamerashot) contains a TArray<FShotTarget> which violates the pin data block's POD constraint (TechDoc §3.2). Runtime pushes (e.g. from LS Shot Sections in Phase E) mutate this struct via the SetActiveShotsFromSequencer API.

BlueprintReadOnly per spec §1.4 — Shot data is designer-authored content, not gameplay-controlled state.

In Phase F's two-Shot blend the field is treated as the primary (outgoing / lower-row) Shot. The secondary Shot is held in SecondaryShot and active iff bHasSecondaryShot is true.

Public Methods

Return Name Description
UComposableCameraCompositionFramingNode inline
void OnInitialize_Implementation virtual
void OnTickNode_Implementation virtual
void GetPinDeclarations_Implementation virtual const
EComposableCameraNodePatchCompatibility GetPatchCompatibility_Implementation virtual const
void DrawNodeDebug virtual const Called each frame when the CCS.Debug.Viewport CVar is enabled, for every node on the currently running camera. Override to draw world-space debug gizmos via DrawDebugHelpers (DrawDebugSphere, DrawDebugLine, etc.) that visualise this node's runtime state — e.g. a pivot sphere for PivotOffsetNode, a look-at line for LookAtNode, the collision trace for CollisionPushNode, a sampled spline path for SplineNode.
void BeginDestroy virtual
void SetActiveShotsFromSequencer Phase F: push the active Shot pair from the LSComponent each frame an override is applied.
void SetExternalAspectRatioOverride inline Push the effective render aspect ratio for solver use. The node by default queries GetEffectiveViewportAspectRatio from OwningPlayerCameraManager, which is null in the LS Component path → falls back to either GameViewport (PIE) or editor active viewport. That works for unconstrained CineCams, but with bConstrainAspectRatio == true the renderer letterboxes to the filmback-derived aspect regardless of viewport, and the solver needs to match. The LS Component computes the effective aspect via GetEffectiveAspectRatioForCineCamera(OutputCineCameraComponent) and pushes it here; OnTickNode prefers the override when > 0.

UComposableCameraCompositionFramingNode

inline

inline UComposableCameraCompositionFramingNode()

OnInitialize_Implementation

virtual

virtual void OnInitialize_Implementation()

OnTickNode_Implementation

virtual

virtual void OnTickNode_Implementation(float DeltaTime, const FComposableCameraPose & CurrentCameraPose, FComposableCameraPose & OutCameraPose)

GetPinDeclarations_Implementation

virtual const

virtual void GetPinDeclarations_Implementation(TArray< FComposableCameraNodePinDeclaration > & OutPins) const

GetPatchCompatibility_Implementation

virtual const

virtual EComposableCameraNodePatchCompatibility GetPatchCompatibility_Implementation() const

DrawNodeDebug

virtual const

virtual void DrawNodeDebug(UWorld * World, bool bViewerIsOutsideCamera) const

Called each frame when the CCS.Debug.Viewport CVar is enabled, for every node on the currently running camera. Override to draw world-space debug gizmos via DrawDebugHelpers (DrawDebugSphere, DrawDebugLine, etc.) that visualise this node's runtime state — e.g. a pivot sphere for PivotOffsetNode, a look-at line for LookAtNode, the collision trace for CollisionPushNode, a sampled spline path for SplineNode.

Access the owning camera via OwningCamera and current-frame pin values via the usual GetInputPinValue<T>() / member-read path — this hook fires AFTER TickNode, so pin-backed UPROPERTYs still hold the resolved values from the most recent evaluation.

bViewerIsOutsideCamera mirrors the ticker's frustum-draw flag: true when the viewer is observing the camera from outside (F8 eject, SIE, or CCS.Debug.Viewport.AlwaysShow), false when the player is looking through the camera. Most gizmos (pivot spheres at distant characters, lines to look-at targets, spline polylines far in the world) can ignore this and draw unconditionally. Gizmos that sit AT the camera's own position (e.g. CollisionPushNode's self-collision sphere) should gate on this bool so they don't hermetically seal the player inside the wireframe during live gameplay.

Default implementation does nothing. Compiled out in shipping builds.


BeginDestroy

virtual

virtual void BeginDestroy()

SetActiveShotsFromSequencer

void SetActiveShotsFromSequencer(const FComposableCameraShot & InPrimaryShot, const FComposableCameraShot * InSecondaryShot, UComposableCameraTransitionDataAsset * InTransition, float InAlpha, bool bPrimaryChanged)

Phase F: push the active Shot pair from the LSComponent each frame an override is applied.

InPrimaryShot → written into Shot (the V1 primary path). InSecondaryShot → optional incoming Shot (the higher-row of an overlap pair). When non-null AND InTransition is non-null, the framing node runs both solvers and blends their poses through the transition using InAlpha ∈ [0, 1]. InTransition → resolved transition asset; null = hard cut. The blend pass is skipped — Shot (primary) is the sole solver input, matching V1 top-row- winner behavior. InAlpha → secondary's contribution weight ∈ [0, 1]. Clamped defensively. Ignored when InSecondaryShot is null or InTransition is null. bPrimaryChanged → true iff the LSComponent detected that the active primary Section has changed since the previous tick (Section A → Section B with no overlap, Section bind to a different ShotAsset, etc.). Triggers a primary-state reseed (bHasLastPrimaryOutputPose = false, LastPrimaryDistance / FOV / Roll cleared) so V2.2 damping doesn't carry the previous shot's pose into the new shot's first frame — without this, Distance / FOV / Roll damping would visibly glide between the two shots' poses on every cut. Phase F blend exits already trigger the same reseed independently; this flag covers the non-overlap cut case.

Persistence: the node retains the last-written state across frames. When the LSComponent's override map empties, no further calls happen and the camera holds its last framing — the gap-fill semantic Phase E established and Phase F preserves.


SetExternalAspectRatioOverride

inline

inline void SetExternalAspectRatioOverride(float Aspect)

Push the effective render aspect ratio for solver use. The node by default queries GetEffectiveViewportAspectRatio from OwningPlayerCameraManager, which is null in the LS Component path → falls back to either GameViewport (PIE) or editor active viewport. That works for unconstrained CineCams, but with bConstrainAspectRatio == true the renderer letterboxes to the filmback-derived aspect regardless of viewport, and the solver needs to match. The LS Component computes the effective aspect via GetEffectiveAspectRatioForCineCamera(OutputCineCameraComponent) and pushes it here; OnTickNode prefers the override when > 0.

Set every tick by [UComposableCameraLevelSequenceComponent::TickComponent](../uobjects-other/UComposableCameraLevelSequenceComponent.md#tickcomponent) before invoking the solver. PCM-driven path doesn't call this — falls back to the OwningPlayerCameraManager query, which is correct for gameplay (PCM has access to PlayerController viewport).

Public Static Methods

Return Name Description
const TSet< TWeakObjectPtr< UComposableCameraCompositionFramingNode > > & GetActiveInstances static Per-process registry of all currently-initialized framing nodes — read by [FComposableCameraShotZoneOverlay](../structs/FComposableCameraShotZoneOverlay.md#fcomposablecamerashotzoneoverlay) (the LS / PIE viewport CCS.Debug.Viewport.ShotZones overlay) so it can paint anchor + zone gizmos for every active Shot regardless of whether the host camera is on the PCM context stack or owned by an LS Component.

GetActiveInstances

static

static const TSet< TWeakObjectPtr< UComposableCameraCompositionFramingNode > > & GetActiveInstances()

Per-process registry of all currently-initialized framing nodes — read by [FComposableCameraShotZoneOverlay](../structs/FComposableCameraShotZoneOverlay.md#fcomposablecamerashotzoneoverlay) (the LS / PIE viewport CCS.Debug.Viewport.ShotZones overlay) so it can paint anchor + zone gizmos for every active Shot regardless of whether the host camera is on the PCM context stack or owned by an LS Component.

Ownership: each instance adds itself in OnInitialize and removes itself in BeginDestroy. TWeakObjectPtr keys ensure GC'd nodes silently drop out of iteration without needing manual cleanup.

Cost: a single static TSet insert/remove per camera lifecycle boundary; iteration cost is paid only when the overlay CVar is on. Compiled out in shipping.

Private Attributes

Return Name Description
int32 LocalFrameCounter Per-instance frame counter for the Periodic bounds-cache policy. Reset in OnInitialize, incremented at the end of every OnTickNode. [EBoundsCachePolicy::Live](#ComposableCameraShotTarget_8h_1a8e6b3060b0f99d10653877e1989d03aba955ad3298db330b5ee880c2c9e6f23a0) ignores this; StaticSnapshot doesn't touch the cache after OnInitialize.
bool bLastTickWasUnresolved
int32 LastUnresolvedTargetCount
bool bHasLastPrimaryOutputPose True iff LastPrimaryOutputPose carries a valid prior solve. False = next tick performs a V1 hard solve and seeds the cache.
FVector LastPrimaryOutputPosition Position + rotation produced by the most recent successful primary solve. Read by the next tick to project Aim/Placement anchors when the corresponding zones are enabled. Only Position + Rotation are cached because the Solver re-derives FOV / Focus from the current frame's context regardless.
FRotator LastPrimaryOutputRotation
float LastPrimaryDistance Last frame's effective Placement.Distance (post-damping + post-clamp) for the primary shot. < 0 ⇒ no prior — solver skips Distance damping on the next tick and uses the authored value. Cached together with the pose because both share the same activation / Section-boundary lifecycle.
float LastPrimaryFOV Last frame's effective FOV / Roll for the primary shot — sentinel semantics match FShotPriorPose::LastFOV / LastRoll: LastPrimaryFOV < 0 ⇒ no prior; LastPrimaryRoll == FLT_MAX ⇒ no prior. Solver skips the corresponding damping on the next tick when the prior is absent.
float LastPrimaryRoll
bool bHasLastSecondaryOutputPose Same as primary, but for the Phase F secondary (incoming) shot. Cleared whenever SetActiveShotsFromSequencer transitions out of the secondary-active state.
FVector LastSecondaryOutputPosition
FRotator LastSecondaryOutputRotation
float LastSecondaryDistance
float LastSecondaryFOV
float LastSecondaryRoll
bool bHasSecondaryShot True iff a secondary (incoming) Shot is currently active for blend. When false the node behaves as V1: Shot is the sole solver input.
FComposableCameraShot SecondaryShot Phase F secondary Shot — the higher-row (incoming) section's effective Shot. Only consumed when bHasSecondaryShot is true and ActiveBlendTransition is non-null.
TObjectPtr< UComposableCameraTransitionDataAsset > ActiveBlendTransition Resolved EnterTransition asset for the active two-Shot blend. Set by SetActiveShotsFromSequencer. Null indicates hard cut — F.4 treats this as "secondary Shot is unused" and runs only the primary solver path.
float ActiveBlendAlpha Secondary's contribution weight ∈ [0, 1] for the active blend. 0 = primary fully dominant; 1 = secondary fully dominant.
float ExternalAspectRatioOverride Push-from-LSComponent override for the solver's ViewportAspectRatio. Honors CineCam's bConstrainAspectRatio (filmback-derived) vs unconstrained (live viewport size, including editor scrub via the [FGetActiveEditorViewport](../structs/FGetActiveEditorViewport.md#fgetactiveeditorviewport) hook). 0 = no override, fall back to GetEffectiveViewportAspectRatio(OwningPlayerCameraManager).

LocalFrameCounter

int32 LocalFrameCounter = 0

Per-instance frame counter for the Periodic bounds-cache policy. Reset in OnInitialize, incremented at the end of every OnTickNode. [EBoundsCachePolicy::Live](#ComposableCameraShotTarget_8h_1a8e6b3060b0f99d10653877e1989d03aba955ad3298db330b5ee880c2c9e6f23a0) ignores this; StaticSnapshot doesn't touch the cache after OnInitialize.


bLastTickWasUnresolved

bool bLastTickWasUnresolved = false

LastUnresolvedTargetCount

int32 LastUnresolvedTargetCount = INDEX_NONE

bHasLastPrimaryOutputPose

bool bHasLastPrimaryOutputPose = false

True iff LastPrimaryOutputPose carries a valid prior solve. False = next tick performs a V1 hard solve and seeds the cache.


LastPrimaryOutputPosition

FVector LastPrimaryOutputPosition = FVector::ZeroVector

Position + rotation produced by the most recent successful primary solve. Read by the next tick to project Aim/Placement anchors when the corresponding zones are enabled. Only Position + Rotation are cached because the Solver re-derives FOV / Focus from the current frame's context regardless.


LastPrimaryOutputRotation

FRotator LastPrimaryOutputRotation = FRotator::ZeroRotator

LastPrimaryDistance

float LastPrimaryDistance = -1.f

Last frame's effective Placement.Distance (post-damping + post-clamp) for the primary shot. < 0 ⇒ no prior — solver skips Distance damping on the next tick and uses the authored value. Cached together with the pose because both share the same activation / Section-boundary lifecycle.


LastPrimaryFOV

float LastPrimaryFOV = -1.f

Last frame's effective FOV / Roll for the primary shot — sentinel semantics match FShotPriorPose::LastFOV / LastRoll: LastPrimaryFOV < 0 ⇒ no prior; LastPrimaryRoll == FLT_MAX ⇒ no prior. Solver skips the corresponding damping on the next tick when the prior is absent.


LastPrimaryRoll

float LastPrimaryRoll = TNumericLimits<float>::Max()

bHasLastSecondaryOutputPose

bool bHasLastSecondaryOutputPose = false

Same as primary, but for the Phase F secondary (incoming) shot. Cleared whenever SetActiveShotsFromSequencer transitions out of the secondary-active state.


LastSecondaryOutputPosition

FVector LastSecondaryOutputPosition = FVector::ZeroVector

LastSecondaryOutputRotation

FRotator LastSecondaryOutputRotation = FRotator::ZeroRotator

LastSecondaryDistance

float LastSecondaryDistance = -1.f

LastSecondaryFOV

float LastSecondaryFOV = -1.f

LastSecondaryRoll

float LastSecondaryRoll = TNumericLimits<float>::Max()

bHasSecondaryShot

bool bHasSecondaryShot = false

True iff a secondary (incoming) Shot is currently active for blend. When false the node behaves as V1: Shot is the sole solver input.


SecondaryShot

FComposableCameraShot SecondaryShot

Phase F secondary Shot — the higher-row (incoming) section's effective Shot. Only consumed when bHasSecondaryShot is true and ActiveBlendTransition is non-null.


ActiveBlendTransition

TObjectPtr< UComposableCameraTransitionDataAsset > ActiveBlendTransition

Resolved EnterTransition asset for the active two-Shot blend. Set by SetActiveShotsFromSequencer. Null indicates hard cut — F.4 treats this as "secondary Shot is unused" and runs only the primary solver path.


ActiveBlendAlpha

float ActiveBlendAlpha = 0.0f

Secondary's contribution weight ∈ [0, 1] for the active blend. 0 = primary fully dominant; 1 = secondary fully dominant.


ExternalAspectRatioOverride

float ExternalAspectRatioOverride = 0.f

Push-from-LSComponent override for the solver's ViewportAspectRatio. Honors CineCam's bConstrainAspectRatio (filmback-derived) vs unconstrained (live viewport size, including editor scrub via the [FGetActiveEditorViewport](../structs/FGetActiveEditorViewport.md#fgetactiveeditorviewport) hook). 0 = no override, fall back to GetEffectiveViewportAspectRatio(OwningPlayerCameraManager).