Additive Stage Variants
Categories:
Additive stages stack — multiple may run per cam. Each declares its phase via GetStage():
Reposition— runs BEFORE noise. Corrections (collision, occlusion, tug reposition). State after this phase is captured asm_stablePose.Noise— runs AFTER reposition. Perturbations (camera shake, drift, impulses). State after this phase is captured asm_finalPose.
The Phantom Camera component partitions additives into m_repositionStages and m_noiseStages once at slot-assign time so per-tick code does not re-check the enum each frame.

Contents
Reposition Additives
CollisionReposition
Soft / hard sphere collision correction.
Header: Stages/StageDefaults.h · Source: Source/Stages/StageDefaults.cpp
Algorithm
Sphere-cast from (target root + anchorOffset) toward the Body’s ideal cam position (state.position). Two radii define behavior:
- Inner radius — hard clamp. Cam is never permitted closer to the hit surface than this. Correction is immediate.
- Outer radius — soft buffer. Cam glides toward this preferred distance from the wall over
halflifeseconds via a damped cached correction delta.
When the Body’s ideal is inside the outer zone (between inner and outer), only the soft correction applies. When it is past the inner zone (closer than inner), hard correction snaps the cam to the inner boundary, and soft continues gliding it toward the outer resting spot.
The cached correction m_appliedCorrection damps cleanly toward zero when the target walks out of collision — no feedback loop fighting the Body’s spring (which runs on its own m_idealPosition per the decoupled-ideal pattern).
Authored fields
| Field | Default | Purpose |
|---|---|---|
m_enabled | true | Master toggle. |
m_anchorOffset | (0, 0, 1.5) | Added to target root to produce the cast origin. Typically cam-height so the cast sweeps at cam altitude. |
m_innerRadius | 0.15 | Hard clamp distance. Serialized as "Radius" for back-compat. |
m_outerRadius | 0.45 | Soft buffer distance. Clamped at evaluate time to be ≥ inner. |
m_halflife | 0.15 | Soft correction halflife (0 = snap). |
m_collisionGroupId | (null = All) | UUID reference to a PhysX collision-group preset. Rendered as a dropdown by PhysX’s property handler — matches the “Collides With” field on PhysX colliders. |
OcclusionReposition
Stub. Math is future work. The damping scaffold (m_halflife, m_appliedCorrection) is in place so authored fields are stable when the real implementation lands.
| Field | Default | Purpose |
|---|---|---|
m_enabled | true | Master toggle. |
m_halflife | 0.15 | Correction halflife. |
TugAimListener
Reposition-phase consumer of Tug Fields. Slerps state.rotation toward look-at the smoothed tug source point while a TugFieldProxyComponent on the rig is in contact with a tug volume.
Header: Stages/TugListeners.h · Source: Source/Stages/TugListeners.cpp
Derives from the abstract TugListenerBase which holds the channel matching, proxy resolution, active-source cache, and smoothed-influence state.
Authored fields
| Field | Default | Purpose |
|---|---|---|
m_enabled | true | Master toggle. |
m_channels | [] | String tags the listener matches against tug volume channels. Crc32-cached at activate. |
m_blendHalflife | 0.15 | Smoothing halflife for influence and source-point. |
m_strength | 1.0 | Per-cam force multiplier. |
Per-tick algorithm
1. ResolveProxy(ctx)
- If target changed since last tick, walk for proxy descendant.
- If proxy changed, bind to it and repopulate from volume world.
2. Sum contributions from m_activeSources (weighted by source strength × channel match).
totalInfluence, weightedSourcePoint = Σ source contributions
3. If totalInfluence == 0 AND m_smoothedInfluence ≈ 0:
m_dormant = true; early-return.
4. If transitioning out of dormancy:
m_smoothedSourcePoint = GetSeedSourcePoint(state) — a point along the cam's
current forward, so the ramp-in produces zero displacement at first engagement.
5. Damp m_smoothedInfluence and m_smoothedSourcePoint toward their target values.
6. ApplyModulation: derive the "natural" rotation from ctx (cam toward primary target)
rather than reading state.rotation. Avoids compound-feedback issue where prior tug
modifications would loop into next tick's blend source.
TugBodyListener
Reposition-phase consumer of Tug Fields. Lerps state.position toward the smoothed source point.
Header: Stages/TugListeners.h · Source: Source/Stages/TugListeners.cpp
Same base class, fields, and dormancy semantics as TugAimListener.
Algorithm differences from TugAimListener
| Step | TugBodyListener |
|---|---|
GetSeedSourcePoint | Returns state.position — first engaged tick produces zero displacement (the cam doesn’t yank). |
ApplyModulation | Derives the natural pose source from ctx.targetTM and the Body’s published state.bodyAnchor sidecar, not state.position directly. Prevents compound feedback. |
A cam can run only the aim listener, only the body listener, both, or neither — each is independent.
Noise Additives
PerlinNoise
Continuous Perlin-noise displacement. Samples a Camera Noise Profile (.camnoiseprofile) through GS_Core::Noise::Perlin1D across six channels (translation X / Y / Z, rotation pitch / yaw / roll).
Header: Stages/NoiseStages.h · Source: Source/Stages/NoiseStages.cpp
Algorithm
m_time += deltaTime
For each of 6 channels in profile:
sample = Perlin1D(m_time * profile.frequency[i] * m_frequencyGain,
profile.octaves[i],
profile.persistence[i])
delta[i] = sample * profile.amplitude[i] * m_amplitudeGain
state.position += (delta[Tx], delta[Ty], delta[Tz])
state.rotation = state.rotation * Quaternion::CreateFromEuler(
delta[Pitch], delta[Yaw], delta[Roll])
Never reads back the committed transform between frames — only adds deltas to state in-flight. Body / Aim ideals stay untouched (decoupled-ideal convention).
Authored fields
| Field | Default | Purpose |
|---|---|---|
m_enabled | true | Master toggle. |
m_profile | (asset slot) | .camnoiseprofile asset. |
m_amplitudeGain | 1.0 | Multiplier over profile amplitudes. |
m_frequencyGain | 1.0 | Multiplier over profile frequencies. |
Init contract
Init() force-loads m_profile via AssetManager per the asset-fresh-load pattern.
ImpulseNoise
Event-triggered one-shot noise burst. Same profile schema as PerlinNoise, same layered Perlin math — only the time source differs.
Stays silent until Trigger(strength) fires, then plays a time-windowed burst shaped by an ADSR envelope (GS_Core::Envelope::ADSREnvelope).
Triggered externally via PhantomCameraRequestBus::TriggerCameraImpulse(strength) on the cam — see Phantom Cameras. The author’s own impulse-source detection system (explosions, foot-stomps, gun kicks) decides when and with what strength.
Stacks freely with PerlinNoise on the same cam — baseline handheld sway plus event-driven shake is the canonical configuration.
Algorithm
On Trigger(strength):
m_elapsed = 0.0
m_triggerStrength = strength
m_active = true // overrides any in-progress release
Per tick (if m_active):
m_elapsed += deltaTime
envelopeGain = m_envelope.Evaluate(m_elapsed) // ADSR 0..1
if (envelopeGain == 0 AND past sustain end):
m_active = false
else:
// Same Perlin sampling as PerlinNoise,
// scaled by envelopeGain × triggerStrength × m_amplitudeGain.
Apply position + rotation deltas to state.
Authored fields
| Field | Default | Purpose |
|---|---|---|
m_enabled | true | Master toggle. |
m_profile | (asset slot) | .camnoiseprofile. |
m_envelope | (ADSR struct) | Attack / Decay / Sustain level / Sustain duration / Release. See GS_Core::Envelope::ADSREnvelope. |
m_amplitudeGain | 1.0 | Multiplier over profile amplitudes. |
m_frequencyGain | 1.0 | Multiplier over profile frequencies. |
Trigger entry point
void ImpulseNoise::Trigger(float strength);
Called by GS_PhantomCameraComponent::TriggerCameraImpulse(strength) for every ImpulseNoise additive on the cam. strength multiplies amplitude for that specific burst. Pass 1.0 for full profile intensity, smaller for attenuated distance falloff, larger to boost.
The owning component routes the call by walking m_noiseStages and dispatching to each ImpulseNoise it finds via RTTI check. Other noise additive types ignore.
Cross-Variant Summary
| Variant | Phase | Reads ctx natural pose? | Notes |
|---|---|---|---|
CollisionReposition | Reposition | — | PhysX sphere-cast pushback. Soft buffer + hard clamp. |
OcclusionReposition | Reposition | — | Stub. Damping scaffold present. |
TugAimListener | Reposition | Yes (cam-to-target forward) | Slerps rotation toward source. |
TugBodyListener | Reposition | Yes (target / bodyAnchor) | Lerps position toward source. |
PerlinNoise | Noise | — | Continuous Perlin sampled by accumulated time. |
ImpulseNoise | Noise | — | Event-triggered; ADSR-gated. |
See Also
- Stage Pipeline — per-tick orchestration, interfaces, conventions.
- Body Stage Variants
- Aim Stage Variants
- Noise Profiles — the
.camnoiseprofileasset. - Tug Fields — volume / source / proxy model; the listener additives consume it.
- Phantom Cameras —
TriggerCameraImpulseentry point.
Get GS_PhantomCam
GS_PhantomCam — Explore this gem on the product page and add it to your project.