#include <ComposableCameraVolumeConstraintNode.h>
Inherits:
UComposableCameraCameraNodeBase
Constrains the camera to stay inside a single world-space volume. When the upstream camera position is outside the volume, it is projected to the nearest point on the volume's boundary. When it is inside, the node is a no-op and passes the pose through untouched.
Single volume only (MVP) — multiple-volume setups with priority / blend radius (PostProcessVolume-style) are not supported; swap cameras via transitions to change the active volume. Only the "keep inside" semantic is implemented; "keep outside" (forbidden region) is not.
The clamp is a hard projection by default. An optional ClampInterpolator adds per-axis temporal smoothing so three discontinuity modes stop reading as visible snaps:
-
Release snap — upstream crosses from outside to inside in one frame. Without smoothing the output jumps from the boundary point back to the freely-moving upstream position.
-
Corner face switch — upstream orbits past a corner where the nearest-point face flips (e.g. +X face → corner → +Y face). Position is still Lipschitz continuous but the tangent direction can change abruptly, reading as a crease.
-
Teleport / warp — any scripted camera jump across the boundary.
When ClampInterpolator is null, the node is fully stateless and the pose is deterministic given the upstream input. When it is set, per-axis smoothing introduces controllable lag.
Position formula each tick: let Volume = Resolve(VolumeSource)
if IsInside(OutPose.Position, Volume):
OutPose.Position unchanged
else:
OutPose.Position = NearestPointInsideVolume(OutPose.Position, Volume)
For a Box volume (OBB), "nearest point" is computed in the volume's local space by per-axis clamping against the half-extents, then transformed back to world. For a Sphere, it is Center + (Pos - Center).SafeNormal * Radius.
Chain placement: runs on the camera's position, so put it after CameraOffsetNode / LookAtNode / any position-writing node that produces the "desired" position, and before CollisionPushNode so the collision push operates on the already-clamped input.
Public Attributes¶
| Return | Name | Description |
|---|---|---|
EComposableCameraVolumeSource |
VolumeSource |
Where the volume geometry comes from. |
TObjectPtr< AActor > |
VolumeActor |
Actor with a UShapeComponent (UBoxComponent / USphereComponent) whose transform and scaled extents define the volume. The first shape component found via GetComponents<UShapeComponent> wins — multiple shape components on one actor are not supported; use Inline mode or a dedicated actor with a single shape when precise control is needed. |
EComposableCameraVolumeShape |
Shape |
Inline-mode shape selector. Ignored in FromActor mode (the shape comes from the component's class). |
FVector |
VolumeCenter |
Inline-mode volume center in world space. |
FRotator |
VolumeRotation |
Inline-mode volume rotation (world space). Only affects the Box shape — an OBB is the difference from an AABB. Ignored for Sphere. |
FVector |
BoxExtents |
Inline-mode box half-extents in the volume's local space (pre-rotation). |
float |
SphereRadius |
Inline-mode sphere radius in world units. |
TObjectPtr< UComposableCameraInterpolatorBase > |
ClampInterpolator |
Optional interpolator applied per axis to the output position. When set, the node keeps a private LastSmoothedPosition and each tick smooths it toward the (clamp or pass-through) target using THREE independent 1D interpolator instances (one per world-space axis), preserving the filter's dynamics per axis. When null, the node is stateless and the output is a hard projection / pass-through. |
VolumeSource¶
EComposableCameraVolumeSource VolumeSource { }
Where the volume geometry comes from.
VolumeActor¶
TObjectPtr< AActor > VolumeActor { nullptr }
Actor with a UShapeComponent (UBoxComponent / USphereComponent) whose transform and scaled extents define the volume. The first shape component found via GetComponents<UShapeComponent> wins — multiple shape components on one actor are not supported; use Inline mode or a dedicated actor with a single shape when precise control is needed.
Shape¶
EComposableCameraVolumeShape Shape { }
Inline-mode shape selector. Ignored in FromActor mode (the shape comes from the component's class).
VolumeCenter¶
FVector VolumeCenter { FVector::ZeroVector }
Inline-mode volume center in world space.
VolumeRotation¶
FRotator VolumeRotation { FRotator::ZeroRotator }
Inline-mode volume rotation (world space). Only affects the Box shape — an OBB is the difference from an AABB. Ignored for Sphere.
BoxExtents¶
FVector BoxExtents { FVector(500.f, 500.f, 300.f) }
Inline-mode box half-extents in the volume's local space (pre-rotation).
SphereRadius¶
float SphereRadius { 500.f }
Inline-mode sphere radius in world units.
ClampInterpolator¶
TObjectPtr< UComposableCameraInterpolatorBase > ClampInterpolator
Optional interpolator applied per axis to the output position. When set, the node keeps a private LastSmoothedPosition and each tick smooths it toward the (clamp or pass-through) target using THREE independent 1D interpolator instances (one per world-space axis), preserving the filter's dynamics per axis. When null, the node is stateless and the output is a hard projection / pass-through.
Picks the same UInterpolatorBase instanced subobject pattern that CollisionPush / PivotDamping use — users choose between SpringDamper, IIR, SimpleSpring, etc.
Public Methods¶
| Return | Name | Description |
|---|---|---|
UComposableCameraVolumeConstraintNode inline |
||
void |
OnInitialize_Implementation virtual |
|
void |
OnTickNode_Implementation virtual |
|
void |
GetPinDeclarations_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. |
UComposableCameraVolumeConstraintNode¶
inline
inline UComposableCameraVolumeConstraintNode()
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
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.
Private Attributes¶
| Return | Name | Description |
|---|---|---|
TUniquePtr< TCameraInterpolator< TValueTypeWrapper< double > > > |
ClampInterpolatorX_T |
Per-axis 1D interpolator instances built from ClampInterpolator in OnInitialize. Three independent filter states let the X/Y/Z smoothing dynamics stay decoupled — a spring overshoot on X shouldn't bleed into Y / Z. |
TUniquePtr< TCameraInterpolator< TValueTypeWrapper< double > > > |
ClampInterpolatorY_T |
|
TUniquePtr< TCameraInterpolator< TValueTypeWrapper< double > > > |
ClampInterpolatorZ_T |
|
FVector |
LastSmoothedPosition |
Persistent smoothed output. Seeded on the first tick from the upstream position so the first frame isn't a snap from origin. |
bool |
bHasSeededSmoothing |
Cleared in OnInitialize; set to true the first time OnTickNode runs so the seed happens against the live upstream pose. Re-activation resets this via OnInitialize. |
FResolvedVolume |
DebugResolvedVolume |
|
bool |
DebugHasResolvedVolume |
|
bool |
DebugIsClamping |
|
FVector |
DebugClampedPosition |
|
FVector |
DebugUpstreamPosition |
ClampInterpolatorX_T¶
TUniquePtr< TCameraInterpolator< TValueTypeWrapper< double > > > ClampInterpolatorX_T
Per-axis 1D interpolator instances built from ClampInterpolator in OnInitialize. Three independent filter states let the X/Y/Z smoothing dynamics stay decoupled — a spring overshoot on X shouldn't bleed into Y / Z.
ClampInterpolatorY_T¶
TUniquePtr< TCameraInterpolator< TValueTypeWrapper< double > > > ClampInterpolatorY_T
ClampInterpolatorZ_T¶
TUniquePtr< TCameraInterpolator< TValueTypeWrapper< double > > > ClampInterpolatorZ_T
LastSmoothedPosition¶
FVector LastSmoothedPosition { FVector::ZeroVector }
Persistent smoothed output. Seeded on the first tick from the upstream position so the first frame isn't a snap from origin.
bHasSeededSmoothing¶
bool bHasSeededSmoothing { false }
Cleared in OnInitialize; set to true the first time OnTickNode runs so the seed happens against the live upstream pose. Re-activation resets this via OnInitialize.
DebugResolvedVolume¶
FResolvedVolume DebugResolvedVolume
DebugHasResolvedVolume¶
bool DebugHasResolvedVolume { false }
DebugIsClamping¶
bool DebugIsClamping { false }
DebugClampedPosition¶
FVector DebugClampedPosition { FVector::ZeroVector }
DebugUpstreamPosition¶
FVector DebugUpstreamPosition { FVector::ZeroVector }
Private Methods¶
| Return | Name | Description |
|---|---|---|
bool |
ResolveVolume const |
Fill OutVolume from the current VolumeSource / VolumeActor / Inline properties. Returns false when the source can't provide a usable volume (e.g. FromActor with null actor, or no supported shape component). Logs the specific reason. |
ResolveVolume¶
const
bool ResolveVolume(FResolvedVolume & OutVolume) const
Fill OutVolume from the current VolumeSource / VolumeActor / Inline properties. Returns false when the source can't provide a usable volume (e.g. FromActor with null actor, or no supported shape component). Logs the specific reason.
Private Static Methods¶
| Return | Name | Description |
|---|---|---|
FVector |
NearestPointInVolume static |
Return the nearest point inside the volume to WorldPos. OutIsAlreadyInside is set to true when WorldPos was already inside (the returned point equals WorldPos in that case). |
NearestPointInVolume¶
static
static FVector NearestPointInVolume(const FResolvedVolume & Volume, const FVector & WorldPos, bool & OutIsAlreadyInside)
Return the nearest point inside the volume to WorldPos. OutIsAlreadyInside is set to true when WorldPos was already inside (the returned point equals WorldPos in that case).