Additive Stage Variants

Additive stage catalog — collision and tug listener Reposition stages, Perlin and Impulse Noise stages. Phase semantics, composition rules, per-variant fields.

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 as m_stablePose.
  • Noise — runs AFTER reposition. Perturbations (camera shake, drift, impulses). State after this phase is captured as m_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.

Additive stages list on the Phantom Camera component

 

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 halflife seconds 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

FieldDefaultPurpose
m_enabledtrueMaster 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_innerRadius0.15Hard clamp distance. Serialized as "Radius" for back-compat.
m_outerRadius0.45Soft buffer distance. Clamped at evaluate time to be ≥ inner.
m_halflife0.15Soft 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.

FieldDefaultPurpose
m_enabledtrueMaster toggle.
m_halflife0.15Correction 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

FieldDefaultPurpose
m_enabledtrueMaster toggle.
m_channels[]String tags the listener matches against tug volume channels. Crc32-cached at activate.
m_blendHalflife0.15Smoothing halflife for influence and source-point.
m_strength1.0Per-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

StepTugBodyListener
GetSeedSourcePointReturns state.position — first engaged tick produces zero displacement (the cam doesn’t yank).
ApplyModulationDerives 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

FieldDefaultPurpose
m_enabledtrueMaster toggle.
m_profile(asset slot).camnoiseprofile asset.
m_amplitudeGain1.0Multiplier over profile amplitudes.
m_frequencyGain1.0Multiplier 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

FieldDefaultPurpose
m_enabledtrueMaster toggle.
m_profile(asset slot).camnoiseprofile.
m_envelope(ADSR struct)Attack / Decay / Sustain level / Sustain duration / Release. See GS_Core::Envelope::ADSREnvelope.
m_amplitudeGain1.0Multiplier over profile amplitudes.
m_frequencyGain1.0Multiplier 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

VariantPhaseReads ctx natural pose?Notes
CollisionRepositionRepositionPhysX sphere-cast pushback. Soft buffer + hard clamp.
OcclusionRepositionRepositionStub. Damping scaffold present.
TugAimListenerRepositionYes (cam-to-target forward)Slerps rotation toward source.
TugBodyListenerRepositionYes (target / bodyAnchor)Lerps position toward source.
PerlinNoiseNoiseContinuous Perlin sampled by accumulated time.
ImpulseNoiseNoiseEvent-triggered; ADSR-gated.

See Also


Get GS_PhantomCam

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