This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Orbital Solver

Pivot-aware position and pose blend primitive — Linear / Cylindrical / Spherical shapes around a configurable pivot. Powers PhantomCam blend shapes, DynamicOrbitBody damping, and LeadingFollowBody band response.

The OrbitalSolver namespace (GS_Core::Math) provides a stateless, deterministic, pure position / pose blend primitive. Given two positions (or transforms) and a pivot point, it blends them by a fraction t along an arc that preserves the geometric relationship to the pivot — rather than the straight-line lerp that cuts through the pivot when the start and end sit on opposite sides.

The primitive originated as a fix for PhantomCam cross-cam blends (two orbit cams at ~180° around a shared target produced visible “cuts through the target” mid-blend). It has since been reframed as the standard movement primitive for any motion naturally arranged around a pivot — orbit cams, leading-follow band response, post-config stage transitions. Linear lerp becomes the special case used only when no meaningful pivot exists.

Used by: gs_phantomcam (cross-cam blend execution, DynamicOrbitBody damping, LeadingFollowBody band response).

For usage guides and PhantomCam-specific consumption patterns, see the PhantomCam consumer pages linked under Consumers.

 

Contents


BlendShape

The path shape. The same enum is reflected for authoring on consumers like Blend Profiles and DynamicOrbitBody.

enum class BlendShape : AZ::u8 {
    Linear,
    Cylindrical,
    Spherical,
};
ShapeBehaviorRight for
LinearStraight-line lerp in world space. Used as the automatic fallback when the pivot or either input is degenerate.Cams with no meaningful pivot. Short displacements where arcing would be over-engineered.
CylindricalInterpolate in a plane perpendicular to cylinderAxis (default world +Z). Radius and angle lerp in the plane; height along the axis lerps linearly.Ground cams sweeping horizontally without dipping / rising mid-sweep. PhantomCam LeadingFollowBody band response default.
SphericalFull 3D arc on the surface of a sphere centered on the pivot. Radius lerps along the arc.Cams that change height during the motion (over-shoulder ↔ overhead). Free pitch-aware orbits. PhantomCam DynamicOrbitBody default.

PivotSource

For two-cam blends, which cam’s pivot drives the arc. Single-pivot consumers don’t need this — they pass the one pivot they already have. The enum exists for cross-cam blend authoring on PhantomBlend.

enum class PivotSource : AZ::u8 {
    Source,
    Destination,
    Shared,
};
ValueBehavior
SourceSweep around the outgoing cam’s pivot. “Leave cam A elegantly, then settle into wherever cam B is.”
DestinationSweep around the incoming cam’s pivot. “Arrive at cam B elegantly, regardless of where cam A was.”
SharedMidpoint of both pivots. Collapses identically to either single-pivot mode when the pivots coincide. Recommended default — handles same-target and split-target cases gracefully.

BlendPositionAroundPivot

Position-only blend.

AZ::Vector3 BlendPositionAroundPivot(
    const AZ::Vector3& a,
    const AZ::Vector3& b,
    const AZ::Vector3& pivot,
    float              t,
    BlendShape         shape,
    const AZ::Vector3& cylinderAxis = AZ::Vector3::CreateAxisZ());

Blend position a toward b around pivot by fraction t along the chosen shape.

Parameters

ParameterPurpose
aStart position.
bEnd position.
pivotWorld-space pivot point. The geometric center of the arc.
tBlend fraction. NOT pre-clamped — values outside [0, 1] extrapolate.
shapeLinear / Cylindrical / Spherical.
cylinderAxisUp axis for cylindrical mode. Default world +Z. Ignored by Linear and Spherical.

Degenerate cases (automatic Linear fallback)

The function falls back to linear lerp when:

  • Either input position is at or very-near the pivot (degenerate radius makes arc direction undefined).
  • shape == Linear explicitly.

Extrapolation

Occasionally useful — e.g. t = -0.1 to anti-extrapolate slightly past the source for an overshoot effect.

Cylindrical math sketch

height_a = (a - pivot) ⋅ cylinderAxis
height_b = (b - pivot) ⋅ cylinderAxis
plane_a  = a - pivot - height_a * cylinderAxis
plane_b  = b - pivot - height_b * cylinderAxis

radius_a = plane_a.length
radius_b = plane_b.length
angle_a  = atan2(plane_a.y, plane_a.x)
angle_b  = atan2(plane_b.y, plane_b.x)
shortestAngle_b = ShortestArc(angle_a, angle_b)

radius_t = lerp(radius_a, radius_b, t)
angle_t  = lerp(angle_a, shortestAngle_b, t)
height_t = lerp(height_a, height_b, t)

result = pivot + height_t * cylinderAxis +
         radius_t * (cos(angle_t) * basisX + sin(angle_t) * basisY)

Spherical math sketch

dir_a    = (a - pivot).normalized
dir_b    = (b - pivot).normalized
radius_a = (a - pivot).length
radius_b = (b - pivot).length

// Slerp the direction on the unit sphere.
angle = acos(clamp(dir_a · dir_b, -1, 1))
if angle ≈ 0:  dir_t = dir_a
else:
    dir_t = (dir_a * sin((1 - t) * angle) + dir_b * sin(t * angle)) / sin(angle)

radius_t = lerp(radius_a, radius_b, t)

result = pivot + dir_t * radius_t

BlendPoseAroundPivot

Pose-level blend. Position uses BlendPositionAroundPivot. Rotation defaults to “look at pivot from the blended position.”

AZ::Transform BlendPoseAroundPivot(
    const AZ::Transform& a,
    const AZ::Transform& b,
    const AZ::Vector3&   pivot,
    float                t,
    BlendShape           shape,
    const AZ::Vector3&   cylinderAxis    = AZ::Vector3::CreateAxisZ(),
    bool                 slerpRotations  = false);

Rotation modes

slerpRotationsRotation behavior
false (default)Fresh “look at pivot from the blended position” quaternion. The most-often-correct cam semantic — orientation always faces the pivot regardless of either input cam’s authored aim quirks. Uses +Y forward / +Z up (matches AZ::Transform::CreateLookAt).
trueSlerp a.GetRotation()b.GetRotation() directly. For UI / VFX consumers that want raw input-rotation interpolation.

ResolveCrossCamPivot

For consumers that need to pick between two cams’ pivots based on a PivotSource enum.

struct ResolvedPivot {
    AZ::Vector3 pivot;
    bool        valid;   // false → no meaningful pivot; use Linear
};

ResolvedPivot ResolveCrossCamPivot(
    bool               sourceHasPivot,
    const AZ::Vector3& sourcePivot,
    bool               destHasPivot,
    const AZ::Vector3& destPivot,
    PivotSource        source);

Resolution table

sourceBoth have pivotsOnly source hasOnly dest hasNeither
SourcesourcePivotsourcePivotdestPivot (fallback)invalid
DestinationdestPivotsourcePivot (fallback)destPivotinvalid
Sharedmidpoint(s, d)sourcePivotdestPivotinvalid

valid = false signals the consumer should fall back to BlendShape::Linear.


Reflection

void ReflectOrbitalSolverEnums(AZ::ReflectContext* context);

Reflects BlendShape and PivotSource into the SerializeContext so authoring sites (PhantomBlend, body stages) can expose them as ComboBox EnumAttributes.

Guarded internally — safe to call multiple times from independent reflect chains. PhantomBlend and DynamicOrbitBody both call it at the top of their own Reflect.


Per-Tick Damping Pattern

The primitive doubles as a per-tick damping operator. Spring damping toward a pivot-arranged destination is exactly this primitive:

m_idealPosition = GS_Core::Math::BlendPositionAroundPivot(
    m_idealPosition,
    desiredPos,
    pivot,
    HalflifeAlpha(halflife, deltaTime),
    shape);

No separate spring needed. HalflifeAlpha comes from the standard halflife → per-frame alpha conversion 1 − exp2(-deltaTime / halflife).

This is how DynamicOrbitBody damps the cam’s position toward whatever its target angles resolve to on the orbit surface — the solver call IS the smoothing. Authors tune one halflife and get spring-like response shaped around the pivot.


Consumers

PhantomCam consumers

ConsumerUse
Cam Core — cross-cam blend executionBlendPositionAroundPivot per-tick during cross-cam blends. Pivot resolved by the matched blend entry’s PivotSource choice (Source / Destination / Shared).
Blend Profiles — authoringEach PhantomBlend entry authors BlendShape and PivotSource.
DynamicOrbitBody — per-tick dampingSolver IS the damping primitive — yaw / pitch target produces desiredPos on the orbit surface; solver damps m_idealPosition toward it.
LeadingFollowBody — band responseCylindrical default. Cam arcs around the target during band response rather than cutting through.

Non-PhantomCam consumers

The same primitive serves any system that needs pivot-aware blending — UI elements orbiting a focal point, world-space VFX paths, unit motion patterns. The primitive is namespaced under GS_Core::Math precisely so other gems can adopt it.


See Also

Related GS_Core utilities:

  • Springs — spring-damper primitives. Use Springs for non-pivot smoothing; use Orbital Solver when motion naturally arranges around a pivot.
  • CurvesCurveType enum used for blend easing curves.
  • Angles Helper — yaw wrap / shortest-arc helpers used by orbit body stages.

PhantomCam consumer pages:


Get GS_Core

Explore other GS_Play gems on the product page.