< Summary

Information
Class: Trailblazer.Navigation.Motor.PlatformLocomotion
Assembly: Trailblazer
File(s): /home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Navigation/Motor/Locomotion/PlatformLocomotion.cs
Line coverage
100%
Covered lines: 147
Uncovered lines: 0
Coverable lines: 147
Total lines: 425
Line coverage: 100%
Branch coverage
95%
Covered branches: 111
Total branches: 116
Branch coverage: 95.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor()100%11100%
get_IsEnabled()100%11100%
set_IsEnabled(...)100%22100%
RequireContext()50%22100%
get_InvDeltaTime()100%11100%
get_IsActive()100%44100%
get_IsLockedToPlatform()100%11100%
get_IsHoldingPlatform()100%44100%
get_InteriaApplied()100%44100%
UpdatePlatformVelocity()100%1010100%
BindContext(...)100%11100%
GetPlatformInfluence(...)100%1010100%
SetHoldPlatform(...)100%11100%
TickHoldOnPlatform()80%1010100%
HandlePlatformChange(...)100%66100%
DidPlatformChange(...)100%44100%
NormalizeKinematicPlatform(...)100%44100%
ResolveMovementTransfer(...)100%44100%
ClearHoldPlatformIfInactive(...)100%44100%
TryRefreshExistingPlatform(...)91.66%1212100%
SwapActivePlatform(...)100%66100%
HandlePlatformMovement(...)100%11100%
GetAttachmentTransform()87.5%88100%
RecordData(...)100%2222100%

File(s)

/home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Navigation/Motor/Locomotion/PlatformLocomotion.cs

#LineLine coverage
 1using Chronicler;
 2using FixedMathSharp;
 3using System;
 4using System.Runtime.CompilerServices;
 5using Trailblazer.Support;
 6
 7namespace Trailblazer.Navigation.Motor;
 8
 9/// <summary>
 10/// Handles movement adjustments when the scout is standing on a moving platform or surface.
 11/// </summary>
 12/// <remarks>
 13/// This locomotion system tracks platform velocity, rotation, and movement transfer behavior.
 14/// It allows the scout to inherit motion from platforms and supports different transfer states.
 15/// </remarks>
 16public class PlatformLocomotion : ILocomotion
 17{
 18    private TrailblazerWorldContext? _context;
 19
 20    #region Constants
 21
 22    /// <summary>
 23    /// Default height adjustment applied when standing on a moving platform.
 24    /// </summary>
 125    public static readonly Fixed64 DefaultHeightAdjust = Fixed64.FromRaw(0x80000000L); // 0.5f;
 26
 27    /// <summary>
 28    /// Maximum number of frames the scout can remain attached to a platform before release.
 29    /// </summary>
 30    public const int MaxHoldPlatformFrames = 2;
 31
 32    #endregion
 33
 34    #region Configuration State
 35
 36    /// <summary>
 37    /// Determines whether platform locomotion is enabled.
 38    /// </summary>
 83139    private bool _isEnabled = true;
 40
 41    /// <summary>
 42    /// The height offset applied when interacting with moving platforms.
 43    /// </summary>
 83144    public Fixed64 HeightAdjust = DefaultHeightAdjust;
 45
 46    #endregion
 47
 48    #region Transient State
 49
 50    /// <inheritdoc cref="ILocomotion.IsEnabled"/>
 51    public bool IsEnabled
 52    {
 1619853        get => _isEnabled;
 54        set
 55        {
 556            _isEnabled = value;
 557            if (!_isEnabled)
 58            {
 459                _preservePreviousTransformForAttachment = false;
 460                this.ClearTransientState();
 61            }
 562        }
 63    }
 64
 65    /// <summary>
 66    /// Indicates whether the scout has just landed on a new platform.
 67    /// </summary>
 68    [Transient]
 69    public bool IsNewPlatform { get; set; }
 70
 71    /// <summary>
 72    /// The currently active platform the scout is standing on, if any.
 73    /// </summary>
 74    [Transient]
 75    public PlatformSnapshot? ActivePlatform { get; set; }
 76
 77    /// <summary>
 78    /// The previously active platform, used for calculating platform velocity and movement transfer.
 79    /// This is cleared when the scout is not on a platform or when platform locomotion is disabled.
 80    ///
 81    /// </summary>
 82    [Transient]
 83    public PlatformSnapshot? PreviousPlatform { get; set; }
 84
 85    /// <summary>
 86    /// A flag to preserve the previous platform's transform for attachment calculations,
 87    /// used when refreshing the same platform with a new transform.
 88    /// </summary>
 89    private bool _preservePreviousTransformForAttachment;
 90
 91    /// <summary>
 92    /// The platform snapshot that the scout is currently holding onto, if any.
 93    /// </summary>
 94    [Transient]
 95    public PlatformSnapshot? HoldPlatform { get; set; }
 96
 97    /// <summary>
 98    /// Defines how movement is transferred from the platform to the scout.
 99    /// </summary>
 100    [Transient]
 101    public MotionTransfer MovementTransfer { get; set; }
 102
 103    /// <summary>
 104    /// The local position of the scout relative to the platform.
 105    /// </summary>
 106    [Transient]
 107    public Vector3d ScoutLocalPoint { get; set; }
 108
 109    /// <summary>
 110    /// The local rotation of the scout relative to the platform.
 111    /// </summary>
 112    [Transient(typeof(FixedQuaternion), nameof(FixedQuaternion.Identity))]
 113    public FixedQuaternion ScoutLocalRotation { get; set; } = FixedQuaternion.Identity;
 114
 115    /// <summary>
 116    /// The velocity of the platform.
 117    /// </summary>
 118    [Transient]
 119    public Vector3d PlatformVelocity { get; set; }
 120
 121    /// <summary>
 122    /// The last known platform velocity when the scout is airborne.
 123    /// </summary>
 124    [Transient]
 125    public Vector3d FramePlatformVelocity { get; set; }
 126
 127    private TrailblazerWorldContext RequireContext() =>
 311128        _context ?? throw new InvalidOperationException("PlatformLocomotion requires an explicit TrailblazerWorldContext
 129
 311130    private Fixed64 InvDeltaTime => RequireContext().InvDeltaTime;
 131
 132    /// <summary>
 133    /// The number of frames the scout has been holding onto a platform.
 134    /// </summary>
 135    [Transient]
 136    public int HoldPlatformFrames { get; private set; }
 137
 138    /// <summary>
 139    /// Gets a value indicating whether the component is currently active and supports kinematic motion.
 140    /// </summary>
 4138141    public bool IsActive => IsEnabled && ActivePlatform?.SupportsKinematicMotion == true;
 142
 143    /// <summary>
 144    /// Gets a value indicating whether the object is permanently locked to the platform.
 145    /// </summary>
 443146    public bool IsLockedToPlatform => MovementTransfer == MotionTransfer.PermaLocked;
 147
 148    /// <summary>
 149    /// Gets a value indicating whether the current object is holding a platform that supports kinematic motion.
 150    /// </summary>
 1884151    public bool IsHoldingPlatform => IsEnabled && HoldPlatform?.SupportsKinematicMotion == true;
 152
 153    /// <summary>
 154    /// Indicates whether platform inertia (initial velocity transfer) has been applied.
 155    /// </summary>
 1873156    public bool InteriaApplied => IsEnabled
 1873157        && (MovementTransfer == MotionTransfer.InitTransfer || MovementTransfer == MotionTransfer.PermaTransfer);
 158
 159    #endregion
 160
 161    #region Methods
 162
 163    /// <summary>
 164    /// Updates the platform velocity based on movement from the last frame.
 165    /// </summary>
 166    public void UpdatePlatformVelocity()
 167    {
 2092168        if (!IsEnabled) return;
 169
 2090170        if (ActivePlatform?.SupportsKinematicMotion != true)
 171        {
 1705172            PlatformVelocity = Vector3d.Zero;
 1705173            _preservePreviousTransformForAttachment = false;
 1705174            return;
 175        }
 176
 385177        if (!IsNewPlatform)
 178        {
 311179            Vector3d currentPoint = ActivePlatform.Value.Transform.TransformPoint(ScoutLocalPoint);
 311180            Vector3d previousPoint = PreviousPlatform?.Transform.TransformPoint(ScoutLocalPoint) ?? Vector3d.Zero;
 181
 182            // Store platform velocity to use as a canceling force
 311183            PlatformVelocity = (currentPoint - previousPoint) * InvDeltaTime;
 184        }
 185
 385186        PreviousPlatform = ActivePlatform;
 385187        IsNewPlatform = false;
 385188        _preservePreviousTransformForAttachment = false;
 385189    }
 190
 191    internal void BindContext(TrailblazerWorldContext context)
 192    {
 895193        Trailblazer.Pathing.PathRequestContextResolver.ThrowIfUnusable(context);
 895194        _context = context;
 895195    }
 196
 197    /// <summary>
 198    /// Applies movement adjustments due to platform motion, ensuring the object inherits platform movement correctly.
 199    /// </summary>
 200    /// <remarks>
 201    /// This method updates the object’s position and rotation based on the platform’s transform,
 202    /// preventing unwanted movement shifts when transitioning between platforms.
 203    /// </remarks>
 204    public void GetPlatformInfluence(
 205        Vector3d position,
 206        FixedQuaternion rotation,
 207        out Vector3d positionDelta,
 208        out FixedQuaternion rotationDelta)
 209    {
 210        // Apply platform rotation first THEN apply platform movement
 147211        FixedQuaternion targetRotation = ActivePlatform?.Transform.Rotation * ScoutLocalRotation ?? FixedQuaternion.Iden
 147212        if (targetRotation != FixedQuaternion.Identity)
 213        {
 50214            FixedQuaternion rotDelta = targetRotation * rotation.Inverse();
 50215            rotationDelta = rotDelta;
 216        }
 217        else
 97218            rotationDelta = FixedQuaternion.Identity;
 219
 147220        Vector3d newGlobalPoint = ActivePlatform?.Transform.TransformPoint(ScoutLocalPoint) ?? Vector3d.Zero;
 147221        position.y += HeightAdjust;
 147222        positionDelta = newGlobalPoint - position;
 147223    }
 224
 225    /// <summary>
 226    /// Assigns the scout to a sampled platform state, initiating a hold state.
 227    /// </summary>
 228    /// <param name="platform">The sampled platform snapshot to attach to.</param>
 229    public void SetHoldPlatform(PlatformSnapshot? platform)
 230    {
 9231        HoldPlatform = NormalizeKinematicPlatform(platform);
 9232        HoldPlatformFrames = 0;
 9233    }
 234
 235    /// <summary>
 236    /// Updates the platform hold state, releasing the hold if the hold duration expires.
 237    /// </summary>
 238    /// <returns>True if the scout should detach from the platform; otherwise, false.</returns>
 239    public bool TickHoldOnPlatform()
 240    {
 11241        if (!IsHoldingPlatform)
 1242            return false;
 243
 10244        HoldPlatformFrames++;
 10245        if (HoldPlatformFrames >= MaxHoldPlatformFrames)
 246        {
 3247            HoldPlatformFrames = 0;
 3248            if (HoldPlatform != ActivePlatform)
 2249                return true;
 250        }
 251
 8252        return false;
 253    }
 254
 255    /// <summary>
 256    /// Handles changes in the platform state based on the specified ground condition. Updates platform-related movement
 257    /// </summary>
 258    /// <remarks>
 259    /// This method should be called whenever the underlying platform or ground condition changes,
 260    /// such as when stepping onto a new platform or leaving one.
 261    /// It resets and updates platform movement and transfer state as needed.
 262    /// </remarks>
 263    /// <param name="condition">The ground condition representing the current platform state. May be null if there is no
 264    public void HandlePlatformChange(GroundCondition? condition)
 265    {
 266        // If we hit a new platform, reset platform state
 2305267        if (!IsEnabled)
 1268            return;
 269
 270        // Clear it to avoid double-applying next frame
 2304271        FramePlatformVelocity = Vector3d.Zero;
 2304272        PlatformSnapshot? refreshedPlatform = NormalizeKinematicPlatform(condition?.Platform);
 2304273        MovementTransfer = ResolveMovementTransfer(refreshedPlatform, condition);
 2304274        ClearHoldPlatformIfInactive(refreshedPlatform);
 275
 2304276        if (TryRefreshExistingPlatform(refreshedPlatform))
 2168277            return;
 278
 136279        SwapActivePlatform(refreshedPlatform);
 136280    }
 281
 282    /// <summary>
 283    /// Determines if the object has transitioned onto a different platform.
 284    /// </summary>
 285    /// <param name="newPlatform"></param>
 286    /// <returns>True if the object is on a new platform; otherwise, false.</returns>
 2304287    private bool DidPlatformChange(PlatformSnapshot? newPlatform) => ActivePlatform != newPlatform;
 288
 289    private static PlatformSnapshot? NormalizeKinematicPlatform(PlatformSnapshot? platform)
 290    {
 2472291        return platform?.SupportsKinematicMotion == true
 2472292            ? platform
 2472293            : null;
 294    }
 295
 296    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 297    private static MotionTransfer ResolveMovementTransfer(PlatformSnapshot? refreshedPlatform, GroundCondition? conditio
 298    {
 2304299        return refreshedPlatform?.SupportsKinematicMotion == true
 2304300            // refreshedPlatform comes from condition.Platform, so a kinematic snapshot implies condition exists.
 2304301            ? condition!.Value.MotionTransferState
 2304302            : MotionTransfer.None;
 303    }
 304
 305    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 306    private void ClearHoldPlatformIfInactive(PlatformSnapshot? refreshedPlatform)
 307    {
 2304308        if (refreshedPlatform?.SupportsKinematicMotion == true)
 503309            return;
 310
 1801311        HoldPlatform = null;
 1801312        HoldPlatformFrames = 0;
 1801313    }
 314
 315    private bool TryRefreshExistingPlatform(PlatformSnapshot? refreshedPlatform)
 316    {
 2304317        if (DidPlatformChange(refreshedPlatform))
 136318            return false;
 319
 2168320        bool hasTransformRefresh = ActivePlatform?.SupportsKinematicMotion == true
 2168321            && refreshedPlatform?.SupportsKinematicMotion == true
 2168322            && !ActivePlatform.Value.Transform.Equals(refreshedPlatform.Value.Transform);
 323
 2168324        if (hasTransformRefresh)
 9325            PreviousPlatform = ActivePlatform;
 326
 327        // Same platform id, newer transform: refresh the snapshot without marking a platform swap.
 2168328        ActivePlatform = refreshedPlatform;
 2168329        _preservePreviousTransformForAttachment = hasTransformRefresh;
 2168330        return true;
 331    }
 332
 333    private void SwapActivePlatform(PlatformSnapshot? refreshedPlatform)
 334    {
 136335        PreviousPlatform = ActivePlatform?.SupportsKinematicMotion != true
 136336            ? refreshedPlatform
 136337            : ActivePlatform;
 136338        ActivePlatform = refreshedPlatform;
 339
 136340        IsNewPlatform = refreshedPlatform?.SupportsKinematicMotion == true;
 136341        _preservePreviousTransformForAttachment = false;
 136342    }
 343
 344    /// <summary>
 345    /// Updates platform movement by synchronizing the object's position and rotation with the platform it is standing o
 346    /// </summary>
 347    /// <remarks>
 348    /// This method prevents unwanted movement shifts when transitioning between platforms, ensuring smooth locomotion.
 349    /// </remarks>
 350    public void HandlePlatformMovement(Vector3d position, FixedQuaternion rotation)
 351    {
 143352        position.y += HeightAdjust;
 143353        Fixed4x4 attachmentTransform = GetAttachmentTransform();
 143354        ScoutLocalPoint = Fixed4x4.InverseTransformPoint(
 143355            attachmentTransform,
 143356            position);
 357
 143358        ScoutLocalRotation = attachmentTransform.Rotation.Inverse() * rotation;
 143359    }
 360
 361    private Fixed4x4 GetAttachmentTransform()
 362    {
 143363        if (_preservePreviousTransformForAttachment && PreviousPlatform?.SupportsKinematicMotion == true)
 8364            return PreviousPlatform.Value.Transform;
 365
 135366        return ActivePlatform?.Transform ?? Fixed4x4.Identity;
 367    }
 368
 369    #endregion
 370
 371    /// <inheritdoc />
 372    public void RecordData(IChronicler chronicler)
 373    {
 106374        RecordValues.Look(chronicler, ref _isEnabled, "IsEnabled", true);
 106375        RecordValues.Look(chronicler, ref HeightAdjust, "HeightAdjust", DefaultHeightAdjust);
 376
 106377        bool isNewPlatform = IsNewPlatform;
 106378        PlatformSnapshot? activePlatform = ActivePlatform;
 106379        PlatformSnapshot? previousPlatform = PreviousPlatform;
 106380        PlatformSnapshot? holdPlatform = HoldPlatform;
 106381        MotionTransfer movementTransfer = MovementTransfer;
 106382        Vector3d scoutLocalPoint = ScoutLocalPoint;
 106383        FixedQuaternion scoutLocalRotation = ScoutLocalRotation;
 106384        Vector3d platformVelocity = PlatformVelocity;
 106385        Vector3d framePlatformVelocity = FramePlatformVelocity;
 106386        int holdPlatformFrames = HoldPlatformFrames;
 387
 106388        RecordValues.Look(chronicler, ref isNewPlatform, "IsNewPlatform", false);
 106389        RecordValues.Look(chronicler, ref activePlatform, "ActivePlatform", null);
 106390        RecordValues.Look(chronicler, ref previousPlatform, "PreviousPlatform", null);
 106391        RecordValues.Look(chronicler, ref holdPlatform, "HoldPlatform", null);
 106392        RecordValues.Look(chronicler, ref movementTransfer, "MovementTransfer", MotionTransfer.None);
 106393        RecordValues.Look(chronicler, ref scoutLocalPoint, "ScoutLocalPoint", Vector3d.Zero);
 106394        RecordValues.Look(chronicler, ref scoutLocalRotation, "ScoutLocalRotation", FixedQuaternion.Identity);
 106395        RecordValues.Look(chronicler, ref platformVelocity, "PlatformVelocity", Vector3d.Zero);
 106396        RecordValues.Look(chronicler, ref framePlatformVelocity, "FramePlatformVelocity", Vector3d.Zero);
 106397        RecordValues.Look(chronicler, ref holdPlatformFrames, "HoldPlatformFrames", 0);
 398
 106399        if (chronicler.Mode == SerializationMode.Loading)
 400        {
 53401            ActivePlatform = NormalizeKinematicPlatform(activePlatform);
 53402            PreviousPlatform = NormalizeKinematicPlatform(previousPlatform);
 53403            HoldPlatform = NormalizeKinematicPlatform(holdPlatform);
 53404            IsNewPlatform = ActivePlatform?.SupportsKinematicMotion == true && isNewPlatform;
 53405            MovementTransfer = ActivePlatform?.SupportsKinematicMotion == true
 53406                ? movementTransfer
 53407                : MotionTransfer.None;
 53408            ScoutLocalPoint = scoutLocalPoint;
 53409            ScoutLocalRotation = scoutLocalRotation;
 53410            PlatformVelocity = ActivePlatform?.SupportsKinematicMotion == true
 53411                ? platformVelocity
 53412                : Vector3d.Zero;
 53413            FramePlatformVelocity = ActivePlatform?.SupportsKinematicMotion == true
 53414                ? framePlatformVelocity
 53415                : Vector3d.Zero;
 53416            HoldPlatformFrames = HoldPlatform?.SupportsKinematicMotion == true
 53417                ? holdPlatformFrames
 53418                : 0;
 53419            _preservePreviousTransformForAttachment = false;
 420
 53421            if (!_isEnabled)
 2422                this.ClearTransientState();
 423        }
 106424    }
 425}