< Summary

Line coverage
100%
Covered lines: 4026
Uncovered lines: 0
Coverable lines: 4026
Total lines: 6586
Line coverage: 100%
Branch coverage
100%
Covered branches: 1202
Total branches: 1202
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
File 1: get_LocalCenterOfMassOffset()100%11100%
File 1: set_LocalCenterOfMassOffset(...)100%66100%
File 1: get_WorldCenterOfMass()100%11100%
File 1: TryGetWorldCenterOfMass(...)100%11100%
File 1: TryGetOffsetFromCenterOfMass(...)100%11100%
File 1: GetCenterOfMassAnchor()100%11100%
File 1: ResetCenterOfMassFromCollider()100%11100%
File 2: get_ContinuousCollisionFrameStart()100%11100%
File 2: get_ContinuousCollisionFrameDisplacement()100%11100%
File 2: get_ContinuousCollisionFrameRotation()100%11100%
File 2: get_ContinuousCollisionFrameEnd()100%11100%
File 2: get_ContinuousCollisionFrameTargetRotation()100%11100%
File 2: get_ContinuousCollisionTrajectoryCount()100%11100%
File 2: GetContinuousCollisionTrajectorySegment(...)100%11100%
File 2: get_HasContinuousCollisionMotion()100%66100%
File 2: ShouldOwnContinuousCollisionMovingPair(...)100%1010100%
File 2: get_HasContinuousCollisionRotationalMotion()100%44100%
File 2: SampleContinuousCollisionPosition(...)100%22100%
File 2: SampleContinuousCollisionRotation(...)100%22100%
File 2: SampleContinuousCollisionLinearVelocity(...)100%22100%
File 2: SampleContinuousCollisionAngularVelocity(...)100%22100%
File 2: ResolveLegacyContinuousCollisionFrameStart()100%11100%
File 2: ResolveLegacyContinuousCollisionFrameDisplacement()100%11100%
File 2: EnsureContinuousCollisionFramePrepared(...)100%66100%
File 2: PrepareMovableContinuousCollisionMotion()100%66100%
File 2: SetContinuousCollisionFrameSegment(...)100%11100%
File 2: AppendContinuousCollisionFrameSegment(...)100%11100%
File 2: AppendContinuousCollisionFrameSegment(...)100%44100%
File 2: CanAppendContinuousCollisionFrameSegment(...)100%44100%
File 2: TryResolveContinuousCollisionMotionBound(...)100%88100%
File 2: ResolveContinuousCollisionTrajectoryBounds(...)100%22100%
File 2: RemoveSupersededContinuousCollisionFrameSegments(...)100%88100%
File 2: ResolveContinuousCollisionSegment(...)100%11100%
File 2: GetContinuousCollisionTrajectorySegmentIndex(...)100%44100%
File 2: GetContinuousCollisionTrajectorySegmentRange(...)100%22100%
File 2: InvalidateContinuousCollisionFrame()100%11100%
File 3: ApplyContinuousCollisionHandoff(...)100%22100%
File 3: ApplyContinuousCollisionHandoff(...)100%22100%
File 3: ApplyContinuousCollisionHandoffState(...)100%22100%
File 3: ApplyContinuousCollisionHandoffStateReserved(...)100%1616100%
File 3: CanApplyContinuousCollisionHandoffState(...)100%22100%
File 3: TryConsumeContinuousCollisionHandoff(...)100%2626100%
File 3: DiscardContinuousCollisionHandoff()100%11100%
File 3: TryResolveContinuousCollision(...)100%11100%
File 3: TryResolveContinuousCollision(...)100%4242100%
File 3: TryApplyContinuousCollisionDynamicResponse(...)100%44100%
File 3: TryApplyContinuousCollisionDynamicResponse(...)100%2020100%
File 3: TryApplyContinuousCollisionMixed3DResponse(...)100%88100%
File 3: ApplyContinuousCollisionSourceResponse(...)100%11100%
File 3: UpdateContinuousCollisionFrameTrajectory(...)100%11100%
File 3: ResolveContinuousCollisionFrameFraction(...)100%11100%
File 3: ResolveContinuousCollisionRestitution(...)100%22100%
File 3: ResolveContinuousCollisionRestitution(...)100%22100%
File 3: RemoveClosingContinuousCollisionVelocity(...)100%44100%
File 4: ShouldUseContinuousCollision(...)100%22100%
File 4: ResolveContinuousCollisionMode()100%1010100%
File 4: ResolveContinuousCollisionProxyRadius()100%22100%
File 4: ResolveMixedContinuousCollisionProxyRadius()100%22100%
File 4: IsValidContinuousCollisionHit(...)100%11100%
File 4: IsClosingContinuousCollisionHit(...)100%11100%
File 4: IsValidContinuousCollisionTarget(...)100%88100%
File 4: IsValidMixedContinuousCollisionHit(...)100%88100%
File 5: TryGetFirstContinuousCollisionHit(...)100%2222100%
File 5: TryGetFirstValidContinuousCollisionHit(...)100%1212100%
File 5: TryGetFirstValidMixedContinuousCollisionHit(...)100%66100%
File 5: TryGetFirstDynamicContinuousCollisionHit(...)100%88100%
File 5: TryGetDynamicRelativeContinuousCollisionHit(...)100%2424100%
File 5: TranslateContactAnchor(...)100%11100%
File 5: TryGetFirstDynamicMixedContinuousCollisionHit(...)100%88100%
File 5: TryGetDynamicMixed3DContinuousCollisionHit(...)100%2828100%
File 5: IsEligibleDynamicContinuousCollisionTarget(...)100%88100%
File 5: IsEligibleDynamicMixed3DTarget(...)100%88100%
File 6: TryResolveKinematicContinuousCollision(...)100%1818100%
File 6: TryGetFirstKinematicStaticContinuousCollisionHit(...)100%1010100%
File 6: TryApplyKinematicDynamicContinuousCollisionPushes(...)100%88100%
File 6: GatherKinematicDynamic2DContinuousCollisionHits(...)100%88100%
File 6: GatherKinematicDynamicMixed3DContinuousCollisionHits(...)100%1212100%
File 6: ApplyKinematicContinuousCollisionHandoff(...)100%44100%
File 6: ApplyKinematicContinuousCollisionHandoff(...)100%66100%
File 7: HasNearbyRotationalContinuousCollisionTarget(...)100%44100%
File 7: IsRotatingContinuousCollisionTarget(...)100%44100%
File 7: ShouldUseRotationalContinuousCollisionArbiter(...)100%2222100%
File 7: TryResolveRotationalContinuousCollision(...)100%11100%
File 7: TryResolveRotationalContinuousCollision(...)100%6464100%
File 7: TryResolveKinematicRotationalContinuousCollision(...)100%11100%
File 7: GatherRotationalContinuousCollisionCandidates(...)100%66100%
File 7: GatherAllRegisteredRotationalContinuousCollisionCandidates()100%88100%
File 7: TryFindEarliestRotationalContinuousCollision(...)100%2222100%
File 7: TryFindEarliestRotationalContinuousCollisionAgainstTarget(...)100%2828100%
File 7: IsRotationalIntervalSeparated(...)100%22100%
File 7: TryGetCirclePairSeparationGap(...)100%44100%
File 7: TryGetCircleSeparationGap(...)100%1212100%
File 7: SampleRotationalContinuousPairPose(...)100%22100%
File 7: IsMovingRotationalContinuousCollisionTarget(...)100%1818100%
File 8: .ctor(...)100%11100%
File 8: SetMixedContinuousCollisionSamplePose(...)100%11100%
File 8: ResolveMixedContinuousCollisionTrajectoryBounds(...)100%11100%
File 8: .ctor(...)100%11100%
File 8: TryFindEarliestMixedRotationalContinuousCollision(...)100%1010100%
File 8: GatherMixedRotationalContinuousCollisionCandidates(...)100%1010100%
File 8: ConsiderMixedRotationalContinuousCollisionCandidate(...)100%66100%
File 8: TryFindEarliestMixedRotationalContinuousCollisionAgainstTarget(...)100%3636100%
File 8: SampleMixedRotationalContinuousPairPose(...)100%22100%
File 8: TryGetExactSphereCircleTranslationalContact(...)100%2626100%
File 8: IsValidMixedRotationalContinuousCollisionTarget(...)100%66100%
File 8: IsMovingMixedRotationalContinuousCollisionTarget(...)100%1010100%
File 8: TryApplyMixedRotationalContinuousCollisionResponse(...)100%5050100%
File 9: TryApplyRotationalContinuousCollisionResponse(...)100%4242100%
File 9: ResolveRotationalFrameFraction(...)100%11100%
File 9: StopRotationalContinuousCollision(...)100%11100%
File 10: .ctor(...)100%11100%
File 10: get_FreezeAxes()100%11100%
File 10: set_FreezeAxes(...)100%22100%
File 10: get_IsPositionFullyFrozen()100%11100%
File 10: get_MotionType()100%11100%
File 10: get_IsDynamic()100%11100%
File 10: get_IsStatic()100%11100%
File 10: get_IsKinematic()100%11100%
File 10: SetMotionType(...)100%22100%
File 10: PrepareMotionTypeTransition(...)100%44100%
File 10: CommitMotionTypeTransition(...)100%11100%
File 10: ApplyLoadedMotionType(...)100%44100%
File 10: PreflightLoadedMotionType(...)100%22100%
File 10: ThrowIfRuntimeRegistrationMissing()100%22100%
File 10: ReconcileMotionTypeRegistration(...)100%11100%
File 10: ClearMotionForMotionTypeTransition()100%11100%
File 10: get_ContinuousCollisionMode()100%11100%
File 10: set_ContinuousCollisionMode(...)100%11100%
File 10: get_IsRotationFullyFrozen()100%11100%
File 10: get_Mass()100%11100%
File 10: set_Mass(...)100%22100%
File 10: get_InverseMass()100%22100%
File 10: get_CanTranslate()100%88100%
File 10: get_CanRotate()100%88100%
File 10: get_HasSolverMobility()100%22100%
File 10: get_EffectiveInverseMass()100%22100%
File 10: get_EffectiveInverseMomentOfInertia()100%22100%
File 10: ProjectLinearMotion(...)100%88100%
File 10: ProjectLinearEndpoint(...)100%44100%
File 10: GetConstrainedInverseMass(...)100%88100%
File 10: get_MomentOfInertia()100%11100%
File 10: get_InverseMomentOfInertia()100%11100%
File 10: get_AngularVelocity()100%11100%
File 10: get_AngularAcceleration()100%11100%
File 10: get_AngularSpeed()100%11100%
File 10: get_Position()100%11100%
File 10: get_Rotation()100%11100%
File 10: get_LinearVelocity()100%11100%
File 10: get_LinearSpeed()100%11100%
File 10: get_GravityScale()100%11100%
File 10: set_GravityScale(...)100%11100%
File 10: get_SleepEnabled()100%11100%
File 10: set_SleepEnabled(...)100%44100%
File 10: get_SleepFrameThreshold()100%11100%
File 10: set_SleepFrameThreshold(...)100%11100%
File 10: get_SleepLinearSpeedThreshold()100%11100%
File 10: set_SleepLinearSpeedThreshold(...)100%11100%
File 10: get_SleepAngularSpeedThreshold()100%11100%
File 10: set_SleepAngularSpeedThreshold(...)100%11100%
File 10: get_IsSleeping()100%11100%
File 10: get_IsAwakeForCollision()100%22100%
File 10: Initialize(...)100%44100%
File 10: GetWorldPoint(...)100%11100%
File 10: TryGetWorldPoint(...)100%22100%
File 10: GetLocalPoint(...)100%11100%
File 10: TryGetLocalPoint(...)100%22100%
File 10: ResetPosition(...)100%44100%
File 10: Sleep()100%22100%
File 10: Wake()100%22100%
File 10: WakeFromCollision()100%22100%
File 10: LateSimulate()100%11100%
File 10: LateSimulate(...)100%1616100%
File 10: UpdateSleepStateAfterPhysicsStep()100%22100%
File 10: OnVisualize()100%22100%
File 10: UpdateKinematicPositionAndRotation(...)100%1212100%
File 10: CanonicalizeRotation(...)100%11100%
File 10: SetHostWorldPose(...)100%11100%
File 10: PublishAuthoritativePose()100%11100%
File 10: Deactivate()100%22100%
File 11: .cctor()100%11100%
File 11: .ctor(...)100%11100%
File 11: get_GroundingMode()100%11100%
File 11: get_GroundProbeMode()100%11100%
File 11: set_GroundProbeMode(...)100%11100%
File 11: get_UseGravityDerivedGroundUpDirection()100%11100%
File 11: set_UseGravityDerivedGroundUpDirection(...)100%11100%
File 11: get_GroundUpDirection()100%11100%
File 11: set_GroundUpDirection(...)100%11100%
File 11: get_GroundProbeRadius()100%11100%
File 11: set_GroundProbeRadius(...)100%11100%
File 11: get_IsGrounded()100%11100%
File 11: set_IsGrounded(...)100%44100%
File 11: get_WasGrounded()100%11100%
File 11: get_GroundNormal()100%11100%
File 11: get_HasGroundPoint()100%11100%
File 11: get_GroundPoint()100%22100%
File 11: TryGetGroundPoint(...)100%22100%
File 11: get_LastGroundedPosition()100%11100%
File 11: UseManualGrounding(...)100%22100%
File 11: UseAutomaticGrounding(...)100%44100%
File 11: SetManualGrounding(...)100%11100%
File 11: ClearManualGrounding()100%11100%
File 11: CheckGround()100%22100%
File 11: BeginAutomaticGroundingRefresh()100%22100%
File 11: TryAcceptContactGroundCandidate(...)100%1818100%
File 11: CompleteAutomaticGroundingRefresh()100%1010100%
File 11: CheckGroundForSimulation()100%11100%
File 11: RemoveIntoGroundComponent(...)100%66100%
File 11: get_CanUseAutomaticGrounding()100%44100%
File 11: CheckGround(...)100%2626100%
File 11: TryFindGroundHit(...)100%44100%
File 11: TryFindGroundHitWithRay(...)100%44100%
File 11: TryFindGroundHitWithSweptCircle(...)100%44100%
File 11: IsValidGroundHit(...)100%44100%
File 11: IsValidGroundCollider(...)100%1010100%
File 11: ResolveGroundProbeMode()100%44100%
File 11: ResolveGroundProbeRadius()100%1212100%
File 11: ResolveGroundUpDirection()100%44100%
File 11: CaptureGroundedTransitionState()100%11100%
File 11: CaptureGroundedStepState()100%22100%
File 11: CompleteGroundedStepState()100%11100%
File 11: ClearGrounding()100%11100%
File 11: ClearGroundingForAutomaticRefresh()100%22100%
File 11: SetGroundingState(...)100%22100%
File 11: ResetGroundingForInitialize(...)100%11100%
File 11: RevalidateAutomaticGroundingWrite(...)100%1414100%
File 11: ClearGroundContactCandidate()100%11100%
File 11: CompareGroundContactCandidate(...)100%1616100%
File 12: AddForce(...)100%22100%
File 12: AddTorque(...)100%22100%
File 12: AddLinearImpulse(...)100%22100%
File 12: AddAngularImpulse(...)100%22100%
File 12: SetPosition(...)100%22100%
File 12: SetRotation(...)100%22100%
File 12: RefreshStaticColliderAfterExplicitPoseChange()100%66100%
File 12: PreflightStaticPoseChange()100%44100%
File 12: RefreshPartitionMobility()100%66100%
File 12: ApplyCollisionLinearVelocityDelta(...)100%44100%
File 12: ApplyCollisionAngularVelocityDelta(...)100%44100%
File 12: CanApplyCollisionVelocityDeltas(...)100%11100%
File 12: ApplyCollisionPositionCorrection(...)100%44100%
File 12: get_CanSleep()100%22100%
File 12: ApplyFreezeConstraintsToMotion()100%22100%
File 12: UpdateSleepState()100%1010100%
File 12: RefreshLinearSpeed()100%22100%
File 12: RefreshAngularSpeed()100%22100%
File 12: RefreshMassPropertiesFromColliderShape()100%66100%
File 13: ContributeReplayHash(...)100%88100%
File 13: WriteContinuousCollisionTrajectory(...)100%22100%
File 14: RecordData(...)100%1616100%
File 14: ApplyLoadedState()100%11100%
File 14: ApplyLoadedFreezeConstraintsToMotion()100%22100%

File(s)

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/2D/SolidBody2D.CenterOfMass.cs

#LineLine coverage
 1//=======================================================================
 2// SolidBody2D.CenterOfMass.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2026-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using FixedMathSharp;
 9using Gravitas.CollisionHandling;
 10using SwiftCollections.Diagnostics;
 11using System;
 12using System.Runtime.CompilerServices;
 13
 14namespace Gravitas;
 15
 16public sealed partial class SolidBody2D
 17{
 18    /// <summary>
 19    /// Gets or sets the authoritative body-local center-of-mass offset in the X/Z simulation plane.
 20    /// </summary>
 21    public Vector2d LocalCenterOfMassOffset
 22    {
 62023        get => _localCenterOfMassOffset;
 24        set
 25        {
 2026            if (_localCenterOfMassOffset == value && _centerOfMassOffsetExplicit)
 127                return;
 28
 1929            _localCenterOfMassOffset = value;
 1930            _centerOfMassOffsetExplicit = true;
 1931            if (!Active)
 132                return;
 33
 1834            Wake();
 1835            RefreshMassPropertiesFromColliderShape();
 1836        }
 37    }
 38
 39    /// <summary>
 40    /// Gets the authoritative world-space center of mass in the X/Z simulation plane.
 41    /// </summary>
 42    /// <exception cref="InvalidOperationException">
 43    /// The center lies outside the representable coordinate domain. Use
 44    /// <see cref="TryGetWorldCenterOfMass(out Vector2d)"/> when that is possible.
 45    /// </exception>
 46    public Vector2d WorldCenterOfMass
 47    {
 48        get
 49        {
 1650            bool succeeded = TryGetWorldCenterOfMass(out Vector2d center);
 1651            SwiftThrowHelper.ThrowIfTrue(
 1652                !succeeded,
 1653                nameof(WorldCenterOfMass),
 1654                "The world center of mass is outside the representable coordinate domain.");
 1555            return center;
 56        }
 57    }
 58
 59    /// <summary>
 60    /// Attempts to materialize the authoritative world-space center of mass
 61    /// without saturation.
 62    /// </summary>
 63    public bool TryGetWorldCenterOfMass(out Vector2d center)
 1964        => Vector2d.TryTransformPoint(
 1965            _position,
 1966            _localCenterOfMassOffset,
 1967            _rotation,
 1968            out center);
 69
 70    internal bool TryGetOffsetFromCenterOfMass(
 71        ContactAnchor2D anchor,
 72        out Vector2d offset)
 305373        => anchor.TryGetOffsetFrom(GetCenterOfMassAnchor(), out offset);
 74
 75    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 76    internal ContactAnchor2D GetCenterOfMassAnchor() =>
 500877        new(
 500878            _position,
 500879            _rotation,
 500880            _localCenterOfMassOffset);
 81
 82    /// <summary>
 83    /// Clears an explicit center-of-mass override and derives the offset from the bound collider again.
 84    /// </summary>
 85    public void ResetCenterOfMassFromCollider()
 86    {
 187        _centerOfMassOffsetExplicit = false;
 188        RefreshMassPropertiesFromColliderShape();
 189    }
 90}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/2D/SolidBody2D.ContinuousCollision.cs

#LineLine coverage
 1//=======================================================================
 2// SolidBody2D.ContinuousCollision.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2026-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using FixedMathSharp;
 9using Gravitas.Colliders;
 10using Gravitas.CollisionHandling;
 11using System.Runtime.CompilerServices;
 12
 13namespace Gravitas;
 14
 15public sealed partial class SolidBody2D
 16{
 17    private LSCollider? _continuousCollisionHandoffIgnoredCollider3D;
 18    private LSCollider2D? _continuousCollisionHandoffIgnoredCollider2D;
 19
 20    internal Vector2d ContinuousCollisionFrameStart
 21    {
 22        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 244523        get => ResolveLegacyContinuousCollisionFrameStart();
 24    }
 25
 26    internal Vector2d ContinuousCollisionFrameDisplacement
 27    {
 28        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 429        get => ResolveLegacyContinuousCollisionFrameDisplacement();
 30    }
 31
 32    internal Fixed64 ContinuousCollisionFrameRotation
 33    {
 34        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 235        get => _continuousCollisionTrajectory[0].StartRotation;
 36    }
 37
 38    internal Vector2d ContinuousCollisionFrameEnd
 39    {
 40        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 261141        get => _continuousCollisionTrajectory[_continuousCollisionTrajectory.Count - 1].EndPosition;
 42    }
 43
 44    internal Fixed64 ContinuousCollisionFrameTargetRotation
 45    {
 46        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 47        get
 48        {
 16849            ContinuousCollisionMotionSegment2D segment =
 16850                _continuousCollisionTrajectory[_continuousCollisionTrajectory.Count - 1];
 16851            return CanonicalizeRotation(segment.StartRotation + segment.AngularDelta);
 52        }
 53    }
 54
 55    internal int ContinuousCollisionTrajectoryCount =>
 69056        _continuousCollisionTrajectory.Count;
 57
 58    internal ContinuousCollisionMotionSegment2D GetContinuousCollisionTrajectorySegment(
 59        int index) =>
 57260        _continuousCollisionTrajectory[index];
 61
 62    internal bool HasContinuousCollisionMotion
 63    {
 64        get
 65        {
 52866            for (int i = 0; i < _continuousCollisionTrajectory.Count; i++)
 67            {
 23368                ContinuousCollisionMotionSegment2D segment =
 23369                    _continuousCollisionTrajectory[i];
 23370                if (segment.Displacement.MagnitudeSquared > Fixed64.Epsilon
 23371                    || segment.AngularDistance > Fixed64.Epsilon)
 72                {
 20273                    return true;
 74                }
 75            }
 76
 3177            return false;
 78        }
 79    }
 80
 81    internal bool ShouldOwnContinuousCollisionMovingPair(bool otherHasRotationalMotion)
 82    {
 74383        if (!IsKinematic || !ShouldUseContinuousCollision(out ContinuousCollisionMode mode))
 65784            return false;
 85
 8686        EnsureContinuousCollisionFramePrepared(Context.LateSimulateToken);
 8687        if (!HasContinuousCollisionMotion)
 188            return false;
 8589        if (mode == ContinuousCollisionMode.Continuous)
 8290            return true;
 91
 392        Fixed64 proxyRadius = ResolveContinuousCollisionProxyRadius();
 393        Vector2d displacement = ContinuousCollisionFrameDisplacement;
 94
 395        Fixed64 angularDistance = Fixed64.Zero;
 1296        for (int i = 0; i < _continuousCollisionTrajectory.Count; i++)
 397            angularDistance += _continuousCollisionTrajectory[i].AngularDistance;
 98
 399        bool requiresTargetSampling = angularDistance <= Fixed64.Epsilon
 3100            && otherHasRotationalMotion;
 3101        return requiresTargetSampling
 3102            | angularDistance * proxyRadius > proxyRadius
 3103            | !ContinuousCollisionMath.IsWithinProxyRadius(
 3104                displacement,
 3105                displacement.MagnitudeSquared,
 3106                proxyRadius);
 107    }
 108
 109    internal bool HasContinuousCollisionRotationalMotion
 110    {
 111        get
 112        {
 3826113            for (int i = 0; i < _continuousCollisionTrajectory.Count; i++)
 114            {
 1195115                if (_continuousCollisionTrajectory[i].AngularDistance > Fixed64.Epsilon)
 102116                    return true;
 117            }
 118
 718119            return false;
 120        }
 121    }
 122
 123    internal Vector2d SampleContinuousCollisionPosition(Fixed64 frameFraction) =>
 3300124        _continuousCollisionTrajectory.Count == 0
 3300125            ? _position
 3300126            : ResolveContinuousCollisionSegment(frameFraction).SamplePosition(frameFraction);
 127
 128    internal Fixed64 SampleContinuousCollisionRotation(Fixed64 frameFraction) =>
 3878129        _continuousCollisionTrajectory.Count == 0
 3878130            ? _rotation
 3878131            : CanonicalizeRotation(
 3878132                ResolveContinuousCollisionSegment(frameFraction).SampleRotation(frameFraction));
 133
 134    internal Vector2d SampleContinuousCollisionLinearVelocity(Fixed64 frameFraction)
 135    {
 239136        if (_continuousCollisionTrajectory.Count == 0)
 12137            return ProjectLinearMotion(_linearVelocity);
 138
 227139        ContinuousCollisionMotionSegment2D segment =
 227140            ResolveContinuousCollisionSegment(frameFraction);
 227141        Fixed64 duration = (segment.EndFraction - segment.StartFraction) * Context.DeltaTime;
 227142        return ProjectLinearMotion(segment.Displacement / duration);
 143    }
 144
 145    internal Fixed64 SampleContinuousCollisionAngularVelocity(Fixed64 frameFraction) =>
 221146        _continuousCollisionTrajectory.Count == 0
 221147            ? _angularVelocity
 221148            : ResolveContinuousCollisionSegment(frameFraction).AngularVelocity;
 149
 150    private Vector2d ResolveLegacyContinuousCollisionFrameStart()
 151    {
 2445152        ContinuousCollisionMotionSegment2D segment =
 2445153            _continuousCollisionTrajectory[_continuousCollisionTrajectory.Count - 1];
 2445154        Vector2d frameDisplacement = ResolveLegacyContinuousCollisionFrameDisplacement();
 2445155        return segment.StartPosition - frameDisplacement * segment.StartFraction;
 156    }
 157
 158    private Vector2d ResolveLegacyContinuousCollisionFrameDisplacement()
 159    {
 2449160        ContinuousCollisionMotionSegment2D segment =
 2449161            _continuousCollisionTrajectory[_continuousCollisionTrajectory.Count - 1];
 2449162        Fixed64 remainingFraction = Fixed64.One - segment.StartFraction;
 2449163        return segment.Displacement / remainingFraction;
 164    }
 165
 166    internal void EnsureContinuousCollisionFramePrepared(int token)
 167    {
 5277168        if (_continuousCollisionFrameToken == token)
 2814169            return;
 170
 2463171        Vector2d startPosition = _position;
 2463172        Fixed64 startRotation = _rotation;
 173        Vector2d displacement;
 174        Vector2d endPosition;
 175        Fixed64 angularDelta;
 176        Fixed64 angularVelocity;
 2463177        if (IsKinematic)
 178        {
 188179            Vector2d targetPosition = ProjectLinearEndpoint(
 188180                startPosition,
 188181                Agent.Transform.WorldPositionXZ);
 188182            endPosition = targetPosition;
 188183            displacement = ShouldUseContinuousCollision(out _)
 188184                ? ContinuousCollisionSweepRange.ValidateEndpoint(
 188185                    startPosition,
 188186                    targetPosition,
 188187                    out _)
 188188                : targetPosition - startPosition;
 189
 187190            Fixed64 targetRotation = CanonicalizeRotation(Agent.Transform.WorldRotationXZRadians);
 187191            angularDelta = CanonicalizeRotation(targetRotation - startRotation);
 187192            angularVelocity = angularDelta / Context.DeltaTime;
 193        }
 194        else
 195        {
 2275196            PrepareMovableContinuousCollisionMotion();
 2275197            displacement = ProjectLinearMotion(_linearVelocity) * Context.DeltaTime;
 2275198            endPosition = startPosition + displacement;
 2275199            angularVelocity = _angularVelocity;
 2275200            angularDelta = angularVelocity * Context.DeltaTime;
 201        }
 202
 2462203        _continuousCollisionFrameToken = token;
 2462204        SetContinuousCollisionFrameSegment(new ContinuousCollisionMotionSegment2D(
 2462205            Fixed64.Zero,
 2462206            Fixed64.One,
 2462207            startPosition,
 2462208            endPosition,
 2462209            displacement,
 2462210            startRotation,
 2462211            angularDelta,
 2462212            angularVelocity));
 2462213    }
 214
 215    private void PrepareMovableContinuousCollisionMotion()
 216    {
 2275217        if (_isSleeping)
 131218            return;
 219
 2144220        Fixed64 deltaTime = Context.DeltaTime;
 2144221        if (CanTranslate)
 222        {
 2128223            _linearAccelerationStore = RemoveIntoGroundComponent(
 2128224                _deltaAcceleration + Gravity * _gravityScale);
 2128225            _deltaAcceleration = Vector2d.Zero;
 2128226            _linearVelocity += ProjectLinearMotion(_linearAccelerationStore * deltaTime);
 2128227            _linearVelocity = RemoveIntoGroundComponent(_linearVelocity);
 2128228            _linearAccelerationStore = Vector2d.Zero;
 2128229            RefreshLinearSpeed();
 230        }
 231        else
 232        {
 16233            _linearAccelerationStore = Vector2d.Zero;
 16234            _deltaAcceleration = Vector2d.Zero;
 235        }
 236
 2144237        if (CanRotate)
 238        {
 2123239            _angularAccelerationStore = _deltaAngularAcceleration;
 2123240            _deltaAngularAcceleration = Fixed64.Zero;
 2123241            _angularVelocity += _angularAccelerationStore * deltaTime;
 2123242            RefreshAngularSpeed();
 243        }
 244        else
 245        {
 21246            _angularAccelerationStore = Fixed64.Zero;
 21247            _deltaAngularAcceleration = Fixed64.Zero;
 248        }
 21249    }
 250
 251    private void SetContinuousCollisionFrameSegment(ContinuousCollisionMotionSegment2D segment)
 252    {
 2462253        _continuousCollisionTrajectory.FastClear();
 2462254        _continuousCollisionTrajectory.Add(segment);
 2462255    }
 256
 257    private void AppendContinuousCollisionFrameSegment(
 258        Vector2d positionAtElapsedTime,
 259        Vector2d velocity,
 260        Fixed64 elapsedTime)
 261    {
 152262        Fixed64 startFraction = FixedMath.Clamp01(elapsedTime / Context.DeltaTime);
 152263        Fixed64 rotationAtElapsedTime = SampleContinuousCollisionRotation(startFraction);
 152264        AppendContinuousCollisionFrameSegment(
 152265            positionAtElapsedTime,
 152266            rotationAtElapsedTime,
 152267            velocity,
 152268            _angularVelocity,
 152269            elapsedTime);
 152270    }
 271
 272    private void AppendContinuousCollisionFrameSegment(
 273        Vector2d positionAtElapsedTime,
 274        Fixed64 rotationAtElapsedTime,
 275        Vector2d velocity,
 276        Fixed64 angularVelocity,
 277        Fixed64 elapsedTime)
 278    {
 395279        Fixed64 deltaTime = Context.DeltaTime;
 395280        Fixed64 startFraction = FixedMath.Clamp01(elapsedTime / deltaTime);
 395281        Fixed64 remainingTime = FixedMath.Max(Fixed64.Zero, deltaTime - elapsedTime);
 395282        Vector2d displacement = ProjectLinearMotion(velocity) * remainingTime;
 395283        _continuousCollisionFrameToken = Context.LateSimulateToken;
 395284        RemoveSupersededContinuousCollisionFrameSegments(
 395285            startFraction,
 395286            positionAtElapsedTime);
 395287        if (startFraction >= Fixed64.One && _continuousCollisionTrajectory.Count > 0)
 3288            return;
 289
 392290        _continuousCollisionTrajectory.Add(new ContinuousCollisionMotionSegment2D(
 392291            startFraction,
 392292            Fixed64.One,
 392293            positionAtElapsedTime,
 392294            positionAtElapsedTime + displacement,
 392295            displacement,
 392296            rotationAtElapsedTime,
 392297            angularVelocity * remainingTime,
 392298            angularVelocity));
 392299    }
 300
 301    internal bool CanAppendContinuousCollisionFrameSegment(Fixed64 elapsedTime)
 302    {
 469303        Fixed64 startFraction = FixedMath.Clamp01(elapsedTime / Context.DeltaTime);
 469304        int retainedCount = _continuousCollisionTrajectory.Count;
 524305        while (retainedCount > 0
 524306            && _continuousCollisionTrajectory[retainedCount - 1].StartFraction >= startFraction)
 307        {
 55308            retainedCount--;
 309        }
 310
 469311        return retainedCount <= Context.Settings.ContinuousCollisionMaxToiIterations;
 312    }
 313
 314    internal bool TryResolveContinuousCollisionMotionBound(
 315        Fixed64 lowerFraction,
 316        Fixed64 upperFraction,
 317        Fixed64 pivotRadius,
 318        out Fixed64 motionBound)
 319    {
 3132320        motionBound = Fixed64.Zero;
 12636321        for (int i = 0; i < _continuousCollisionTrajectory.Count; i++)
 322        {
 3187323            ContinuousCollisionMotionSegment2D segment = _continuousCollisionTrajectory[i];
 3187324            Fixed64 lower = FixedMath.Max(lowerFraction, segment.StartFraction);
 3187325            Fixed64 upper = FixedMath.Min(upperFraction, segment.EndFraction);
 3187326            if (upper <= lower)
 327                continue;
 328
 3136329            if (!segment.TryResolveMotionBound(lower, upper, pivotRadius, out Fixed64 segmentBound)
 3136330                || !Fixed64.TryAdd(motionBound, segmentBound, out motionBound))
 331            {
 1332                motionBound = default;
 1333                return false;
 334            }
 335        }
 336
 3131337        return true;
 338    }
 339
 340    internal DynamicCcdPlanarBounds ResolveContinuousCollisionTrajectoryBounds(Fixed64 radius)
 341    {
 806342        ContinuousCollisionMotionSegment2D first = _continuousCollisionTrajectory[0];
 806343        Fixed64 minX = FixedMath.Min(first.StartPosition.X, first.EndPosition.X);
 806344        Fixed64 minZ = FixedMath.Min(first.StartPosition.Y, first.EndPosition.Y);
 806345        Fixed64 maxX = FixedMath.Max(first.StartPosition.X, first.EndPosition.X);
 806346        Fixed64 maxZ = FixedMath.Max(first.StartPosition.Y, first.EndPosition.Y);
 2486347        for (int i = 1; i < _continuousCollisionTrajectory.Count; i++)
 348        {
 437349            ContinuousCollisionMotionSegment2D segment = _continuousCollisionTrajectory[i];
 437350            minX = FixedMath.Min(minX, FixedMath.Min(segment.StartPosition.X, segment.EndPosition.X));
 437351            minZ = FixedMath.Min(minZ, FixedMath.Min(segment.StartPosition.Y, segment.EndPosition.Y));
 437352            maxX = FixedMath.Max(maxX, FixedMath.Max(segment.StartPosition.X, segment.EndPosition.X));
 437353            maxZ = FixedMath.Max(maxZ, FixedMath.Max(segment.StartPosition.Y, segment.EndPosition.Y));
 354        }
 355
 806356        return new DynamicCcdPlanarBounds(
 806357            minX - radius,
 806358            minZ - radius,
 806359            maxX + radius,
 806360            maxZ + radius);
 361    }
 362
 363    private void RemoveSupersededContinuousCollisionFrameSegments(
 364        Fixed64 startFraction,
 365        Vector2d positionAtElapsedTime)
 366    {
 442367        while (_continuousCollisionTrajectory.Count > 0
 442368            && _continuousCollisionTrajectory[_continuousCollisionTrajectory.Count - 1]
 442369                .StartFraction >= startFraction)
 370        {
 47371            _continuousCollisionTrajectory.RemoveAt(
 47372                _continuousCollisionTrajectory.Count - 1);
 373        }
 374
 395375        if (_continuousCollisionTrajectory.Count == 0)
 29376            return;
 377
 366378        int predecessorIndex = _continuousCollisionTrajectory.Count - 1;
 366379        ContinuousCollisionMotionSegment2D predecessor =
 366380            _continuousCollisionTrajectory[predecessorIndex];
 366381        if (predecessor.EndFraction <= startFraction)
 20382            return;
 383
 346384        Fixed64 localFraction = (startFraction - predecessor.StartFraction)
 346385            / (predecessor.EndFraction - predecessor.StartFraction);
 346386        _continuousCollisionTrajectory[predecessorIndex] =
 346387            new ContinuousCollisionMotionSegment2D(
 346388                predecessor.StartFraction,
 346389                startFraction,
 346390                predecessor.StartPosition,
 346391                positionAtElapsedTime,
 346392                positionAtElapsedTime - predecessor.StartPosition,
 346393                predecessor.StartRotation,
 346394                predecessor.AngularDelta * localFraction,
 346395                predecessor.AngularVelocity);
 346396    }
 397
 398    private ContinuousCollisionMotionSegment2D ResolveContinuousCollisionSegment(
 399        Fixed64 frameFraction) =>
 7600400        _continuousCollisionTrajectory[
 7600401            GetContinuousCollisionTrajectorySegmentIndex(frameFraction)];
 402
 403    internal int GetContinuousCollisionTrajectorySegmentIndex(
 404        Fixed64 frameFraction)
 405    {
 15820406        for (int i = _continuousCollisionTrajectory.Count - 1; i > 0; i--)
 407        {
 522408            ContinuousCollisionMotionSegment2D segment =
 522409                _continuousCollisionTrajectory[i];
 522410            if (frameFraction >= segment.StartFraction)
 232411                return i;
 412        }
 413
 7388414        return 0;
 415    }
 416
 417    internal void GetContinuousCollisionTrajectorySegmentRange(
 418        Fixed64 startFraction,
 419        out int startIndex,
 420        out int endExclusive)
 421    {
 330422        startIndex = startFraction <= Fixed64.Zero
 330423            ? 0
 330424            : GetContinuousCollisionTrajectorySegmentIndex(startFraction);
 330425        endExclusive = _continuousCollisionTrajectory.Count;
 330426    }
 427
 428    private void InvalidateContinuousCollisionFrame()
 429    {
 4999430        _continuousCollisionFrameToken = int.MinValue;
 4999431        _continuousCollisionTrajectory.FastClear();
 4999432    }
 433}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/2D/SolidBody2D.ContinuousCollision.Dynamic.cs

#LineLine coverage
 1//=======================================================================
 2// SolidBody2D.ContinuousCollision.Dynamic.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2026-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using FixedMathSharp;
 9using Gravitas.Colliders;
 10using Gravitas.CollisionHandling;
 11using Gravitas.Materials;
 12using System.Runtime.CompilerServices;
 13
 14namespace Gravitas;
 15
 16public sealed partial class SolidBody2D
 17{
 18    internal bool ApplyContinuousCollisionHandoff(
 19        Vector2d positionAtImpact,
 20        Vector2d velocityDelta,
 21        Fixed64 remainingTime,
 22        LSCollider? ignoredCollider3D = null,
 23        LSCollider2D? ignoredCollider2D = null)
 24    {
 4925        Fixed64 elapsedTime = Context.DeltaTime - remainingTime;
 4926        Fixed64 frameFraction = ResolveContinuousCollisionFrameFraction(elapsedTime);
 4927        Fixed64 rotationAtImpact = _continuousCollisionTrajectory.Count == 0
 4928            ? _rotation
 4929            : SampleContinuousCollisionRotation(frameFraction);
 4930        return ApplyContinuousCollisionHandoff(
 4931            positionAtImpact,
 4932            rotationAtImpact,
 4933            velocityDelta,
 4934            Fixed64.Zero,
 4935            remainingTime,
 4936            ignoredCollider3D,
 4937            ignoredCollider2D);
 38    }
 39
 40    internal bool ApplyContinuousCollisionHandoff(
 41        Vector2d positionAtImpact,
 42        Fixed64 rotationAtImpact,
 43        Vector2d linearVelocityDelta,
 44        Fixed64 angularVelocityDelta,
 45        Fixed64 remainingTime,
 46        LSCollider? ignoredCollider3D = null,
 47        LSCollider2D? ignoredCollider2D = null)
 48    {
 5249        Vector2d projectedLinearDelta = ProjectLinearMotion(linearVelocityDelta);
 5250        bool linearVelocityResolved = Vector2d.TryAdd(
 5251            _linearVelocity,
 5252            projectedLinearDelta,
 5253            out Vector2d postLinearVelocity);
 5254        bool angularVelocityResolved = Fixed64.TryAdd(
 5255            _angularVelocity,
 5256            angularVelocityDelta,
 5257            out Fixed64 postAngularVelocity);
 5258        if (!(linearVelocityResolved & angularVelocityResolved))
 59        {
 160            return false;
 61        }
 62
 5163        return ApplyContinuousCollisionHandoffState(
 5164            positionAtImpact,
 5165            rotationAtImpact,
 5166            postLinearVelocity,
 5167            postAngularVelocity,
 5168            remainingTime,
 5169            ignoredCollider3D,
 5170            ignoredCollider2D);
 71    }
 72
 73    internal bool ApplyContinuousCollisionHandoffState(
 74        Vector2d positionAtImpact,
 75        Fixed64 rotationAtImpact,
 76        Vector2d postLinearVelocity,
 77        Fixed64 postAngularVelocity,
 78        Fixed64 remainingTime,
 79        LSCollider? ignoredCollider3D = null,
 80        LSCollider2D? ignoredCollider2D = null)
 81    {
 10082        if (!CanApplyContinuousCollisionHandoffState(
 10083                positionAtImpact,
 10084                rotationAtImpact,
 10085                remainingTime,
 10086                out Vector2d resolvedPosition))
 87        {
 388            return false;
 89        }
 90
 9791        _ = Context.Physics2D.TryReserveContinuousCollisionCandidateRefresh(this);
 92
 9793        ApplyContinuousCollisionHandoffStateReserved(
 9794            resolvedPosition,
 9795            rotationAtImpact,
 9796            postLinearVelocity,
 9797            postAngularVelocity,
 9798            remainingTime,
 9799            ignoredCollider3D,
 97100            ignoredCollider2D);
 97101        return true;
 102    }
 103
 104    internal void ApplyContinuousCollisionHandoffStateReserved(
 105        Vector2d resolvedPosition,
 106        Fixed64 rotationAtImpact,
 107        Vector2d postLinearVelocity,
 108        Fixed64 postAngularVelocity,
 109        Fixed64 remainingTime,
 110        LSCollider? ignoredCollider3D = null,
 111        LSCollider2D? ignoredCollider2D = null)
 112    {
 201113        _position = resolvedPosition;
 201114        _rotation = CanonicalizeRotation(rotationAtImpact);
 201115        Collider.PublishPreparedBodyPose();
 201116        postLinearVelocity = ProjectLinearMotion(postLinearVelocity);
 201117        postAngularVelocity = CanRotate ? postAngularVelocity : Fixed64.Zero;
 201118        if (_linearVelocity != postLinearVelocity || _angularVelocity != postAngularVelocity)
 183119            WakeFromCollision();
 201120        _linearVelocity = postLinearVelocity;
 201121        _angularVelocity = postAngularVelocity;
 201122        RefreshLinearSpeed();
 201123        RefreshAngularSpeed();
 201124        bool hasRemainingMotion = remainingTime > Fixed64.Epsilon
 201125            && (_linearVelocity.MagnitudeSquared > Fixed64.Epsilon
 201126                || _angularVelocity.Abs() > Fixed64.Epsilon);
 201127        AppendContinuousCollisionFrameSegment(
 201128            resolvedPosition,
 201129            _rotation,
 201130            hasRemainingMotion ? _linearVelocity : Vector2d.Zero,
 201131            hasRemainingMotion ? _angularVelocity : Fixed64.Zero,
 201132            Context.DeltaTime - remainingTime);
 201133        Context.Physics2D.RefreshContinuousCollisionCandidate(this);
 201134        if (!hasRemainingMotion)
 135        {
 26136            DiscardContinuousCollisionHandoff();
 26137            return;
 138        }
 139
 175140        _continuousCollisionHandoffToken = Context.LateSimulateToken;
 175141        _continuousCollisionHandoffRemainingTime = remainingTime;
 175142        _continuousCollisionHandoffIgnoredCollider3D = ignoredCollider3D;
 175143        _continuousCollisionHandoffIgnoredCollider2D = ignoredCollider2D;
 175144        _continuousCollisionHandoffPending = true;
 175145        Context.Physics2D.QueueContinuousCollisionHandoff(this);
 175146    }
 147
 148    internal bool CanApplyContinuousCollisionHandoffState(
 149        Vector2d positionAtImpact,
 150        Fixed64 rotationAtImpact,
 151        Fixed64 remainingTime,
 152        out Vector2d resolvedPosition)
 153    {
 207154        Fixed64 elapsedTime = Context.DeltaTime - remainingTime;
 207155        bool hasMobility = CanTranslate | CanRotate;
 207156        bool hasTrajectoryCapacity = CanAppendContinuousCollisionFrameSegment(elapsedTime);
 207157        bool positionDeltaResolved = Vector2d.TrySubtract(
 207158            positionAtImpact,
 207159            _position,
 207160            out Vector2d positionDelta);
 207161        bool positionResolved = Vector2d.TryAdd(
 207162            _position,
 207163            ProjectLinearMotion(positionDelta),
 207164            out resolvedPosition);
 207165        if (!(hasMobility
 207166                & hasTrajectoryCapacity
 207167                & positionDeltaResolved
 207168                & positionResolved))
 169        {
 5170            return false;
 171        }
 172
 202173        return Collider.TryPrepareBodyPose(
 202174            resolvedPosition,
 202175            CanonicalizeRotation(rotationAtImpact));
 176    }
 177
 178    internal bool TryConsumeContinuousCollisionHandoff(bool updateSleepState, bool updateColliderState)
 179    {
 2379180        if (!_continuousCollisionHandoffPending || _continuousCollisionHandoffToken != Context.LateSimulateToken)
 2307181            return false;
 182
 72183        Fixed64 remainingTime = _continuousCollisionHandoffRemainingTime;
 72184        _continuousCollisionHandoffPending = false;
 72185        _continuousCollisionHandoffRemainingTime = Fixed64.Zero;
 72186        bool hasLinearMotion = CanTranslate
 72187            && _linearVelocity.MagnitudeSquared > Fixed64.Epsilon;
 72188        bool hasAngularMotion = CanRotate
 72189            && _angularVelocity.Abs() > Fixed64.Epsilon;
 72190        if (!hasLinearMotion && !hasAngularMotion)
 191        {
 2192            _continuousCollisionHandoffIgnoredCollider3D = null;
 2193            _continuousCollisionHandoffIgnoredCollider2D = null;
 2194            if (updateColliderState)
 1195                Collider.Rebuild();
 2196            return true;
 197        }
 198
 70199        Vector2d startPosition = _position;
 70200        Vector2d proposedPosition = hasLinearMotion
 70201            ? startPosition + _linearVelocity * remainingTime
 70202            : startPosition;
 70203        Fixed64 startRotation = _rotation;
 70204        Fixed64 proposedRotation = hasAngularMotion
 70205            ? startRotation + _angularVelocity * remainingTime
 70206            : startRotation;
 70207        Fixed64 elapsedTime = FixedMath.Max(Fixed64.Zero, Context.DeltaTime - remainingTime);
 208        try
 209        {
 70210            if (ShouldUseRotationalContinuousCollisionArbiter(
 70211                    startPosition,
 70212                    proposedPosition,
 70213                    startRotation,
 70214                    proposedRotation,
 70215                    forceContinuous: true))
 216            {
 3217                TryResolveRotationalContinuousCollision(
 3218                    startPosition,
 3219                    ref proposedPosition,
 3220                    startRotation,
 3221                    ref proposedRotation,
 3222                    remainingTime,
 3223                    elapsedTime,
 3224                    forceContinuous: true);
 225            }
 67226            else if (hasLinearMotion)
 227            {
 67228                TryResolveContinuousCollision(
 67229                    startPosition,
 67230                    ref proposedPosition,
 67231                    remainingTime,
 67232                    elapsedTime,
 67233                    forceContinuous: true);
 234            }
 66235        }
 236        finally
 237        {
 70238            _continuousCollisionHandoffIgnoredCollider3D = null;
 70239            _continuousCollisionHandoffIgnoredCollider2D = null;
 70240        }
 69241        _position = proposedPosition;
 69242        _rotation = CanonicalizeRotation(proposedRotation);
 69243        if (updateColliderState)
 1244            Collider.Rebuild();
 245        else
 68246            Collider.RebuildRuntimeShapeOnly();
 247
 69248        if (updateSleepState)
 1249            UpdateSleepState();
 250
 69251        return true;
 252    }
 253
 254    internal void DiscardContinuousCollisionHandoff()
 255    {
 339256        _continuousCollisionHandoffPending = false;
 339257        _continuousCollisionHandoffRemainingTime = Fixed64.Zero;
 339258        _continuousCollisionHandoffIgnoredCollider3D = null;
 339259        _continuousCollisionHandoffIgnoredCollider2D = null;
 339260    }
 261
 262    private bool TryResolveContinuousCollision(Vector2d startPosition, ref Vector2d proposedPosition) =>
 1897263        TryResolveContinuousCollision(
 1897264            startPosition,
 1897265            ref proposedPosition,
 1897266            Context.DeltaTime,
 1897267            Fixed64.Zero,
 1897268            forceContinuous: false);
 269
 270    private bool TryResolveContinuousCollision(
 271        Vector2d startPosition,
 272        ref Vector2d proposedPosition,
 273        Fixed64 initialRemainingTime,
 274        Fixed64 initialElapsedTime,
 275        bool forceContinuous)
 276    {
 1964277        LastContinuousCollisionToiIterationCount = 0;
 1964278        LastContinuousCollisionToiIterationLimitReached = false;
 279
 1964280        ContinuousCollisionMode mode = ContinuousCollisionMode.Continuous;
 1964281        if (!forceContinuous && !ShouldUseContinuousCollision(out mode))
 1706282            return false;
 283
 258284        Vector2d requestedDisplacement = _linearVelocity * initialRemainingTime;
 258285        Vector2d displacement = ContinuousCollisionSweepRange.ValidateEndpoint(
 258286            startPosition,
 258287            proposedPosition,
 258288            requestedDisplacement,
 258289            out _);
 290
 256291        Fixed64 displacementMagnitudeSquared = displacement.MagnitudeSquared;
 256292        if (displacementMagnitudeSquared <= Fixed64.Epsilon)
 8293            return false;
 294
 248295        Fixed64 proxyRadius = ResolveContinuousCollisionProxyRadius();
 248296        if (proxyRadius <= Fixed64.Epsilon
 248297            || (mode == ContinuousCollisionMode.Auto
 248298                && ContinuousCollisionMath.IsWithinProxyRadius(displacement, displacementMagnitudeSquared, proxyRadius))
 299        {
 2300            return false;
 301        }
 302
 246303        bool resolved = false;
 246304        int conservativeMixedRefinementCount = 0;
 246305        Vector2d currentPosition = startPosition;
 246306        Fixed64 remainingTime = initialRemainingTime;
 246307        Fixed64 elapsedTime = initialElapsedTime;
 246308        int maxToiIterations = Context.Settings.ContinuousCollisionMaxToiIterations;
 246309        LSCollider2D? originalIgnoredCollider2D = _continuousCollisionHandoffIgnoredCollider2D;
 246310        LSCollider? originalIgnoredCollider3D = _continuousCollisionHandoffIgnoredCollider3D;
 311        try
 312        {
 321313            while (LastContinuousCollisionToiIterationCount < maxToiIterations)
 314            {
 321315                Vector2d requestedSegmentDisplacement = _linearVelocity * remainingTime;
 321316                Vector2d requestedSegmentEnd = currentPosition + requestedSegmentDisplacement;
 321317                Vector2d segmentDisplacement = ContinuousCollisionSweepRange.ValidateEndpoint(
 321318                    currentPosition,
 321319                    requestedSegmentEnd,
 321320                    requestedSegmentDisplacement,
 321321                    out Fixed64 segmentLength);
 321322                Vector2d segmentEnd = requestedSegmentEnd;
 323
 321324                Fixed64 elapsedFraction = elapsedTime / Context.DeltaTime;
 321325                if (!TryGetFirstContinuousCollisionHit(
 321326                        currentPosition,
 321327                        segmentEnd,
 321328                        proxyRadius,
 321329                        elapsedFraction,
 321330                        out Vector2d hitNormal,
 321331                        out Fixed64 hitDistance,
 321332                        out ContinuousCollisionTargetKind targetKind,
 321333                        out LSCollider2D? target2D,
 321334                        out LSCollider? target3D))
 335                {
 167336                    currentPosition = segmentEnd;
 167337                    break;
 338                }
 339
 154340                Fixed64 hitTime = FixedMath.Clamp01(hitDistance / segmentLength);
 154341                currentPosition += segmentDisplacement.Normalized * hitDistance;
 154342                Vector2d previousVelocity = _linearVelocity;
 154343                Fixed64 consumedTime = remainingTime * hitTime;
 154344                Fixed64 remainingAfterHit = remainingTime - consumedTime;
 154345                Fixed64 hitElapsedTime = elapsedTime + consumedTime;
 154346                if (targetKind
 154347                    == ContinuousCollisionTargetKind.UnresolvedMixed)
 348                {
 6349                    resolved = true;
 6350                    if (hitTime > Fixed64.Epsilon
 6351                        && conservativeMixedRefinementCount
 6352                            < ContinuousCollisionMath.RotationalIntervalMaxDepth)
 353                    {
 3354                        conservativeMixedRefinementCount++;
 3355                        remainingTime = remainingAfterHit;
 3356                        elapsedTime = hitElapsedTime;
 3357                        if (remainingTime > Fixed64.Epsilon)
 358                            continue;
 359                    }
 360
 3361                    LastContinuousCollisionToiIterationCount++;
 3362                    LastContinuousCollisionToiIterationLimitReached =
 3363                        remainingAfterHit > Fixed64.Epsilon;
 3364                    _ = Context.Physics2D
 3365                        .TryReserveContinuousCollisionCandidateRefresh(this);
 3366                    UpdateContinuousCollisionFrameTrajectory(
 3367                        currentPosition,
 3368                        Vector2d.Zero,
 3369                        hitElapsedTime);
 3370                    Context.Physics2D
 3371                        .RefreshContinuousCollisionCandidate(this);
 372
 3373                    if (LastContinuousCollisionToiIterationLimitReached)
 3374                        Context.Physics2D.ReportContinuousCollisionIterationLimit();
 375
 3376                    break;
 377                }
 378
 148379                if (!CanAppendContinuousCollisionFrameSegment(hitElapsedTime))
 380                {
 2381                    ContinuousCollisionMotionSegment2D activeSegment =
 2382                        ResolveContinuousCollisionSegment(
 2383                            ResolveContinuousCollisionFrameFraction(hitElapsedTime));
 2384                    currentPosition = activeSegment.StartPosition;
 2385                    elapsedTime = activeSegment.StartFraction * Context.DeltaTime;
 2386                    RemoveClosingContinuousCollisionVelocity(hitNormal);
 2387                    UpdateContinuousCollisionFrameTrajectory(
 2388                        currentPosition,
 2389                        _linearVelocity,
 2390                        elapsedTime);
 2391                    Context.Physics2D.RefreshContinuousCollisionCandidate(this);
 2392                    LastContinuousCollisionToiIterationCount++;
 2393                    LastContinuousCollisionToiIterationLimitReached = true;
 2394                    Context.Physics2D.ReportContinuousCollisionIterationLimit();
 2395                    resolved = true;
 2396                    break;
 397                }
 398
 146399                bool appliedResponse = TryApplyContinuousCollisionDynamicResponse(
 146400                        hitNormal,
 146401                        targetKind,
 146402                        target2D,
 146403                        target3D,
 146404                        currentPosition,
 146405                        hitElapsedTime,
 146406                        remainingAfterHit);
 146407                if (!appliedResponse)
 408                {
 95409                    RemoveClosingContinuousCollisionVelocity(hitNormal);
 95410                    UpdateContinuousCollisionFrameTrajectory(currentPosition, _linearVelocity, hitElapsedTime);
 411                }
 51412                else if (targetKind == ContinuousCollisionTargetKind.Dynamic2D)
 413                {
 34414                    _continuousCollisionHandoffIgnoredCollider2D = target2D;
 34415                    _continuousCollisionHandoffIgnoredCollider3D = null;
 416                }
 417                else
 418                {
 17419                    _continuousCollisionHandoffIgnoredCollider2D = null;
 17420                    _continuousCollisionHandoffIgnoredCollider3D = target3D;
 421                }
 422
 146423                LastContinuousCollisionToiIterationCount++;
 146424                resolved = true;
 425
 146426                remainingTime = remainingAfterHit;
 146427                elapsedTime = hitElapsedTime;
 146428                if (remainingTime <= Fixed64.Epsilon || _linearVelocity.MagnitudeSquared <= Fixed64.Epsilon)
 429                    break;
 430
 80431                if (LastContinuousCollisionToiIterationCount >= maxToiIterations)
 432                {
 4433                    LastContinuousCollisionToiIterationLimitReached = true;
 4434                    break;
 435                }
 436
 76437                if (hitTime <= Fixed64.Epsilon && previousVelocity == _linearVelocity)
 438                    break;
 439            }
 440
 246441            proposedPosition = currentPosition;
 246442            return resolved;
 443        }
 444        finally
 445        {
 246446            _continuousCollisionHandoffIgnoredCollider2D = originalIgnoredCollider2D;
 246447            _continuousCollisionHandoffIgnoredCollider3D = originalIgnoredCollider3D;
 246448        }
 246449    }
 450
 451    private bool TryApplyContinuousCollisionDynamicResponse(
 452        Vector2d normalForSource,
 453        ContinuousCollisionTargetKind targetKind,
 454        LSCollider2D? target2D,
 455        LSCollider? target3D,
 456        Vector2d sourcePositionAtImpact,
 457        Fixed64 hitElapsedTime,
 458        Fixed64 remainingTime)
 459    {
 146460        if (targetKind == ContinuousCollisionTargetKind.Dynamic2D)
 461        {
 35462            SolidBody2D targetBody = target2D!.Body!;
 35463            Fixed64 frameFraction = ResolveContinuousCollisionFrameFraction(hitElapsedTime);
 35464            Vector2d targetPositionAtImpact = targetBody.SampleContinuousCollisionPosition(
 35465                frameFraction);
 35466            return TryApplyContinuousCollisionDynamicResponse(
 35467                targetBody,
 35468                normalForSource,
 35469                sourcePositionAtImpact,
 35470                targetPositionAtImpact,
 35471                hitElapsedTime,
 35472                remainingTime);
 473        }
 474
 111475        if (targetKind == ContinuousCollisionTargetKind.Dynamic3D)
 476        {
 25477            SolidBody targetBody = target3D!.Body!;
 25478            Fixed64 frameFraction = ResolveContinuousCollisionFrameFraction(hitElapsedTime);
 25479            Vector3d targetPositionAtImpact = targetBody.SampleContinuousCollisionPosition(
 25480                frameFraction);
 25481            return TryApplyContinuousCollisionMixed3DResponse(
 25482                targetBody,
 25483                normalForSource,
 25484                sourcePositionAtImpact,
 25485                targetPositionAtImpact,
 25486                hitElapsedTime,
 25487                remainingTime);
 488        }
 489
 86490        return false;
 491    }
 492
 493    internal bool TryApplyContinuousCollisionDynamicResponse(
 494        SolidBody2D target,
 495        Vector2d normalForSource,
 496        Vector2d sourcePositionAtImpact,
 497        Vector2d targetPositionAtImpact,
 498        Fixed64 hitElapsedTime,
 499        Fixed64 remainingTime)
 500    {
 42501        if (!ContinuousCollisionImpulsePolicy.TryResolveImpactNormal(normalForSource, out Vector2d normal))
 1502            return false;
 503
 41504        Fixed64 constrainedInverseMassA = GetConstrainedInverseMass(normal);
 41505        Fixed64 constrainedInverseMassB = target.GetConstrainedInverseMass(normal);
 41506        Fixed64 frameFraction = ResolveContinuousCollisionFrameFraction(hitElapsedTime);
 41507        Vector2d targetLinearVelocity = target.SampleContinuousCollisionLinearVelocity(frameFraction);
 41508        bool inverseMassResolved = Fixed64.TryAdd(
 41509            constrainedInverseMassA,
 41510            constrainedInverseMassB,
 41511            out Fixed64 inverseMass);
 41512        bool relativeVelocityResolved = Vector2d.TrySubtract(
 41513            _linearVelocity,
 41514            targetLinearVelocity,
 41515            out Vector2d relativeVelocity);
 41516        if (!(inverseMassResolved & relativeVelocityResolved))
 517        {
 1518            return false;
 519        }
 520
 40521        Fixed64 normalVelocity = Vector2d.Dot(relativeVelocity, normal);
 40522        if (normalVelocity >= -Fixed64.Epsilon)
 1523            return false;
 524
 39525        Fixed64 restitution = ResolveContinuousCollisionRestitution(target, -normalVelocity);
 39526        bool responseFactorResolved = Fixed64.TryAdd(
 39527            Fixed64.One,
 39528            restitution,
 39529            out Fixed64 responseFactor);
 39530        bool closingSpeedResolved = Fixed64.TrySubtract(
 39531            Fixed64.Zero,
 39532            normalVelocity,
 39533            out Fixed64 closingSpeed);
 39534        bool responseSpeedResolved = Fixed64.TryMultiplyDivide(
 39535            closingSpeed,
 39536            responseFactor,
 39537            Fixed64.One,
 39538            out Fixed64 responseSpeed);
 39539        if (!(responseFactorResolved & closingSpeedResolved & responseSpeedResolved))
 540        {
 1541            return false;
 542        }
 543
 38544        Vector2d sourceResponseNormal = ProjectLinearMotion(normal);
 38545        Vector2d targetResponseNormal = target.ProjectLinearMotion(-normal);
 38546        bool sourceDeltaResolved = ContinuousCollisionImpulsePolicy.TryResolveVelocityDelta(
 38547            sourceResponseNormal,
 38548            responseSpeed,
 38549            EffectiveInverseMass,
 38550            inverseMass,
 38551            out Vector2d sourceVelocityDelta);
 38552        bool targetDeltaResolved = ContinuousCollisionImpulsePolicy.TryResolveVelocityDelta(
 38553            targetResponseNormal,
 38554            responseSpeed,
 38555            target.EffectiveInverseMass,
 38556            inverseMass,
 38557            out Vector2d targetVelocityDelta);
 38558        if (!(sourceDeltaResolved & targetDeltaResolved))
 559        {
 1560            return false;
 561        }
 562
 37563        bool sourceVelocityResolved = Vector2d.TryAdd(
 37564            _linearVelocity,
 37565            sourceVelocityDelta,
 37566            out Vector2d sourcePostLinearVelocity);
 37567        bool sourceTrajectoryAvailable = CanAppendContinuousCollisionFrameSegment(hitElapsedTime);
 37568        if (!(sourceVelocityResolved & sourceTrajectoryAvailable))
 569        {
 1570            return false;
 571        }
 572
 36573        Vector2d targetResolvedPosition = default;
 36574        Vector2d targetPostLinearVelocity = targetLinearVelocity;
 36575        Fixed64 targetRotationAtImpact = target.SampleContinuousCollisionRotation(frameFraction);
 36576        Fixed64 targetAngularVelocity = target.SampleContinuousCollisionAngularVelocity(frameFraction);
 36577        bool targetStateAvailable = true;
 36578        if (target.CanTranslate)
 579        {
 33580            bool targetVelocityResolved = Vector2d.TryAdd(
 33581                targetLinearVelocity,
 33582                targetVelocityDelta,
 33583                out targetPostLinearVelocity);
 33584            bool targetTrajectoryAvailable = target.CanApplyContinuousCollisionHandoffState(
 33585                targetPositionAtImpact,
 33586                targetRotationAtImpact,
 33587                remainingTime,
 33588                out targetResolvedPosition);
 33589            targetStateAvailable = targetVelocityResolved & targetTrajectoryAvailable;
 590        }
 591
 36592        if (!targetStateAvailable)
 593        {
 1594            return false;
 595        }
 596
 35597        _ = target.CanTranslate
 35598            ? Context.Physics2D.TryReserveContinuousCollisionCandidateRefresh(this, target)
 35599            : Context.Physics2D.TryReserveContinuousCollisionCandidateRefresh(this);
 600
 35601        ApplyContinuousCollisionSourceResponse(
 35602            sourcePositionAtImpact,
 35603            sourcePostLinearVelocity,
 35604            hitElapsedTime);
 35605        if (target.CanTranslate)
 606        {
 32607            target.ApplyContinuousCollisionHandoffStateReserved(
 32608                targetResolvedPosition,
 32609                targetRotationAtImpact,
 32610                targetPostLinearVelocity,
 32611                targetAngularVelocity,
 32612                remainingTime,
 32613                ignoredCollider2D: Collider);
 614        }
 615
 35616        return true;
 617    }
 618
 619    internal bool TryApplyContinuousCollisionMixed3DResponse(
 620        SolidBody target,
 621        Vector2d normalForSource,
 622        Vector2d sourcePositionAtImpact,
 623        Vector3d targetPositionAtImpact,
 624        Fixed64 hitElapsedTime,
 625        Fixed64 remainingTime)
 626    {
 26627        bool normalResolved = ContinuousCollisionImpulsePolicy.TryResolveImpactNormal(
 26628            normalForSource,
 26629            out Vector2d normal);
 26630        Vector3d normal3D = normal.ToVector3d(Fixed64.Zero);
 26631        Fixed64 constrainedInverseMassA = GetConstrainedInverseMass(normal);
 26632        Fixed64 constrainedInverseMassB = target.GetConstrainedInverseMass(normal3D);
 26633        Fixed64 frameFraction = ResolveContinuousCollisionFrameFraction(hitElapsedTime);
 26634        Vector3d targetVelocity = target.SampleContinuousCollisionLinearVelocity(
 26635            frameFraction);
 26636        bool inverseMassResolved = Fixed64.TryAdd(
 26637            constrainedInverseMassA,
 26638            constrainedInverseMassB,
 26639            out Fixed64 inverseMass);
 26640        bool relativeVelocityResolved = Vector3d.TrySubtract(
 26641            _linearVelocity.ToVector3d(Fixed64.Zero),
 26642            targetVelocity,
 26643            out Vector3d relativeVelocity);
 26644        Fixed64 normalVelocity = Vector3d.Dot(relativeVelocity, normal3D);
 26645        Fixed64 restitution = ResolveContinuousCollisionRestitution(target, -normalVelocity);
 26646        bool responseFactorResolved = Fixed64.TryAdd(
 26647            Fixed64.One,
 26648            restitution,
 26649            out Fixed64 responseFactor);
 26650        bool closingSpeedResolved = Fixed64.TrySubtract(
 26651            Fixed64.Zero,
 26652            normalVelocity,
 26653            out Fixed64 closingSpeed);
 26654        bool responseSpeedResolved = Fixed64.TryMultiplyDivide(
 26655            closingSpeed,
 26656            responseFactor,
 26657            Fixed64.One,
 26658            out Fixed64 responseSpeed);
 26659        Vector2d sourceResponseNormal = ProjectLinearMotion(normal);
 26660        Vector3d targetResponseNormal = target.ProjectLinearMotion(-normal3D);
 26661        bool sourceDeltaResolved = ContinuousCollisionImpulsePolicy.TryResolveVelocityDelta(
 26662            sourceResponseNormal,
 26663            responseSpeed,
 26664            EffectiveInverseMass,
 26665            inverseMass,
 26666            out Vector2d sourceVelocityDelta);
 26667        bool targetDeltaResolved = ContinuousCollisionImpulsePolicy.TryResolveVelocityDelta(
 26668            targetResponseNormal,
 26669            responseSpeed,
 26670            target.EffectiveInverseMass,
 26671            inverseMass,
 26672            out Vector3d targetVelocityDelta);
 26673        bool sourceVelocityResolved = Vector2d.TryAdd(
 26674            _linearVelocity,
 26675            sourceVelocityDelta,
 26676            out Vector2d sourcePostLinearVelocity);
 26677        bool sourceTrajectoryAvailable = CanAppendContinuousCollisionFrameSegment(hitElapsedTime);
 26678        Vector3d targetResolvedPosition = default;
 26679        Vector3d targetPostLinearVelocity = targetVelocity;
 26680        FixedQuaternion targetRotationAtImpact = target.SampleContinuousCollisionRotation(frameFraction);
 26681        Vector3d targetAngularVelocity = target.SampleContinuousCollisionAngularVelocity(frameFraction);
 26682        bool targetStateAvailable = true;
 26683        if (target.CanTranslate)
 684        {
 23685            bool targetVelocityResolved = Vector3d.TryAdd(
 23686                targetVelocity,
 23687                targetVelocityDelta,
 23688                out targetPostLinearVelocity);
 23689            bool targetTrajectoryAvailable = target.CanApplyContinuousCollisionHandoff(
 23690                targetPositionAtImpact,
 23691                targetRotationAtImpact,
 23692                remainingTime,
 23693                out targetResolvedPosition);
 23694            targetStateAvailable = targetVelocityResolved & targetTrajectoryAvailable;
 695        }
 696
 26697        bool responseAdmissible = normalResolved
 26698            & inverseMassResolved
 26699            & relativeVelocityResolved
 26700            & normalVelocity < -Fixed64.Epsilon
 26701            & responseFactorResolved
 26702            & closingSpeedResolved
 26703            & responseSpeedResolved
 26704            & sourceDeltaResolved
 26705            & targetDeltaResolved
 26706            & sourceVelocityResolved
 26707            & sourceTrajectoryAvailable
 26708            & targetStateAvailable;
 26709        if (!responseAdmissible)
 710        {
 9711            return false;
 712        }
 713
 17714        _ = Context.Physics2D.TryReserveContinuousCollisionCandidateRefresh(this);
 17715        if (target.CanTranslate)
 14716            _ = Context.Physics.TryReserveContinuousCollisionCandidateRefresh(target);
 717
 17718        ApplyContinuousCollisionSourceResponse(
 17719            sourcePositionAtImpact,
 17720            sourcePostLinearVelocity,
 17721            hitElapsedTime);
 17722        if (target.CanTranslate)
 723        {
 14724            target.ApplyContinuousCollisionHandoffReserved(
 14725                targetResolvedPosition,
 14726                targetRotationAtImpact,
 14727                targetPostLinearVelocity,
 14728                targetAngularVelocity,
 14729                remainingTime,
 14730                ignoredCollider2D: Collider);
 731        }
 732
 17733        return true;
 734    }
 735
 736    private void ApplyContinuousCollisionSourceResponse(
 737        Vector2d positionAtImpact,
 738        Vector2d postLinearVelocity,
 739        Fixed64 elapsedTime)
 740    {
 52741        postLinearVelocity = ProjectLinearMotion(postLinearVelocity);
 52742        WakeFromCollision();
 52743        _linearVelocity = postLinearVelocity;
 52744        RefreshLinearSpeed();
 52745        UpdateContinuousCollisionFrameTrajectory(positionAtImpact, _linearVelocity, elapsedTime);
 52746        Context.Physics2D.RefreshContinuousCollisionCandidate(this);
 52747    }
 748
 749    private void UpdateContinuousCollisionFrameTrajectory(
 750        Vector2d positionAtElapsedTime,
 751        Vector2d velocity,
 752        Fixed64 elapsedTime)
 753    {
 152754        AppendContinuousCollisionFrameSegment(
 152755            positionAtElapsedTime,
 152756            velocity,
 152757            elapsedTime);
 152758    }
 759
 760    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 761    private Fixed64 ResolveContinuousCollisionFrameFraction(Fixed64 hitElapsedTime) =>
 178762        FixedMath.Clamp01(hitElapsedTime / Context.DeltaTime);
 763
 764    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 765    private Fixed64 ResolveContinuousCollisionRestitution(SolidBody2D target, Fixed64 closingSpeed)
 766    {
 47767        if (closingSpeed <= Context.Settings.RestitutionVelocityThreshold)
 2768            return Fixed64.Zero;
 769
 45770        return PhysicsMaterial.CombineRestitution(Collider.Material, target.Collider.Material);
 771    }
 772
 773    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 774    private Fixed64 ResolveContinuousCollisionRestitution(SolidBody target, Fixed64 closingSpeed)
 775    {
 33776        if (closingSpeed <= Context.Settings.RestitutionVelocityThreshold)
 13777            return Fixed64.Zero;
 778
 20779        return PhysicsMaterial.CombineRestitution(Collider.Material, target.Collider.Material);
 780    }
 781
 782    private void RemoveClosingContinuousCollisionVelocity(Vector2d normal)
 783    {
 100784        if (normal.MagnitudeSquared <= Fixed64.Epsilon)
 10785            return;
 786
 90787        Fixed64 closingSpeed = Vector2d.Dot(_linearVelocity, normal);
 90788        if (closingSpeed >= Fixed64.Zero)
 1789            return;
 790
 89791        _linearVelocity -= normal * closingSpeed;
 89792        RefreshLinearSpeed();
 89793    }
 794
 795}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/2D/SolidBody2D.ContinuousCollision.Helpers.cs

#LineLine coverage
 1//=======================================================================
 2// SolidBody2D.ContinuousCollision.Helpers.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2026-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using FixedMathSharp;
 9using Gravitas.Colliders;
 10using Gravitas.CollisionHandling;
 11using Gravitas.Queries;
 12using System.Runtime.CompilerServices;
 13
 14namespace Gravitas;
 15
 16public sealed partial class SolidBody2D
 17{
 18    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 19    private bool ShouldUseContinuousCollision(out ContinuousCollisionMode mode)
 20    {
 453821        mode = ResolveContinuousCollisionMode();
 453822        return mode == ContinuousCollisionMode.Continuous || mode == ContinuousCollisionMode.Auto;
 23    }
 24
 25    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 26    private ContinuousCollisionMode ResolveContinuousCollisionMode()
 27    {
 453828        ContinuousCollisionMode mode = _continuousCollisionMode;
 453829        if (mode != ContinuousCollisionMode.Inherit)
 104330            return mode;
 31
 349532        SolidBody2D? parentBody = Collider.TopParent2D?.Body;
 349533        if (parentBody != null && parentBody._continuousCollisionMode != ContinuousCollisionMode.Inherit)
 434            return parentBody._continuousCollisionMode;
 35
 349136        mode = Context.Settings.DefaultContinuousCollisionMode;
 349137        return mode == ContinuousCollisionMode.Inherit
 349138            ? ContinuousCollisionMode.Discrete
 349139            : mode;
 40    }
 41
 42    internal Fixed64 ResolveContinuousCollisionProxyRadius()
 43    {
 746244        bool offsetResolved =
 746245            Collider.TryGetCurrentScaledOffset(out Vector2d centerOffset);
 746246        bool distanceResolved =
 746247            centerOffset.TryGetMagnitudeCeiling(
 746248                out Fixed64 offsetDistance);
 746249        bool radiusResolved = Fixed64.TryAdd(
 746250            offsetDistance,
 746251            Collider.CanonicalCenteredProxyRadius,
 746252            out Fixed64 proxyRadius);
 746253        return offsetResolved & distanceResolved & radiusResolved
 746254            ? proxyRadius
 746255            : Fixed64.MaxValue;
 56    }
 57
 58    internal Fixed64 ResolveMixedContinuousCollisionProxyRadius()
 59    {
 18960        Vector2d mixedExtents = new(
 18961            ResolveContinuousCollisionProxyRadius(),
 18962            Collider.MixedHalfThickness);
 18963        return mixedExtents.TryGetMagnitudeCeiling(out Fixed64 radius)
 18964            ? radius
 18965            : Fixed64.MaxValue;
 66    }
 67
 68    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 69    private bool IsValidContinuousCollisionHit(Physics2DHit hit) =>
 21170        IsValidContinuousCollisionTarget(hit.Collider);
 71
 72    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 73    private static bool IsClosingContinuousCollisionHit(Vector2d displacement, Vector2d normal) =>
 18474        Vector2d.Dot(displacement, normal) < -Fixed64.Epsilon;
 75
 76    private bool IsValidContinuousCollisionTarget(LSCollider2D hitCollider)
 77    {
 29478        SolidBody2D? hitBody = hitCollider.Body;
 29479        if (hitBody?.IsKinematic == true)
 80        {
 2181            hitBody.EnsureContinuousCollisionFramePrepared(Context.LateSimulateToken);
 2182            if (hitBody.HasContinuousCollisionMotion)
 1083                return false;
 84        }
 85
 28486        return ContinuousCollisionTargetPolicy.AllowsStaticOrKinematic2DTarget(
 28487            ReferenceEquals(hitCollider, Collider),
 28488            ContinuousCollisionCandidateOrdering.IsIgnoredTarget(hitCollider, _continuousCollisionHandoffIgnoredCollider
 28489            hitCollider.IsTrigger,
 28490            Context.Physics2D.RequireCollisionPair(Collider, hitCollider),
 28491            hitCollider.IsStatic,
 28492            hitBody != null && hitBody.IsKinematic);
 93    }
 94
 95    private bool IsValidMixedContinuousCollisionHit(LSCollider hitCollider)
 96    {
 1397        SolidBody? hitBody = hitCollider.Body;
 1398        if (hitBody?.IsKinematic == true)
 99        {
 4100            hitBody.EnsureContinuousCollisionFramePrepared(Context.LateSimulateToken);
 4101            if (hitBody.HasContinuousCollisionMotion)
 3102                return false;
 103        }
 104
 10105        return ContinuousCollisionTargetPolicy.AllowsMixedStaticOrKinematicTarget(
 10106            ContinuousCollisionCandidateOrdering.IsIgnoredTarget(hitCollider, _continuousCollisionHandoffIgnoredCollider
 10107            hitCollider.IsTrigger,
 10108            Context.MixedCollisions.RequireCollisionPair(hitCollider, Collider),
 10109            hitCollider.IsStatic,
 10110            hitBody != null && hitBody.IsKinematic);
 111    }
 112
 113}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/2D/SolidBody2D.ContinuousCollision.Hits.cs

#LineLine coverage
 1//=======================================================================
 2// SolidBody2D.ContinuousCollision.Hits.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2026-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using FixedMathSharp;
 9using FixedMathSharp.Geometry;
 10using Gravitas.Colliders;
 11using Gravitas.CollisionHandling;
 12using Gravitas.Queries;
 13using Gravitas.Support;
 14using SwiftCollections;
 15
 16namespace Gravitas;
 17
 18public sealed partial class SolidBody2D
 19{
 20    private bool TryGetFirstContinuousCollisionHit(
 21        Vector2d startPosition,
 22        Vector2d proposedPosition,
 23        Fixed64 proxyRadius,
 24        Fixed64 elapsedFrameFraction,
 25        out Vector2d normal,
 26        out Fixed64 distance,
 27        out ContinuousCollisionTargetKind targetKind,
 28        out LSCollider2D? target2D,
 29        out LSCollider? target3D)
 30    {
 32131        Vector2d originalPosition = _position;
 32        try
 33        {
 32134            _position = startPosition;
 32135            Collider.RebuildRuntimeShapeOnly();
 36
 32137            int hitCount = Context.Query2D.SweepCircleAgainstStaticAll(
 32138                startPosition,
 32139                proposedPosition,
 32140                proxyRadius,
 32141                PhysicsLayerMask.All,
 32142                _continuousCollisionHits,
 32143                Collider,
 32144                includeTriggers: false);
 32145            int mixedHitCount = Context.Settings.RuntimeMode.RunsMixedContacts()
 32146                ? Context.QueryMixed.SweepCircleAgainstStatic3DAll(
 32147                    startPosition,
 32148                    proposedPosition,
 32149                    proxyRadius,
 32150                    Collider.MixedSlabCenterY,
 32151                    Collider.MixedHalfThickness,
 32152                    PhysicsLayerMask.All,
 32153                    _continuousMixedCollisionHits,
 32154                    Collider,
 32155                    includeTriggers: false,
 32156                    cacheTargetPartitions: true)
 32157                : 0;
 58
 32159            bool found2D = TryGetFirstValidContinuousCollisionHit(startPosition, proposedPosition, hitCount, out Physics
 32160            ContinuousCollisionTargetKind hit2DKind = found2D
 32161                ? ContinuousCollisionTargetKind.Static2D
 32162                : ContinuousCollisionTargetKind.None;
 32163            bool foundDynamic2D = TryGetFirstDynamicContinuousCollisionHit(
 32164                startPosition,
 32165                proposedPosition,
 32166                proxyRadius,
 32167                elapsedFrameFraction,
 32168                out Physics2DHit dynamicHit2D,
 32169                out Fixed64 dynamicClosingSpeed2D);
 32170            if (ContinuousCollisionCandidateOrdering.ShouldReplaceHit(dynamicHit2D, dynamicClosingSpeed2D, foundDynamic2
 71            {
 3672                hit2D = dynamicHit2D;
 3673                found2D = true;
 3674                hit2DKind = ContinuousCollisionTargetKind.Dynamic2D;
 75            }
 76
 32177            bool foundMixed = TryGetFirstValidMixedContinuousCollisionHit(startPosition, proposedPosition, mixedHitCount
 32178            ContinuousCollisionTargetKind hitMixedKind = foundMixed
 32179                ? ContinuousCollisionTargetKind.Static3D
 32180                : ContinuousCollisionTargetKind.None;
 32181            Fixed64 hitMixedDistance = foundMixed
 32182                ? hitMixed.Distance
 32183                : Fixed64.Zero;
 32184            bool foundDynamicMixed = TryGetFirstDynamicMixedContinuousCollisionHit(
 32185                startPosition,
 32186                proposedPosition,
 32187                elapsedFrameFraction,
 32188                out DynamicMixedIntervalHit dynamicMixed);
 32189            if (DynamicMixedIntervalHit.ShouldReplaceStatic(
 32190                    dynamicMixed,
 32191                    foundDynamicMixed,
 32192                    hitMixed,
 32193                    foundMixed))
 94            {
 3295                hitMixed = dynamicMixed.ExactHit;
 3296                hitMixedDistance = dynamicMixed.SafeDistance;
 3297                foundMixed = true;
 3298                hitMixedKind = dynamicMixed.Status
 3299                    == ContinuousCollisionMath.IntervalSearchStatus.ExactHit
 32100                    ? ContinuousCollisionTargetKind.Dynamic3D
 32101                    : ContinuousCollisionTargetKind.UnresolvedMixed;
 102            }
 103
 321104            if (DynamicMixedIntervalHit.ShouldSelect2D(
 321105                    found2D,
 321106                    hit2D.Distance,
 321107                    foundMixed,
 321108                    hitMixedDistance))
 109            {
 116110                normal = hit2D.Normal;
 116111                distance = hit2D.Distance;
 116112                targetKind = hit2DKind;
 116113                target2D = hit2D.Collider;
 116114                target3D = null;
 116115                return true;
 116            }
 117
 205118            if (foundMixed)
 119            {
 38120                normal = hitMixedKind == ContinuousCollisionTargetKind.UnresolvedMixed
 38121                    ? Vector2d.Zero
 38122                    : hitMixed.NormalFor2DSource;
 38123                distance = hitMixedDistance;
 38124                targetKind = hitMixedKind;
 38125                target2D = null;
 38126                target3D = hitMixedKind == ContinuousCollisionTargetKind.UnresolvedMixed
 38127                    ? null
 38128                    : hitMixed.Collider3D;
 38129                return true;
 130            }
 167131        }
 132        finally
 133        {
 321134            _position = originalPosition;
 321135            Collider.RebuildRuntimeShapeOnly();
 321136        }
 137
 167138        normal = Vector2d.Zero;
 167139        distance = Fixed64.Zero;
 167140        targetKind = ContinuousCollisionTargetKind.None;
 167141        target2D = null;
 167142        target3D = null;
 167143        return false;
 154144    }
 145
 146    private bool TryGetFirstValidContinuousCollisionHit(
 147        Vector2d startPosition,
 148        Vector2d proposedPosition,
 149        int hitCount,
 150        out Physics2DHit hit)
 151    {
 380152        Vector2d displacement = proposedPosition - startPosition;
 380153        bool found = false;
 380154        Physics2DHit best = default;
 1182155        for (int i = 0; i < hitCount; i++)
 156        {
 211157            Physics2DHit candidate = _continuousCollisionHits[i];
 211158            if (!IsValidContinuousCollisionHit(candidate))
 159                continue;
 160
 207161            if (!QueryDetection2D.TrySweepMoverShape(Collider, displacement, candidate.Collider, out Physics2DHit refine
 207162                || !IsClosingContinuousCollisionHit(displacement, refined.Normal))
 163                continue;
 164
 145165            if (found && !Physics2DHitSorter.ComesBefore(refined, best))
 166                continue;
 167
 113168            best = refined;
 113169            found = true;
 170        }
 171
 380172        hit = best;
 380173        return found;
 174    }
 175
 176    private bool TryGetFirstValidMixedContinuousCollisionHit(
 177        Vector2d startPosition,
 178        Vector2d proposedPosition,
 179        int hitCount,
 180        out PhysicsMixedHit hit)
 181    {
 380182        Vector2d displacement = proposedPosition - startPosition;
 770183        for (int i = 0; i < hitCount; i++)
 184        {
 13185            PhysicsMixedHit candidate = _continuousMixedCollisionHits[i];
 13186            if (!IsValidMixedContinuousCollisionHit(candidate.Collider3D!)
 13187                || !IsClosingContinuousCollisionHit(displacement, candidate.NormalFor2DSource))
 188                continue;
 189
 8190            hit = candidate;
 8191            return true;
 192        }
 193
 372194        hit = default;
 372195        return false;
 196    }
 197
 198    private bool TryGetFirstDynamicContinuousCollisionHit(
 199        Vector2d startPosition,
 200        Vector2d proposedPosition,
 201        Fixed64 proxyRadius,
 202        Fixed64 elapsedFrameFraction,
 203        out Physics2DHit hit,
 204        out Fixed64 closingSpeed)
 205    {
 321206        hit = default;
 321207        closingSpeed = Fixed64.Zero;
 208
 321209        Vector2d sourceDisplacement = proposedPosition - startPosition;
 321210        Fixed64 sourceLength = sourceDisplacement.Magnitude;
 321211        bool found = false;
 321212        Physics2DHit best = default;
 321213        Fixed64 bestClosingSpeed = Fixed64.Zero;
 321214        int token = Context.LateSimulateToken;
 321215        SwiftList<int> candidateIds = Context.Physics2D.QueryPlanarContinuousCollisionCandidates(
 321216            DynamicCcdCandidateIndex2D.CreateSweptCircleBounds(startPosition, sourceDisplacement, proxyRadius));
 1508217        for (int candidateIndex = 0; candidateIndex < candidateIds.Count; candidateIndex++)
 218        {
 433219            int dynamicId = candidateIds[candidateIndex];
 433220            SolidBody2D target = Context.Physics2D.GetContinuousCollisionCandidate(dynamicId);
 433221            if (!IsEligibleDynamicContinuousCollisionTarget(target))
 222            {
 223                continue;
 224            }
 225
 58226            target.EnsureContinuousCollisionFramePrepared(token);
 58227            if (!TryGetDynamicRelativeContinuousCollisionHit(
 58228                    target,
 58229                    startPosition,
 58230                    sourceDisplacement,
 58231                    proxyRadius,
 58232                    sourceLength,
 58233                    elapsedFrameFraction,
 58234                    out Physics2DHit candidate,
 58235                    out Fixed64 candidateClosingSpeed))
 236            {
 237                continue;
 238            }
 239
 46240            if (!ContinuousCollisionCandidateOrdering.ShouldReplaceHit(candidate, candidateClosingSpeed, true, found, be
 241                continue;
 242
 39243            best = candidate;
 39244            bestClosingSpeed = candidateClosingSpeed;
 39245            found = true;
 246        }
 247
 321248        hit = best;
 321249        closingSpeed = bestClosingSpeed;
 321250        return found;
 251    }
 252
 253    internal bool TryGetDynamicRelativeContinuousCollisionHit(
 254        SolidBody2D target,
 255        Vector2d sourceStart,
 256        Vector2d sourceDisplacement,
 257        Fixed64 sourceRadius,
 258        Fixed64 sourceLength,
 259        Fixed64 queryStartFraction,
 260        out Physics2DHit hit,
 261        out Fixed64 closingSpeed)
 262    {
 281263        hit = default;
 281264        closingSpeed = Fixed64.Zero;
 281265        Vector2d sourceEnd = sourceStart + sourceDisplacement;
 281266        Fixed64 targetRadius = target.ResolveContinuousCollisionProxyRadius();
 281267        bool found = false;
 281268        Physics2DHit best = default;
 281269        Fixed64 bestClosingSpeed = Fixed64.Zero;
 270
 281271        Vector2d originalSourcePosition = _position;
 281272        Fixed64 originalSourceRotation = _rotation;
 281273        Vector2d originalTargetPosition = target._position;
 281274        Fixed64 originalTargetRotation = target._rotation;
 275        try
 276        {
 281277            target.GetContinuousCollisionTrajectorySegmentRange(
 281278                queryStartFraction,
 281279                out int segmentStartIndex,
 281280                out int segmentEndExclusive);
 281281            for (int segmentIndex = segmentStartIndex;
 306282                segmentIndex < segmentEndExclusive;
 25283                segmentIndex++)
 284            {
 292285                ContinuousCollisionMotionSegment2D segment =
 292286                    target.GetContinuousCollisionTrajectorySegment(segmentIndex);
 292287                ContinuousCollisionMath.ClipTranslationalTrajectoryInterval(
 292288                    queryStartFraction,
 292289                    segment.StartFraction,
 292290                    segment.EndFraction,
 292291                    out Fixed64 overlapStart,
 292292                    out Fixed64 overlapEnd,
 292293                    out Fixed64 sourceStartTime,
 292294                    out Fixed64 sourceEndTime);
 295
 292296                Vector2d sourceSegmentStart = Vector2d.Lerp(
 292297                    sourceStart,
 292298                    sourceEnd,
 292299                    sourceStartTime);
 292300                Vector2d sourceSegmentEnd = Vector2d.Lerp(
 292301                    sourceStart,
 292302                    sourceEnd,
 292303                    sourceEndTime);
 292304                Vector2d sourceSegmentDisplacement =
 292305                    ContinuousCollisionSweepRange.ValidateEndpoint(
 292306                        sourceSegmentStart,
 292307                        sourceSegmentEnd,
 292308                        out Fixed64 sourceSegmentLength);
 292309                Vector2d targetStart = segment.SamplePosition(overlapStart);
 292310                Vector2d targetEnd = segment.SamplePosition(overlapEnd);
 292311                Vector2d targetDisplacement =
 292312                    ContinuousCollisionSweepRange.ValidateEndpoint(
 292313                        targetStart,
 292314                        targetEnd,
 292315                        out _);
 292316                LSCircleCollider2D? sourceCircle = Collider as LSCircleCollider2D;
 292317                LSCircleCollider2D? targetCircle = target.Collider as LSCircleCollider2D;
 292318                bool radialPair = sourceCircle is not null && targetCircle is not null;
 292319                Vector2d relativeDisplacement = default;
 292320                Fixed64 relativeLength = default;
 292321                if (!radialPair
 292322                    && !ContinuousCollisionMath.TryGetRelativeCircleOverlapDistanceInterval(
 292323                            sourceSegmentStart,
 292324                            sourceSegmentDisplacement,
 292325                            sourceRadius,
 292326                            targetStart,
 292327                            targetDisplacement,
 292328                            targetRadius,
 292329                            out _,
 292330                            out _,
 292331                            out relativeDisplacement,
 292332                            out relativeLength,
 292333                            out _,
 292334                            out _))
 335                {
 336                    continue;
 337                }
 292338                _position = sourceSegmentStart;
 292339                target._position = targetStart;
 292340                target._rotation = target.SampleContinuousCollisionRotation(
 292341                    overlapStart);
 292342                Collider.RebuildRuntimeShapeOnly();
 292343                target.Collider.RebuildRuntimeShapeOnly();
 344
 345                Physics2DHit relativeHit;
 292346                if (radialPair)
 347                {
 289348                    if (!ContinuousCollisionMath.TryGetRelativeCircleOverlapDistanceInterval(
 289349                            sourceCircle!.Center,
 289350                            sourceSegmentDisplacement,
 289351                            sourceCircle.ScaledRadius,
 289352                            targetCircle!.Center,
 289353                            targetDisplacement,
 289354                            targetCircle.ScaledRadius,
 289355                            out Fixed64 circleDistance,
 289356                            out _,
 289357                            out relativeDisplacement,
 289358                            out relativeLength,
 289359                            out Vector2d circleNormal,
 289360                            out _))
 361                    {
 362                        continue;
 363                    }
 364
 270365                    relativeHit = new Physics2DHit(
 270366                        targetCircle,
 270367                        new ContactAnchor2D(
 270368                            targetCircle.Center,
 270369                            circleNormal * targetCircle.ScaledRadius),
 270370                        circleNormal,
 270371                        circleDistance);
 372                }
 3373                else if (!QueryDetection2D.TrySweepMoverShape(
 3374                            Collider,
 3375                            relativeDisplacement,
 3376                            target.Collider,
 3377                            out relativeHit))
 378                {
 379                    continue;
 380                }
 381
 271382                Fixed64 successorStart = segmentIndex + 1
 271383                    < target.ContinuousCollisionTrajectoryCount
 271384                    ? target.GetContinuousCollisionTrajectorySegment(segmentIndex + 1)
 271385                        .StartFraction
 271386                    : Fixed64.Zero;
 271387                if (ContinuousCollisionMath.IsSupersededTranslationalBoundaryHit(
 271388                        relativeHit.Distance >= relativeLength,
 271389                        overlapEnd,
 271390                        segmentIndex,
 271391                        target.ContinuousCollisionTrajectoryCount,
 271392                        successorStart))
 393                {
 394                    continue;
 395                }
 396
 270397                Fixed64 localClosingSpeed =
 270398                    -Vector2d.Dot(relativeDisplacement, relativeHit.Normal);
 270399                if (!ContinuousCollisionMath.TryNormalizeTranslationalClosingSpeed(
 270400                        localClosingSpeed,
 270401                        sourceEndTime - sourceStartTime,
 270402                        out Fixed64 candidateClosingSpeed))
 403                    continue;
 404
 267405                ContactAnchor2D candidateAnchor = TranslateContactAnchor(
 267406                    relativeHit.Anchor,
 267407                    new FixedSegment2d(targetStart, targetEnd),
 267408                    relativeHit.Distance,
 267409                    relativeLength);
 267410                if (!Fixed64.TryMultiplyDivide(
 267411                        sourceSegmentLength,
 267412                        relativeHit.Distance,
 267413                        relativeLength,
 267414                        out Fixed64 localSourceDistance)
 267415                    || !Fixed64.TryMultiplyAdd(
 267416                        sourceLength,
 267417                        sourceStartTime,
 267418                        localSourceDistance,
 267419                        out Fixed64 sourceDistance))
 420                {
 421                    continue;
 422                }
 423
 267424                var candidate = new Physics2DHit(
 267425                    target.Collider,
 267426                    candidateAnchor,
 267427                    relativeHit.Normal,
 267428                    sourceDistance);
 267429                best = candidate;
 267430                bestClosingSpeed = candidateClosingSpeed;
 267431                found = true;
 267432                break;
 433            }
 14434        }
 435        finally
 436        {
 281437            _position = originalSourcePosition;
 281438            _rotation = originalSourceRotation;
 281439            target._position = originalTargetPosition;
 281440            target._rotation = originalTargetRotation;
 281441            target.Collider.RebuildRuntimeShapeOnly();
 281442            Collider.RebuildRuntimeShapeOnly();
 281443        }
 444
 281445        hit = best;
 281446        closingSpeed = bestClosingSpeed;
 281447        return found;
 448    }
 449
 450    private static ContactAnchor2D TranslateContactAnchor(
 451        ContactAnchor2D anchor,
 452        FixedSegment2d targetTrajectory,
 453        Fixed64 distance,
 454        Fixed64 totalDistance)
 455    {
 267456        Vector2d targetAtImpact = targetTrajectory.GetPointAtDistance(
 267457            distance,
 267458            totalDistance);
 267459        _ = Vector2d.TrySubtract(
 267460            targetAtImpact,
 267461            targetTrajectory.Start,
 267462            out Vector2d translation);
 267463        _ = Vector2d.TryAdd(anchor.Origin, translation, out Vector2d origin);
 267464        return new ContactAnchor2D(
 267465            origin,
 267466            anchor.Rotation,
 267467            anchor.LocalPoint,
 267468            anchor.LocalDisplacement);
 469    }
 470
 471    private bool TryGetFirstDynamicMixedContinuousCollisionHit(
 472        Vector2d startPosition,
 473        Vector2d proposedPosition,
 474        Fixed64 elapsedFrameFraction,
 475        out DynamicMixedIntervalHit hit)
 476    {
 321477        hit = default;
 478
 321479        if (!Context.Settings.RuntimeMode.RunsMixedContacts())
 208480            return false;
 481
 113482        Vector2d sourceDisplacement2D = proposedPosition - startPosition;
 113483        Fixed64 sourceLength = sourceDisplacement2D.Magnitude;
 113484        Vector3d sourceStart = new(startPosition.X, Collider.MixedSlabCenterY, startPosition.Y);
 113485        Vector3d sourceDisplacement = new(sourceDisplacement2D.X, Fixed64.Zero, sourceDisplacement2D.Y);
 113486        Fixed64 sourceRadius = ResolveMixedContinuousCollisionProxyRadius();
 113487        bool found = false;
 113488        DynamicMixedIntervalHit best = default;
 113489        int token = Context.LateSimulateToken;
 113490        SwiftList<int> candidateIds = Context.Physics.QueryContinuousCollisionCandidates(
 113491            DynamicCcdCandidateIndex.CreateSweptSphereBounds(sourceStart, sourceDisplacement, sourceRadius));
 438492        for (int candidateIndex = 0; candidateIndex < candidateIds.Count; candidateIndex++)
 493        {
 106494            int dynamicId = candidateIds[candidateIndex];
 106495            SolidBody target = Context.Physics.GetContinuousCollisionCandidate(dynamicId);
 106496            if (!IsEligibleDynamicMixed3DTarget(target))
 497            {
 498                continue;
 499            }
 500
 35501            target.EnsureContinuousCollisionFramePrepared(token);
 35502            ContinuousCollisionMath.IntervalSearchStatus status =
 35503                TryGetDynamicMixed3DContinuousCollisionHit(
 35504                    target,
 35505                    sourceStart,
 35506                    sourceDisplacement,
 35507                    sourceRadius,
 35508                    sourceLength,
 35509                    elapsedFrameFraction,
 35510                    out DynamicMixedIntervalHit candidate);
 35511            if (status
 35512                == ContinuousCollisionMath.IntervalSearchStatus.CertifiedNoHit)
 513            {
 514                continue;
 515            }
 516
 33517            best = DynamicMixedIntervalHit.Select(
 33518                candidate,
 33519                best,
 33520                ref found);
 521        }
 522
 113523        hit = best;
 113524        return found;
 525    }
 526
 527    private ContinuousCollisionMath.IntervalSearchStatus
 528        TryGetDynamicMixed3DContinuousCollisionHit(
 529        SolidBody target,
 530        Vector3d sourceStart,
 531        Vector3d sourceDisplacement,
 532        Fixed64 sourceRadius,
 533        Fixed64 sourceLength,
 534        Fixed64 queryStartFraction,
 535        out DynamicMixedIntervalHit hit)
 536    {
 58537        hit = default;
 58538        Vector3d sourceEnd = sourceStart + sourceDisplacement;
 58539        Vector3d sourceDirection = sourceDisplacement.Normalized;
 58540        Fixed64 targetRadius =
 58541            target.ResolveContinuousCollisionProxyRadius();
 58542        target.GetContinuousCollisionTrajectorySegmentRange(
 58543            queryStartFraction,
 58544            out int segmentStartIndex,
 58545            out int segmentEndExclusive);
 58546        for (int segmentIndex = segmentStartIndex;
 67547            segmentIndex < segmentEndExclusive;
 9548            segmentIndex++)
 549        {
 60550            ContinuousCollisionMotionSegment3D segment =
 60551                target.GetContinuousCollisionTrajectorySegment(segmentIndex);
 60552            ContinuousCollisionMath.ClipTranslationalTrajectoryInterval(
 60553                queryStartFraction,
 60554                segment.StartFraction,
 60555                segment.EndFraction,
 60556                out Fixed64 overlapStart,
 60557                out Fixed64 overlapEnd,
 60558                out Fixed64 sourceStartTime,
 60559                out Fixed64 sourceEndTime);
 560
 60561            Vector3d sourceSegmentStart = Vector3d.Lerp(
 60562                sourceStart,
 60563                sourceEnd,
 60564                sourceStartTime);
 60565            Vector3d sourceSegmentEnd = Vector3d.Lerp(
 60566                sourceStart,
 60567                sourceEnd,
 60568                sourceEndTime);
 60569            Vector3d sourceSegmentDisplacement =
 60570                ContinuousCollisionSweepRange.ValidateEndpoint(
 60571                    sourceSegmentStart,
 60572                    sourceSegmentEnd,
 60573                    out Fixed64 sourceSegmentLength);
 60574            Vector3d targetStart = segment.SamplePosition(overlapStart);
 60575            Vector3d targetEnd = segment.SamplePosition(overlapEnd);
 60576            Vector3d targetDisplacement =
 60577                ContinuousCollisionSweepRange.ValidateEndpoint(
 60578                    targetStart,
 60579                    targetEnd,
 60580                    out Fixed64 targetSegmentLength);
 60581            if (!ContinuousCollisionMath.TryGetRelativeSphereOverlapDistanceInterval(
 60582                    sourceSegmentStart,
 60583                    sourceSegmentDisplacement,
 60584                    sourceRadius,
 60585                    targetStart,
 60586                    targetDisplacement,
 60587                    targetRadius,
 60588                    out Fixed64 entryDistance,
 60589                    out Fixed64 exitDistance,
 60590                    out Vector3d relativeDisplacement,
 60591                    out Fixed64 relativeLength,
 60592                    out _,
 60593                    out _))
 594            {
 595                continue;
 596            }
 597
 58598            Fixed64 successorStart = segmentIndex + 1
 58599                < target.ContinuousCollisionTrajectoryCount
 58600                ? target.GetContinuousCollisionTrajectorySegment(segmentIndex + 1)
 58601                    .StartFraction
 58602                : Fixed64.Zero;
 58603            if (ContinuousCollisionMath.IsSupersededTranslationalBoundaryHit(
 58604                    entryDistance >= relativeLength,
 58605                    overlapEnd,
 58606                    segmentIndex,
 58607                    target.ContinuousCollisionTrajectoryCount,
 58608                    successorStart))
 609            {
 610                continue;
 611            }
 612
 58613            Fixed64 sourceStartRotation =
 58614                SampleContinuousCollisionRotation(overlapStart);
 58615            Fixed64 sourceEndRotation =
 58616                SampleContinuousCollisionRotation(overlapEnd);
 58617            Fixed64 angularDelta = CanonicalizeRotation(
 58618                sourceEndRotation - sourceStartRotation);
 58619            Fixed64 intervalElapsedTime =
 58620                overlapStart * Context.DeltaTime;
 58621            Fixed64 intervalDuration =
 58622                (overlapEnd - overlapStart) * Context.DeltaTime;
 58623            bool foundExactContact =
 58624                TryGetExactSphereCircleTranslationalContact(
 58625                    target,
 58626                    segment,
 58627                    new FixedSegment(targetStart, targetEnd),
 58628                    targetDisplacement,
 58629                    targetSegmentLength,
 58630                    new FixedSegment2d(
 58631                        sourceSegmentStart.ToVector2d(),
 58632                        sourceSegmentEnd.ToVector2d()),
 58633                    sourceSegmentDisplacement.ToVector2d(),
 58634                    sourceSegmentLength,
 58635                    sourceStartRotation,
 58636                    angularDelta,
 58637                    out bool requiresRotationalFallback,
 58638                    out Fixed64 exactRelativeDistance,
 58639                    out Fixed64 sourceContactDistance,
 58640                    out Fixed64 exactLocalClosingSpeed,
 58641                    out MixedContact exactContact);
 58642            if (foundExactContact)
 643            {
 42644                if (ContinuousCollisionMath.IsSupersededTranslationalBoundaryHit(
 42645                        exactRelativeDistance >= relativeLength,
 42646                        overlapEnd,
 42647                        segmentIndex,
 42648                        target.ContinuousCollisionTrajectoryCount,
 42649                        successorStart))
 650                {
 651                    continue;
 652                }
 653
 40654                if (!ContinuousCollisionMath.TryNormalizeTranslationalClosingSpeed(
 40655                        exactLocalClosingSpeed,
 40656                        sourceEndTime - sourceStartTime,
 40657                        out Fixed64 exactClosingSpeed))
 658                {
 659                    continue;
 660                }
 661
 40662                if (!Fixed64.TryMultiplyAdd(
 40663                        sourceLength,
 40664                        sourceStartTime,
 40665                        sourceContactDistance,
 40666                        out Fixed64 exactSourceDistance))
 667                {
 668                    continue;
 669                }
 670
 40671                var analyticHit = new PhysicsMixedHit(
 40672                    target.Collider,
 40673                    null,
 40674                    exactContact.Anchor3D,
 40675                    exactContact.Anchor2D,
 40676                    exactContact.Normal3DTo2D,
 40677                    PhysicsQueryReducerKind.Exact,
 40678                    exactSourceDistance,
 40679                    sourceDirection);
 40680                hit = new DynamicMixedIntervalHit(
 40681                    ContinuousCollisionMath.IntervalSearchStatus.ExactHit,
 40682                    analyticHit,
 40683                    analyticHit.Distance,
 40684                    exactClosingSpeed,
 40685                    target.Collider.Id);
 40686                return ContinuousCollisionMath.IntervalSearchStatus.ExactHit;
 687            }
 16688            if (!requiresRotationalFallback)
 689                continue;
 690
 13691            Fixed64 entryTime = entryDistance / relativeLength;
 13692            Fixed64 exitTime = exitDistance / relativeLength;
 693
 13694            bool searchFound =
 13695                TryFindEarliestMixedRotationalContinuousCollisionAgainstTarget(
 13696                    target.Collider,
 13697                    sourceSegmentStart.ToVector2d(),
 13698                    sourceSegmentDisplacement.ToVector2d(),
 13699                    sourceStartRotation,
 13700                    angularDelta,
 13701                    angularDelta.Abs(),
 13702                    sourceRadius,
 13703                    intervalElapsedTime,
 13704                    intervalDuration,
 13705                    entryTime,
 13706                    exitTime,
 13707                    out Fixed64 safeTime,
 13708                    out MixedContact contact,
 13709                    out bool hasContact,
 13710                    out Fixed64 contactTime);
 13711            if (!searchFound)
 712            {
 713                continue;
 714            }
 715
 11716            bool closingResolved = Vector3d.TryDot(
 11717                relativeDisplacement,
 11718                contact.Normal3DTo2D,
 11719                out Fixed64 contactClosingSpeed);
 11720            contactClosingSpeed = -contactClosingSpeed;
 11721            bool hasOnlyTranslationalMotion =
 11722                angularDelta.Abs() <= Fixed64.Epsilon
 11723                & segment.AngularDistance <= Fixed64.Epsilon;
 11724            bool useContactClosingSpeed = hasContact & hasOnlyTranslationalMotion;
 11725            if (useContactClosingSpeed
 11726                & (!closingResolved
 11727                    | contactClosingSpeed <= Fixed64.Epsilon))
 728            {
 729                continue;
 730            }
 11731            Fixed64 candidateClosingSpeed =
 11732                (useContactClosingSpeed
 11733                    ? contactClosingSpeed
 11734                    : relativeLength)
 11735                / (sourceEndTime - sourceStartTime);
 736
 11737            Fixed64 safeSourceTime = FixedMath.Lerp(
 11738                sourceStartTime,
 11739                sourceEndTime,
 11740                safeTime);
 11741            bool exactHit = hasContact & contactTime == safeTime;
 11742            PhysicsMixedHit exact = exactHit
 11743                ? new PhysicsMixedHit(
 11744                    target.Collider,
 11745                    null,
 11746                    contact.Anchor3D,
 11747                    contact.Anchor2D,
 11748                    contact.Normal3DTo2D,
 11749                    PhysicsQueryReducerKind.ConservativeFallback,
 11750                    sourceLength * safeSourceTime,
 11751                    sourceDirection)
 11752                : default;
 11753            ContinuousCollisionMath.IntervalSearchStatus status = exactHit
 11754                ? ContinuousCollisionMath.IntervalSearchStatus.ExactHit
 11755                : ContinuousCollisionMath.IntervalSearchStatus.Unresolved;
 11756            hit = new DynamicMixedIntervalHit(
 11757                status,
 11758                exact,
 11759                sourceLength * safeSourceTime,
 11760                candidateClosingSpeed,
 11761                target.Collider.Id);
 11762            return status;
 763        }
 764
 7765        return ContinuousCollisionMath.IntervalSearchStatus.CertifiedNoHit;
 766    }
 767
 768    private bool IsEligibleDynamicContinuousCollisionTarget(SolidBody2D target)
 769    {
 504770        return (IsKinematic || !target.ShouldOwnContinuousCollisionMovingPair(
 504771                HasContinuousCollisionRotationalMotion))
 504772            && !ContinuousCollisionCandidateOrdering.IsIgnoredTarget(
 504773                target.Collider,
 504774                _continuousCollisionHandoffIgnoredCollider2D)
 504775            && ContinuousCollisionTargetPolicy.AllowsIndexed2DTarget(
 504776                ReferenceEquals(target, this),
 504777                target.Active,
 504778                target.IsDynamic,
 504779                target.IsKinematic,
 504780                target.IsKinematic && target.HasContinuousCollisionMotion,
 504781                target.Collider.IsTrigger,
 504782                Context.Physics2D.RequireCollisionPair(Collider, target.Collider));
 783    }
 784
 785    private bool IsEligibleDynamicMixed3DTarget(SolidBody target)
 786    {
 120787        return (IsKinematic || !target.ShouldOwnContinuousCollisionMovingPair(
 120788                HasContinuousCollisionRotationalMotion))
 120789            && !ContinuousCollisionCandidateOrdering.IsIgnoredTarget(
 120790                target.Collider,
 120791                _continuousCollisionHandoffIgnoredCollider3D)
 120792            && ContinuousCollisionTargetPolicy.AllowsMixedIndexedTarget(
 120793                target.Active,
 120794                target.IsDynamic,
 120795                target.IsKinematic,
 120796                target.IsKinematic && target.HasContinuousCollisionMotion,
 120797                target.Collider.IsTrigger,
 120798                Context.MixedCollisions.RequireCollisionPair(target.Collider, Collider));
 799    }
 800
 801}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/2D/SolidBody2D.ContinuousCollision.Kinematic.cs

#LineLine coverage
 1//=======================================================================
 2// SolidBody2D.ContinuousCollision.Kinematic.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2026-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using FixedMathSharp;
 9using Gravitas.CollisionHandling;
 10using Gravitas.Queries;
 11using Gravitas.Support;
 12using SwiftCollections;
 13
 14namespace Gravitas;
 15
 16public sealed partial class SolidBody2D
 17{
 18    private bool TryResolveKinematicContinuousCollision(Vector2d startPosition, ref Vector2d proposedPosition)
 19    {
 8520        if (!ShouldUseContinuousCollision(out ContinuousCollisionMode mode))
 2021            return false;
 22
 6523        Vector2d displacement = ContinuousCollisionSweepRange.ValidateEndpoint(
 6524            startPosition,
 6525            proposedPosition,
 6526            out Fixed64 sourceLength);
 6527        Fixed64 displacementMagnitudeSquared = displacement.MagnitudeSquared;
 6528        if (displacementMagnitudeSquared <= Fixed64.Epsilon)
 329            return false;
 30
 6231        Fixed64 proxyRadius = ResolveContinuousCollisionProxyRadius();
 6232        if (proxyRadius <= Fixed64.Epsilon
 6233            || (mode == ContinuousCollisionMode.Auto
 6234                && ContinuousCollisionMath.IsWithinProxyRadius(displacement, displacementMagnitudeSquared, proxyRadius))
 35        {
 336            return false;
 37        }
 38
 5939        bool foundStatic = TryGetFirstKinematicStaticContinuousCollisionHit(
 5940            startPosition,
 5941            proposedPosition,
 5942            proxyRadius,
 5943            out Fixed64 staticHitDistance);
 5944        Fixed64 dynamicPushLimit = foundStatic ? staticHitDistance : sourceLength;
 5945        bool pushedDynamic = TryApplyKinematicDynamicContinuousCollisionPushes(
 5946            startPosition,
 5947            proposedPosition,
 5948            proxyRadius,
 5949            dynamicPushLimit,
 5950            sourceLength,
 5951            out bool hasUnresolvedMixedLimit,
 5952            out Fixed64 unresolvedMixedDistance);
 5953        staticHitDistance = FixedMath.Min(
 5954            dynamicPushLimit,
 5955            unresolvedMixedDistance);
 5956        foundStatic |= hasUnresolvedMixedLimit;
 5957        if (!foundStatic && !pushedDynamic)
 1558            return false;
 59
 4460        LastContinuousCollisionToiIterationCount++;
 4461        if (foundStatic)
 3462            proposedPosition = startPosition + displacement.Normalized * staticHitDistance;
 63
 4464        return true;
 65    }
 66
 67    private bool TryGetFirstKinematicStaticContinuousCollisionHit(
 68        Vector2d startPosition,
 69        Vector2d proposedPosition,
 70        Fixed64 proxyRadius,
 71        out Fixed64 distance)
 72    {
 5973        Vector2d originalPosition = _position;
 74        try
 75        {
 5976            _position = startPosition;
 5977            Collider.RebuildRuntimeShapeOnly();
 78
 5979            int hitCount = Context.Query2D.SweepCircleAgainstStaticAll(
 5980                startPosition,
 5981                proposedPosition,
 5982                proxyRadius,
 5983                PhysicsLayerMask.All,
 5984                _continuousCollisionHits,
 5985                Collider,
 5986                includeTriggers: false);
 5987            int mixedHitCount = Context.Settings.RuntimeMode.RunsMixedContacts()
 5988                ? Context.QueryMixed.SweepCircleAgainstStatic3DAll(
 5989                    startPosition,
 5990                    proposedPosition,
 5991                    proxyRadius,
 5992                    Collider.MixedSlabCenterY,
 5993                    Collider.MixedHalfThickness,
 5994                    PhysicsLayerMask.All,
 5995                    _continuousMixedCollisionHits,
 5996                    Collider,
 5997                    includeTriggers: false,
 5998                    cacheTargetPartitions: true)
 5999                : 0;
 100
 59101            bool found2D = TryGetFirstValidContinuousCollisionHit(startPosition, proposedPosition, hitCount, out Physics
 59102            bool foundMixed = TryGetFirstValidMixedContinuousCollisionHit(startPosition, proposedPosition, mixedHitCount
 59103            if (found2D
 59104                && (!foundMixed
 59105                    || ContinuousCollisionCandidateOrdering.Is2DHitFirst(
 59106                        hit2D.Distance,
 59107                        hitMixed.Distance)))
 108            {
 31109                distance = hit2D.Distance;
 31110                return true;
 111            }
 112
 28113            if (foundMixed)
 114            {
 2115                distance = hitMixed.Distance;
 2116                return true;
 117            }
 26118        }
 119        finally
 120        {
 59121            _position = originalPosition;
 59122            Collider.RebuildRuntimeShapeOnly();
 59123        }
 124
 26125        distance = Fixed64.Zero;
 26126        return false;
 33127    }
 128
 129    private bool TryApplyKinematicDynamicContinuousCollisionPushes(
 130        Vector2d startPosition,
 131        Vector2d proposedPosition,
 132        Fixed64 proxyRadius,
 133        Fixed64 maxDistance,
 134        Fixed64 sourceLength,
 135        out bool hasUnresolvedMixedLimit,
 136        out Fixed64 unresolvedMixedDistance)
 137    {
 59138        hasUnresolvedMixedLimit = false;
 59139        unresolvedMixedDistance = maxDistance;
 59140        _continuousCollisionHits.FastClear();
 59141        _continuousMixedCollisionHits.FastClear();
 59142        GatherKinematicDynamic2DContinuousCollisionHits(
 59143            startPosition,
 59144            proposedPosition,
 59145            proxyRadius,
 59146            maxDistance,
 59147            sourceLength);
 59148        GatherKinematicDynamicMixed3DContinuousCollisionHits(
 59149            startPosition,
 59150            proposedPosition,
 59151            maxDistance,
 59152            sourceLength,
 59153            ref hasUnresolvedMixedLimit,
 59154            ref unresolvedMixedDistance);
 155
 59156        Physics2DHitSorter.SortByDistance(_continuousCollisionHits);
 59157        PhysicsMixedHitSorter.SortByDistance(_continuousMixedCollisionHits);
 59158        Vector2d sourceDisplacement = proposedPosition - startPosition;
 59159        int hit2DIndex = 0;
 59160        int hit3DIndex = 0;
 59161        bool pushed = false;
 76162        while (hit2DIndex < _continuousCollisionHits.Count
 76163            || hit3DIndex < _continuousMixedCollisionHits.Count)
 164        {
 17165            bool take2D = hit3DIndex >= _continuousMixedCollisionHits.Count
 17166                || (hit2DIndex < _continuousCollisionHits.Count
 17167                    && _continuousCollisionHits[hit2DIndex].Distance
 17168                        <= _continuousMixedCollisionHits[hit3DIndex].Distance);
 17169            if (take2D)
 170            {
 8171                Physics2DHit hit = _continuousCollisionHits[hit2DIndex++];
 8172                pushed |= ApplyKinematicContinuousCollisionHandoff(
 8173                    hit.Body!,
 8174                    sourceDisplacement,
 8175                    hit.Normal,
 8176                    hit.Distance,
 8177                    sourceLength);
 178
 8179                continue;
 180            }
 181
 9182            PhysicsMixedHit hit3D = _continuousMixedCollisionHits[hit3DIndex++];
 9183            pushed |= ApplyKinematicContinuousCollisionHandoff(
 9184                hit3D.Body3D!,
 9185                sourceDisplacement,
 9186                hit3D.NormalFor2DSource,
 9187                hit3D.Distance,
 9188                sourceLength);
 189        }
 190
 59191        return pushed;
 192    }
 193
 194    private void GatherKinematicDynamic2DContinuousCollisionHits(
 195        Vector2d startPosition,
 196        Vector2d proposedPosition,
 197        Fixed64 proxyRadius,
 198        Fixed64 maxDistance,
 199        Fixed64 sourceLength)
 200    {
 59201        Vector2d sourceDisplacement = proposedPosition - startPosition;
 59202        int token = Context.LateSimulateToken;
 59203        SwiftList<int> candidateIds = Context.Physics2D.QueryPlanarContinuousCollisionCandidates(
 59204            DynamicCcdCandidateIndex2D.CreateSweptCircleBounds(startPosition, sourceDisplacement, proxyRadius));
 260205        for (int candidateIndex = 0; candidateIndex < candidateIds.Count; candidateIndex++)
 206        {
 71207            int dynamicId = candidateIds[candidateIndex];
 71208            SolidBody2D target = Context.Physics2D.GetContinuousCollisionCandidate(dynamicId);
 71209            if (!IsEligibleDynamicContinuousCollisionTarget(target))
 210            {
 211                continue;
 212            }
 213
 11214            target.EnsureContinuousCollisionFramePrepared(token);
 11215            if (!TryGetDynamicRelativeContinuousCollisionHit(
 11216                    target,
 11217                    startPosition,
 11218                    sourceDisplacement,
 11219                    proxyRadius,
 11220                    sourceLength,
 11221                    Fixed64.Zero,
 11222                    out Physics2DHit exactHit,
 11223                    out _))
 224            {
 225                continue;
 226            }
 227
 9228            if (exactHit.Distance > maxDistance)
 229                continue;
 230
 8231            _continuousCollisionHits.Add(exactHit);
 232        }
 59233    }
 234
 235    private void GatherKinematicDynamicMixed3DContinuousCollisionHits(
 236        Vector2d startPosition,
 237        Vector2d proposedPosition,
 238        Fixed64 maxDistance,
 239        Fixed64 sourceLength,
 240        ref bool hasUnresolvedMixedLimit,
 241        ref Fixed64 unresolvedMixedDistance)
 242    {
 59243        if (!Context.Settings.RuntimeMode.RunsMixedContacts())
 43244            return;
 245
 16246        Vector2d sourceDisplacement2D = proposedPosition - startPosition;
 16247        Vector3d sourceStart = new(startPosition.X, Collider.MixedSlabCenterY, startPosition.Y);
 16248        Vector3d sourceDisplacement = new(sourceDisplacement2D.X, Fixed64.Zero, sourceDisplacement2D.Y);
 16249        Fixed64 sourceRadius = ResolveMixedContinuousCollisionProxyRadius();
 16250        int token = Context.LateSimulateToken;
 16251        SwiftList<int> candidateIds = Context.Physics.QueryContinuousCollisionCandidates(
 16252            DynamicCcdCandidateIndex.CreateSweptSphereBounds(sourceStart, sourceDisplacement, sourceRadius));
 60253        for (int candidateIndex = 0; candidateIndex < candidateIds.Count; candidateIndex++)
 254        {
 14255            int dynamicId = candidateIds[candidateIndex];
 14256            SolidBody target = Context.Physics.GetContinuousCollisionCandidate(dynamicId);
 14257            if (!IsEligibleDynamicMixed3DTarget(target))
 258            {
 259                continue;
 260            }
 261
 13262            target.EnsureContinuousCollisionFramePrepared(token);
 13263            ContinuousCollisionMath.IntervalSearchStatus status =
 13264                TryGetDynamicMixed3DContinuousCollisionHit(
 13265                    target,
 13266                    sourceStart,
 13267                    sourceDisplacement,
 13268                    sourceRadius,
 13269                    sourceLength,
 13270                    Fixed64.Zero,
 13271                    out DynamicMixedIntervalHit candidate);
 13272            if (status
 13273                    == ContinuousCollisionMath.IntervalSearchStatus.CertifiedNoHit
 13274                || candidate.SafeDistance > maxDistance)
 275            {
 276                continue;
 277            }
 278
 10279            if (status
 10280                == ContinuousCollisionMath.IntervalSearchStatus.Unresolved)
 281            {
 1282                hasUnresolvedMixedLimit = true;
 1283                unresolvedMixedDistance = FixedMath.Min(
 1284                    unresolvedMixedDistance,
 1285                    candidate.SafeDistance);
 1286                continue;
 287            }
 288
 9289            _continuousMixedCollisionHits.Add(candidate.ExactHit);
 290        }
 16291    }
 292
 293    internal bool ApplyKinematicContinuousCollisionHandoff(
 294        SolidBody2D target,
 295        Vector2d sourceDisplacement,
 296        Vector2d normalForSource,
 297        Fixed64 hitDistance,
 298        Fixed64 sourceLength)
 299    {
 9300        _ = ContinuousCollisionImpulsePolicy.TryResolveSourceNormal(normalForSource, sourceDisplacement, out Vector2d no
 301
 9302        Fixed64 deltaTime = Context.DeltaTime;
 9303        Fixed64 constrainedInverseMass = target.GetConstrainedInverseMass(normal);
 9304        if (constrainedInverseMass <= Fixed64.Zero)
 1305            return false;
 306
 8307        Fixed64 hitTime = FixedMath.Clamp01(hitDistance / sourceLength);
 8308        Vector2d sourceVelocity = SampleContinuousCollisionLinearVelocity(hitTime);
 8309        Vector2d targetVelocity = target.SampleContinuousCollisionLinearVelocity(hitTime);
 8310        bool relativeVelocityResolved = Vector2d.TrySubtract(
 8311            sourceVelocity,
 8312            targetVelocity,
 8313            out Vector2d relativeVelocity);
 8314        Fixed64 normalVelocity = Vector2d.Dot(relativeVelocity, normal);
 8315        Fixed64 restitution = ResolveContinuousCollisionRestitution(target, -normalVelocity);
 8316        bool responseFactorResolved = Fixed64.TryAdd(
 8317            Fixed64.One,
 8318            restitution,
 8319            out Fixed64 responseFactor);
 8320        bool closingSpeedResolved = Fixed64.TrySubtract(
 8321            Fixed64.Zero,
 8322            normalVelocity,
 8323            out Fixed64 closingSpeed);
 8324        bool responseSpeedResolved = Fixed64.TryMultiplyDivide(
 8325            closingSpeed,
 8326            responseFactor,
 8327            Fixed64.One,
 8328            out Fixed64 responseSpeed);
 8329        Vector2d targetResponseNormal = target.ProjectLinearMotion(-normal);
 8330        bool targetDeltaResolved = ContinuousCollisionImpulsePolicy.TryResolveVelocityDelta(
 8331            targetResponseNormal,
 8332            responseSpeed,
 8333            target.EffectiveInverseMass,
 8334            constrainedInverseMass,
 8335            out Vector2d targetVelocityDelta);
 8336        bool targetVelocityResolved = Vector2d.TryAdd(
 8337            targetVelocity,
 8338            target.ProjectLinearMotion(targetVelocityDelta),
 8339            out Vector2d targetPostVelocity);
 8340        if (!(relativeVelocityResolved
 8341            & normalVelocity < -Fixed64.Epsilon
 8342            & responseFactorResolved
 8343            & closingSpeedResolved
 8344            & responseSpeedResolved
 8345            & targetDeltaResolved
 8346            & targetVelocityResolved))
 347        {
 1348            return false;
 349        }
 350
 7351        return target.ApplyContinuousCollisionHandoffState(
 7352            target.SampleContinuousCollisionPosition(hitTime),
 7353            target.SampleContinuousCollisionRotation(hitTime),
 7354            targetPostVelocity,
 7355            target.SampleContinuousCollisionAngularVelocity(hitTime),
 7356            deltaTime * (Fixed64.One - hitTime),
 7357            ignoredCollider2D: Collider);
 358    }
 359
 360    internal bool ApplyKinematicContinuousCollisionHandoff(
 361        SolidBody target,
 362        Vector2d sourceDisplacement,
 363        Vector2d normalForSource,
 364        Fixed64 hitDistance,
 365        Fixed64 sourceLength)
 366    {
 11367        if (!ContinuousCollisionImpulsePolicy.TryResolveImpactNormal(
 11368                normalForSource,
 11369                out Vector2d normal))
 370        {
 3371            return false;
 372        }
 373
 8374        Fixed64 deltaTime = Context.DeltaTime;
 8375        Fixed64 constrainedInverseMass = target.GetConstrainedInverseMass(normal.ToVector3d(Fixed64.Zero));
 8376        if (constrainedInverseMass <= Fixed64.Zero)
 1377            return false;
 378
 7379        Vector3d normal3D = normal.ToVector3d(Fixed64.Zero);
 7380        Fixed64 hitTime = FixedMath.Clamp01(hitDistance / sourceLength);
 7381        Vector3d sourceVelocity = SampleContinuousCollisionLinearVelocity(hitTime)
 7382            .ToVector3d(Fixed64.Zero);
 7383        Vector3d targetVelocity = target.SampleContinuousCollisionLinearVelocity(hitTime);
 7384        bool relativeVelocityResolved = Vector3d.TrySubtract(
 7385            sourceVelocity,
 7386            targetVelocity,
 7387            out Vector3d relativeVelocity);
 7388        Fixed64 normalVelocity = Vector3d.Dot(relativeVelocity, normal3D);
 7389        Fixed64 restitution = ResolveContinuousCollisionRestitution(target, -normalVelocity);
 7390        bool responseFactorResolved = Fixed64.TryAdd(
 7391            Fixed64.One,
 7392            restitution,
 7393            out Fixed64 responseFactor);
 7394        bool closingSpeedResolved = Fixed64.TrySubtract(
 7395            Fixed64.Zero,
 7396            normalVelocity,
 7397            out Fixed64 closingSpeed);
 7398        bool responseSpeedResolved = Fixed64.TryMultiplyDivide(
 7399            closingSpeed,
 7400            responseFactor,
 7401            Fixed64.One,
 7402            out Fixed64 responseSpeed);
 7403        Vector3d targetResponseNormal = target.ProjectLinearMotion(-normal3D);
 7404        bool targetDeltaResolved = ContinuousCollisionImpulsePolicy.TryResolveVelocityDelta(
 7405            targetResponseNormal,
 7406            responseSpeed,
 7407            target.EffectiveInverseMass,
 7408            constrainedInverseMass,
 7409            out Vector3d targetVelocityDelta);
 7410        bool targetVelocityResolved = Vector3d.TryAdd(
 7411            targetVelocity,
 7412            target.ProjectLinearMotion(targetVelocityDelta),
 7413            out Vector3d targetPostVelocity);
 7414        if (!(relativeVelocityResolved
 7415            & normalVelocity < -Fixed64.Epsilon
 7416            & responseFactorResolved
 7417            & closingSpeedResolved
 7418            & responseSpeedResolved
 7419            & targetDeltaResolved
 7420            & targetVelocityResolved))
 421        {
 1422            return false;
 423        }
 424
 6425        return target.ApplyContinuousCollisionHandoff(
 6426            target.SampleContinuousCollisionPosition(hitTime),
 6427            target.SampleContinuousCollisionRotation(hitTime),
 6428            targetPostVelocity,
 6429            target.SampleContinuousCollisionAngularVelocity(hitTime),
 6430            deltaTime * (Fixed64.One - hitTime),
 6431            ignoredCollider2D: Collider);
 432    }
 433
 434}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/2D/SolidBody2D.ContinuousCollision.Rotational.cs

#LineLine coverage
 1//=======================================================================
 2// SolidBody2D.ContinuousCollision.Rotational.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2026-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using FixedMathSharp;
 9using Gravitas.Colliders;
 10using Gravitas.CollisionHandling;
 11using Gravitas.Queries;
 12using Gravitas.Support;
 13using SwiftCollections;
 14using System;
 15
 16namespace Gravitas;
 17
 18public sealed partial class SolidBody2D
 19{
 20    private bool HasNearbyRotationalContinuousCollisionTarget(
 21        Vector2d startPosition,
 22        Vector2d displacement,
 23        Fixed64 pivotRadius)
 24    {
 31725        SwiftList<int> candidateIds = Context.Physics2D.QueryPlanarContinuousCollisionCandidates(
 31726            DynamicCcdCandidateIndex2D.CreateSweptCircleBounds(
 31727                startPosition,
 31728                displacement,
 31729                pivotRadius));
 147230        for (int i = 0; i < candidateIds.Count; i++)
 31        {
 42132            SolidBody2D target = Context.Physics2D.GetContinuousCollisionCandidate(candidateIds[i]);
 42133            if (IsRotatingContinuousCollisionTarget(target))
 34            {
 235                return true;
 36            }
 37        }
 38
 31539        return false;
 40    }
 41
 42    private bool IsRotatingContinuousCollisionTarget(SolidBody2D target)
 43    {
 42144        if (ReferenceEquals(target, this)
 42145            || !IsMovingRotationalContinuousCollisionTarget(target))
 46        {
 35147            return false;
 48        }
 49
 7050        target.EnsureContinuousCollisionFramePrepared(Context.LateSimulateToken);
 7051        return target.HasContinuousCollisionRotationalMotion;
 52    }
 53
 54    private bool ShouldUseRotationalContinuousCollisionArbiter(
 55        Vector2d startPosition,
 56        Vector2d proposedPosition,
 57        Fixed64 startRotation,
 58        Fixed64 proposedRotation,
 59        bool forceContinuous)
 60    {
 228161        ContinuousCollisionMode mode = ContinuousCollisionMode.Continuous;
 228162        if (!forceContinuous && !ShouldUseContinuousCollision(out mode))
 172663            return false;
 64
 55565        Fixed64 pivotRadius = ResolveContinuousCollisionProxyRadius();
 55566        if (pivotRadius <= Fixed64.Epsilon)
 467            return false;
 68
 55169        Vector2d displacement = proposedPosition - startPosition;
 55170        Fixed64 angularDistance = (proposedRotation - startRotation).Abs();
 55171        bool targetRequiresRotationalSampling = angularDistance <= Fixed64.Epsilon
 55172            && HasNearbyRotationalContinuousCollisionTarget(
 55173                startPosition,
 55174                displacement,
 55175                pivotRadius);
 55176        if (angularDistance <= Fixed64.Epsilon && !targetRequiresRotationalSampling)
 31577            return false;
 78
 23679        Fixed64 angularArcLength = angularDistance * pivotRadius;
 23680        if (!targetRequiresRotationalSampling
 23681            && angularArcLength <= Fixed64.Epsilon)
 282            return false;
 23483        if (targetRequiresRotationalSampling)
 284            return true;
 85
 23286        if (mode != ContinuousCollisionMode.Auto)
 22887            return true;
 88
 489        return angularArcLength > pivotRadius
 490            || !ContinuousCollisionMath.IsWithinProxyRadius(
 491                displacement,
 492                displacement.MagnitudeSquared,
 493                pivotRadius);
 94    }
 95
 96    private bool TryResolveRotationalContinuousCollision(
 97        Vector2d startPosition,
 98        ref Vector2d proposedPosition,
 99        Fixed64 startRotation,
 100        ref Fixed64 proposedRotation) =>
 154101        TryResolveRotationalContinuousCollision(
 154102            startPosition,
 154103            ref proposedPosition,
 154104            startRotation,
 154105            ref proposedRotation,
 154106            Context.DeltaTime,
 154107            Fixed64.Zero,
 154108            forceContinuous: false);
 109
 110    private bool TryResolveRotationalContinuousCollision(
 111        Vector2d startPosition,
 112        ref Vector2d proposedPosition,
 113        Fixed64 startRotation,
 114        ref Fixed64 proposedRotation,
 115        Fixed64 remainingTime,
 116        Fixed64 elapsedTime,
 117        bool forceContinuous)
 118    {
 232119        Vector2d originalPosition = _position;
 232120        Fixed64 originalRotation = _rotation;
 232121        LSCollider2D? originalIgnoredCollider = _continuousCollisionHandoffIgnoredCollider2D;
 232122        LSCollider? originalIgnoredMixedCollider = _continuousCollisionHandoffIgnoredCollider3D;
 232123        bool resolved = false;
 232124        Vector2d currentPosition = startPosition;
 232125        Fixed64 currentRotation = startRotation;
 232126        Fixed64 pivotRadius = ResolveContinuousCollisionProxyRadius();
 232127        Vector2d kinematicLinearVelocity = (proposedPosition - startPosition) / remainingTime;
 232128        Fixed64 kinematicAngularVelocity =
 232129            CanonicalizeRotation(proposedRotation - startRotation) / remainingTime;
 232130        Vector2d motionSegmentStartPosition = startPosition;
 232131        Fixed64 motionSegmentStartRotation = startRotation;
 232132        Fixed64 motionSegmentStartElapsedTime = elapsedTime;
 232133        Vector2d motionSegmentLinearVelocity = IsKinematic
 232134            ? kinematicLinearVelocity
 232135            : ProjectLinearMotion(_linearVelocity);
 232136        Fixed64 motionSegmentAngularVelocity = IsKinematic
 232137            ? kinematicAngularVelocity
 232138            : _angularVelocity;
 232139        int maxToiIterations = Context.Settings.ContinuousCollisionMaxToiIterations;
 232140        int conservativeRefinementCount = 0;
 141        try
 142        {
 372143            while (remainingTime > Fixed64.Epsilon)
 144            {
 372145                Fixed64 segmentEndElapsedTime = elapsedTime + remainingTime;
 372146                Fixed64 segmentElapsedTime = segmentEndElapsedTime
 372147                    - motionSegmentStartElapsedTime;
 372148                Vector2d segmentEnd = motionSegmentStartPosition
 372149                    + motionSegmentLinearVelocity * segmentElapsedTime;
 372150                Fixed64 segmentEndRotation = CanonicalizeRotation(
 372151                    motionSegmentStartRotation
 372152                    + motionSegmentAngularVelocity * segmentElapsedTime);
 372153                Vector2d displacement = segmentEnd - currentPosition;
 372154                Fixed64 angularDelta = CanonicalizeRotation(
 372155                    segmentEndRotation - currentRotation);
 372156                Fixed64 angularDistance = angularDelta.Abs();
 372157                if (angularDistance <= Fixed64.Epsilon)
 158                {
 4159                    angularDelta = Fixed64.Zero;
 4160                    angularDistance = Fixed64.Zero;
 161                }
 372162                int hitCount = GatherRotationalContinuousCollisionCandidates(
 372163                    currentPosition,
 372164                    segmentEnd,
 372165                    displacement,
 372166                    pivotRadius);
 372167                Fixed64 safeTime = Fixed64.Zero;
 372168                Contact2D contact = default;
 372169                bool hasContact = false;
 372170                Fixed64 contactTime = Fixed64.Zero;
 372171                LSCollider2D? target = null;
 372172                bool foundSameDimension = hitCount != 0
 372173                    && TryFindEarliestRotationalContinuousCollision(
 372174                        currentPosition,
 372175                        displacement,
 372176                        currentRotation,
 372177                        angularDelta,
 372178                        angularDistance,
 372179                        pivotRadius,
 372180                        elapsedTime,
 372181                        remainingTime,
 372182                        out safeTime,
 372183                        out contact,
 372184                        out hasContact,
 372185                        out contactTime,
 372186                        out target);
 372187                bool foundMixed = TryFindEarliestMixedRotationalContinuousCollision(
 372188                    currentPosition,
 372189                    displacement,
 372190                    currentRotation,
 372191                    angularDelta,
 372192                    angularDistance,
 372193                    pivotRadius,
 372194                    elapsedTime,
 372195                    remainingTime,
 372196                    out RotationalMixed3DHit mixedHit);
 197                // 2D targets precede 3D targets at equal normalized time.
 372198                bool useMixed = foundMixed
 372199                    && (!foundSameDimension || mixedHit.SafeTime < safeTime);
 372200                if (!foundSameDimension && !foundMixed)
 201                {
 206202                    currentPosition = segmentEnd;
 206203                    currentRotation = segmentEndRotation;
 206204                    break;
 205                }
 206
 166207                Fixed64 conservativeTime = useMixed ? mixedHit.SafeTime : safeTime;
 166208                Fixed64 witnessedTime = useMixed ? mixedHit.ContactTime : contactTime;
 166209                bool responseWitnessIsEarliest = witnessedTime == conservativeTime;
 166210                bool canResolveMovingPair = responseWitnessIsEarliest && (useMixed
 166211                    ? mixedHit.HasContact
 166212                        && TryApplyMixedRotationalContinuousCollisionResponse(
 166213                            mixedHit.Target,
 166214                            mixedHit.Contact,
 166215                            mixedHit.ContactTime,
 166216                            currentPosition,
 166217                            displacement,
 166218                            currentRotation,
 166219                            angularDelta,
 166220                            elapsedTime,
 166221                            remainingTime)
 166222                    : hasContact
 166223                        && target!.Body is SolidBody2D targetBody
 166224                        && TryApplyRotationalContinuousCollisionResponse(
 166225                            targetBody,
 166226                            contact,
 166227                            contactTime,
 166228                            currentPosition,
 166229                            currentRotation,
 166230                            displacement,
 166231                            angularDelta,
 166232                            elapsedTime,
 166233                            remainingTime));
 166234                bool deferUnresolvedWitness = !responseWitnessIsEarliest
 166235                    && witnessedTime > conservativeTime
 166236                    && conservativeTime > Fixed64.Epsilon
 166237                    && conservativeRefinementCount
 166238                        < ContinuousCollisionMath.RotationalIntervalMaxDepth;
 166239                Fixed64 eventTime = canResolveMovingPair
 166240                    ? witnessedTime
 166241                    : conservativeTime;
 166242                Fixed64 consumedTime = remainingTime * eventTime;
 166243                Fixed64 impactElapsedTime = elapsedTime + consumedTime;
 166244                if (!canResolveMovingPair
 166245                    && !deferUnresolvedWitness
 166246                    && (!CanAppendContinuousCollisionFrameSegment(impactElapsedTime)
 166247                        || !Context.Physics2D.TryReserveContinuousCollisionCandidateRefresh(this)))
 248                {
 2249                    LastContinuousCollisionToiIterationLimitReached = true;
 2250                    break;
 251                }
 252
 164253                Fixed64 impactSegmentElapsedTime = impactElapsedTime
 164254                    - motionSegmentStartElapsedTime;
 164255                currentPosition = motionSegmentStartPosition
 164256                    + motionSegmentLinearVelocity * impactSegmentElapsedTime;
 164257                currentRotation = CanonicalizeRotation(
 164258                    motionSegmentStartRotation
 164259                    + motionSegmentAngularVelocity * impactSegmentElapsedTime);
 164260                resolved = true;
 261
 164262                Fixed64 remainingAfterImpact = remainingTime - consumedTime;
 164263                if (deferUnresolvedWitness)
 264                {
 70265                    conservativeRefinementCount++;
 70266                    remainingTime = remainingAfterImpact;
 70267                    elapsedTime = impactElapsedTime;
 70268                    continue;
 269                }
 270
 94271                LastContinuousCollisionToiIterationCount++;
 94272                if (!canResolveMovingPair)
 273                {
 18274                    if (!IsKinematic)
 275                    {
 3276                        StopRotationalContinuousCollision(
 3277                            useMixed
 3278                                ? mixedHit.Contact.Normal3DTo2D.ToVector2d()
 3279                                : contact.Normal);
 280                    }
 18281                    AppendContinuousCollisionFrameSegment(
 18282                        currentPosition,
 18283                        currentRotation,
 18284                        Vector2d.Zero,
 18285                        Fixed64.Zero,
 18286                        impactElapsedTime);
 18287                    Context.Physics2D.RefreshContinuousCollisionCandidate(this);
 18288                    break;
 289                }
 290
 76291                if (!IsKinematic)
 292                {
 24293                    AppendContinuousCollisionFrameSegment(
 24294                        currentPosition,
 24295                        currentRotation,
 24296                        _linearVelocity,
 24297                        _angularVelocity,
 24298                        impactElapsedTime);
 24299                    Context.Physics2D.RefreshContinuousCollisionCandidate(this);
 24300                    motionSegmentStartPosition = currentPosition;
 24301                    motionSegmentStartRotation = currentRotation;
 24302                    motionSegmentStartElapsedTime = impactElapsedTime;
 24303                    motionSegmentLinearVelocity = ProjectLinearMotion(_linearVelocity);
 24304                    motionSegmentAngularVelocity = _angularVelocity;
 305                }
 306
 76307                if (useMixed)
 308                {
 46309                    _continuousCollisionHandoffIgnoredCollider3D = mixedHit.Target;
 46310                    _continuousCollisionHandoffIgnoredCollider2D = null;
 311                }
 312                else
 313                {
 30314                    _continuousCollisionHandoffIgnoredCollider2D = target;
 30315                    _continuousCollisionHandoffIgnoredCollider3D = null;
 316                }
 76317                remainingTime = remainingAfterImpact;
 76318                elapsedTime = impactElapsedTime;
 76319                if (LastContinuousCollisionToiIterationCount >= maxToiIterations)
 320                {
 6321                    LastContinuousCollisionToiIterationLimitReached = remainingTime > Fixed64.Epsilon;
 6322                    if (IsKinematic)
 323                    {
 4324                        currentPosition = proposedPosition;
 4325                        currentRotation = proposedRotation;
 326                    }
 4327                    break;
 328                }
 329            }
 330
 232331            proposedPosition = currentPosition;
 232332            proposedRotation = currentRotation;
 232333            return resolved;
 334        }
 335        finally
 336        {
 232337            _continuousCollisionHandoffIgnoredCollider2D = originalIgnoredCollider;
 232338            _position = originalPosition;
 232339            _rotation = originalRotation;
 232340            Collider.RebuildRuntimeShapeOnly();
 232341            _continuousCollisionHandoffIgnoredCollider3D = originalIgnoredMixedCollider;
 232342        }
 232343    }
 344
 345    private bool TryResolveKinematicRotationalContinuousCollision(
 346        Vector2d startPosition,
 347        ref Vector2d proposedPosition,
 348        Fixed64 startRotation,
 349        ref Fixed64 proposedRotation)
 75350        => TryResolveRotationalContinuousCollision(
 75351            startPosition,
 75352            ref proposedPosition,
 75353            startRotation,
 75354            ref proposedRotation,
 75355            Context.DeltaTime,
 75356            Fixed64.Zero,
 75357            forceContinuous: true);
 358
 359    internal int GatherRotationalContinuousCollisionCandidates(
 360        Vector2d startPosition,
 361        Vector2d proposedPosition,
 362        Vector2d displacement,
 363        Fixed64 pivotRadius)
 364    {
 374365        _rotationalContinuousCollisionCandidateIds.FastClear();
 374366        if (pivotRadius == Fixed64.MaxValue)
 5367            return GatherAllRegisteredRotationalContinuousCollisionCandidates();
 368
 369369        int staticHitCount = displacement.MagnitudeSquared <= Fixed64.Epsilon
 369370            ? Context.Query2D.OverlapCircleAgainstStaticAll(
 369371                startPosition,
 369372                pivotRadius,
 369373                PhysicsLayerMask.All,
 369374                _continuousCollisionHits,
 369375                Collider,
 369376                includeTriggers: false)
 369377            : Context.Query2D.SweepCircleAgainstStaticAll(
 369378                startPosition,
 369379                proposedPosition,
 369380                pivotRadius,
 369381                PhysicsLayerMask.All,
 369382                _continuousCollisionHits,
 369383                Collider,
 369384                includeTriggers: false);
 385
 369386        SwiftList<int> candidateIds = Context.Physics2D.QueryPlanarContinuousCollisionCandidates(
 369387            DynamicCcdCandidateIndex2D.CreateSweptCircleBounds(
 369388                startPosition,
 369389                displacement,
 369390                pivotRadius));
 369391        _rotationalContinuousCollisionCandidateIds.EnsureCapacity(candidateIds.Count);
 1622392        for (int i = 0; i < candidateIds.Count; i++)
 442393            _rotationalContinuousCollisionCandidateIds.Add(candidateIds[i]);
 394
 369395        return staticHitCount + _rotationalContinuousCollisionCandidateIds.Count;
 396    }
 397
 398    private int GatherAllRegisteredRotationalContinuousCollisionCandidates()
 399    {
 5400        _continuousCollisionHits.FastClear();
 5401        _rotationalContinuousCollisionCandidateIds.FastClear();
 5402        int colliderCount = Context.Physics2D.ColliderCount;
 5403        _continuousCollisionHits.EnsureCapacity(colliderCount);
 26404        for (int serviceIndex = 0; serviceIndex < colliderCount; serviceIndex++)
 405        {
 8406            LSCollider2D target = Context.Physics2D.GetColliderByServiceIndex(serviceIndex);
 8407            if (target.Body is SolidBody2D targetBody
 8408                && IsMovingRotationalContinuousCollisionTarget(targetBody))
 409            {
 1410                _rotationalContinuousCollisionCandidateIds.Add(targetBody.DynamicId);
 1411                continue;
 412            }
 413
 7414            if (!IsValidContinuousCollisionTarget(target))
 415                continue;
 416
 2417            _continuousCollisionHits.Add(new Physics2DHit(
 2418                target,
 2419                target.Center,
 2420                Vector2d.Zero,
 2421                Fixed64.Zero));
 422        }
 423
 5424        return _continuousCollisionHits.Count
 5425            + _rotationalContinuousCollisionCandidateIds.Count;
 426    }
 427
 428    private bool TryFindEarliestRotationalContinuousCollision(
 429        Vector2d startPosition,
 430        Vector2d displacement,
 431        Fixed64 startRotation,
 432        Fixed64 angularDelta,
 433        Fixed64 angularDistance,
 434        Fixed64 pivotRadius,
 435        Fixed64 elapsedTime,
 436        Fixed64 remainingTime,
 437        out Fixed64 safeTime,
 438        out Contact2D contact,
 439        out bool hasContact,
 440        out Fixed64 contactTime,
 441        out LSCollider2D? hitTarget)
 442    {
 370443        safeTime = Fixed64.Zero;
 370444        contact = default;
 370445        hasContact = false;
 370446        contactTime = Fixed64.Zero;
 370447        hitTarget = null;
 448
 370449        bool foundCollision = false;
 370450        Fixed64 earliestTime = Fixed64.One;
 370451        int earliestTargetId = int.MaxValue;
 370452        Contact2D earliestContact = default;
 370453        bool earliestHasContact = false;
 370454        Fixed64 earliestContactTime = Fixed64.Zero;
 370455        LSCollider2D? earliestTarget = null;
 456
 370457        int staticHitCount = _continuousCollisionHits.Count;
 892458        for (int hitIndex = 0; hitIndex < staticHitCount; hitIndex++)
 459        {
 76460            LSCollider2D target = _continuousCollisionHits[hitIndex].Collider;
 76461            if (!IsValidContinuousCollisionTarget(target)
 76462                || ColliderSettings2D.GetCollisionType(Collider.Shape, target.Shape) == CollisionType2D.None
 76463                || !TryFindEarliestRotationalContinuousCollisionAgainstTarget(
 76464                    target,
 76465                    startPosition,
 76466                    displacement,
 76467                    startRotation,
 76468                    angularDelta,
 76469                    angularDistance,
 76470                    pivotRadius,
 76471                    elapsedTime,
 76472                    remainingTime,
 76473                    out Fixed64 candidateTime,
 76474                    out Contact2D candidateContact,
 76475                    out bool candidateHasContact,
 76476                    out Fixed64 candidateContactTime))
 477            {
 478                continue;
 479            }
 480
 31481            if (!ContinuousCollisionMath.ShouldReplaceContinuousCollisionHit(
 31482                    candidateTime,
 31483                    target.Id,
 31484                    foundCollision,
 31485                    earliestTime,
 31486                    earliestTargetId))
 487            {
 488                continue;
 489            }
 490
 30491            foundCollision = true;
 30492            earliestTime = candidateTime;
 30493            earliestTargetId = target.Id;
 30494            earliestContact = candidateContact;
 30495            earliestHasContact = candidateHasContact;
 30496            earliestContactTime = candidateContactTime;
 30497            earliestTarget = target;
 498        }
 499
 370500        for (int candidateIndex = 0;
 812501            candidateIndex < _rotationalContinuousCollisionCandidateIds.Count;
 442502            candidateIndex++)
 503        {
 442504            int dynamicId = _rotationalContinuousCollisionCandidateIds[candidateIndex];
 442505            SolidBody2D targetBody = Context.Physics2D.GetContinuousCollisionCandidate(dynamicId);
 442506            if (!IsMovingRotationalContinuousCollisionTarget(targetBody)
 442507                || ColliderSettings2D.GetCollisionType(
 442508                    Collider.Shape,
 442509                    targetBody.Collider.Shape) == CollisionType2D.None
 442510                || !TryFindEarliestRotationalContinuousCollisionAgainstTarget(
 442511                    targetBody.Collider,
 442512                    startPosition,
 442513                    displacement,
 442514                    startRotation,
 442515                    angularDelta,
 442516                    angularDistance,
 442517                    pivotRadius,
 442518                    elapsedTime,
 442519                    remainingTime,
 442520                    out Fixed64 candidateTime,
 442521                    out Contact2D candidateContact,
 442522                    out bool candidateHasContact,
 442523                    out Fixed64 candidateContactTime))
 524            {
 525                continue;
 526            }
 527
 43528            if (!ContinuousCollisionMath.ShouldReplaceContinuousCollisionHit(
 43529                    candidateTime,
 43530                    targetBody.Collider.Id,
 43531                    foundCollision,
 43532                    earliestTime,
 43533                    earliestTargetId))
 534            {
 535                continue;
 536            }
 537
 41538            foundCollision = true;
 41539            earliestTime = candidateTime;
 41540            earliestTargetId = targetBody.Collider.Id;
 41541            earliestContact = candidateContact;
 41542            earliestHasContact = candidateHasContact;
 41543            earliestContactTime = candidateContactTime;
 41544            earliestTarget = targetBody.Collider;
 545        }
 546
 370547        if (!foundCollision)
 301548            return false;
 549
 69550        safeTime = earliestTime;
 69551        contact = earliestContact;
 69552        hasContact = earliestHasContact;
 69553        contactTime = earliestContactTime;
 69554        hitTarget = earliestTarget;
 69555        return true;
 556    }
 557
 558    private bool TryFindEarliestRotationalContinuousCollisionAgainstTarget(
 559        LSCollider2D target,
 560        Vector2d startPosition,
 561        Vector2d displacement,
 562        Fixed64 startRotation,
 563        Fixed64 angularDelta,
 564        Fixed64 angularDistance,
 565        Fixed64 pivotRadius,
 566        Fixed64 elapsedTime,
 567        Fixed64 remainingTime,
 568        out Fixed64 safeTime,
 569        out Contact2D contact,
 570        out bool hasContact,
 571        out Fixed64 contactTime)
 572    {
 106573        safeTime = Fixed64.Zero;
 106574        contact = default;
 106575        hasContact = false;
 106576        contactTime = Fixed64.Zero;
 106577        SolidBody2D? targetBody = target.Body;
 106578        bool samplesTargetMotion = targetBody != null
 106579            && IsMovingRotationalContinuousCollisionTarget(targetBody);
 106580        Vector2d originalTargetPosition = targetBody?._position ?? Vector2d.Zero;
 106581        Fixed64 originalTargetRotation = targetBody?._rotation ?? Fixed64.Zero;
 106582        if (samplesTargetMotion)
 45583            targetBody!.EnsureContinuousCollisionFramePrepared(Context.LateSimulateToken);
 584
 585        try
 586        {
 106587            Span<ContinuousCollisionMath.RotationalInterval> intervals =
 106588                stackalloc ContinuousCollisionMath.RotationalInterval[
 106589                    ContinuousCollisionMath.RotationalIntervalMaxDepth + 2];
 106590            int intervalCount = 1;
 106591            int processedNodeCount = 0;
 106592            bool hasKnownContact = false;
 106593            Fixed64 knownContactTime = Fixed64.One;
 106594            Contact2D knownContact = default;
 106595            intervals[0] = new ContinuousCollisionMath.RotationalInterval(
 106596                Fixed64.Zero,
 106597                Fixed64.One,
 106598                depth: 0);
 599
 1828600            while (intervalCount > 0)
 601            {
 1796602                ContinuousCollisionMath.RotationalInterval interval = intervals[--intervalCount];
 1796603                Fixed64 midpoint = (interval.LowerTime + interval.UpperTime) * Fixed64.Half;
 1796604                Fixed64 intervalSpan = interval.UpperTime - interval.LowerTime;
 1796605                Fixed64 midpointFrameFraction = ResolveRotationalFrameFraction(
 1796606                    elapsedTime,
 1796607                    remainingTime,
 1796608                    midpoint);
 1796609                SampleRotationalContinuousPairPose(
 1796610                    startPosition,
 1796611                    displacement,
 1796612                    startRotation,
 1796613                    angularDelta,
 1796614                    midpoint,
 1796615                    targetBody,
 1796616                    midpointFrameFraction,
 1796617                    samplesTargetMotion);
 1796618                processedNodeCount++;
 619
 1796620                bool hasMotionBound = ContinuousCollisionMath.TryResolveRotationalIntervalMotionBound(
 1796621                    displacement,
 1796622                    angularDistance,
 1796623                    pivotRadius,
 1796624                    intervalSpan,
 1796625                    out Fixed64 motionBound);
 1796626                if (hasMotionBound && samplesTargetMotion)
 627                {
 799628                    Fixed64 lowerFrameFraction = ResolveRotationalFrameFraction(
 799629                        elapsedTime,
 799630                        remainingTime,
 799631                        interval.LowerTime);
 799632                    Fixed64 upperFrameFraction = ResolveRotationalFrameFraction(
 799633                        elapsedTime,
 799634                        remainingTime,
 799635                        interval.UpperTime);
 799636                    Fixed64 targetRadius = targetBody!.ResolveContinuousCollisionProxyRadius();
 799637                    bool targetBoundResolved = targetBody.TryResolveContinuousCollisionMotionBound(
 799638                        lowerFrameFraction,
 799639                        upperFrameFraction,
 799640                        targetRadius,
 799641                        out Fixed64 targetMotionBound);
 799642                    bool combinedBoundResolved = Fixed64.TryAdd(
 799643                        motionBound,
 799644                        targetMotionBound,
 799645                        out motionBound);
 799646                    hasMotionBound = targetBoundResolved & combinedBoundResolved;
 647                }
 1796648                bool sampleHasContact = CollisionDetection2D.TryCollide(
 1796649                    Collider,
 1796650                    target,
 1796651                    out Contact2D sampleContact);
 1796652                if (sampleHasContact
 1796653                    && (!hasKnownContact || midpoint < knownContactTime))
 654                {
 498655                    hasKnownContact = true;
 498656                    knownContactTime = midpoint;
 498657                    knownContact = sampleContact;
 658                }
 659
 1796660                if (!sampleHasContact
 1796661                    && hasMotionBound
 1796662                    && IsRotationalIntervalSeparated(target, motionBound))
 663                {
 664                    continue;
 665                }
 666
 1212667                if (ContinuousCollisionMath.TryResolveRotationalSearchLimit(
 1212668                        interval,
 1212669                        processedNodeCount,
 1212670                        hasKnownContact,
 1212671                        knownContactTime,
 1212672                        out safeTime,
 1212673                        out contactTime,
 1212674                        out hasContact))
 675                {
 74676                    contact = knownContact;
 74677                    return true;
 678                }
 679
 1138680                int childDepth = interval.Depth + 1;
 1138681                if (sampleHasContact)
 682                {
 683                    // A real midpoint contact is already an upper bound. Only the
 684                    // earlier half can contain an earlier first contact.
 481685                    intervals[intervalCount++] = new ContinuousCollisionMath.RotationalInterval(
 481686                        interval.LowerTime,
 481687                        midpoint,
 481688                        childDepth);
 481689                    continue;
 690                }
 691
 657692                intervals[intervalCount++] = new ContinuousCollisionMath.RotationalInterval(
 657693                    midpoint,
 657694                    interval.UpperTime,
 657695                    childDepth);
 657696                intervals[intervalCount++] = new ContinuousCollisionMath.RotationalInterval(
 657697                    interval.LowerTime,
 657698                    midpoint,
 657699                    childDepth);
 700            }
 701
 32702            safeTime = knownContactTime;
 32703            contact = knownContact;
 32704            hasContact = hasKnownContact;
 32705            contactTime = knownContactTime;
 32706            return hasKnownContact;
 707        }
 708        finally
 709        {
 106710            if (targetBody != null)
 711            {
 103712                targetBody._position = originalTargetPosition;
 103713                targetBody._rotation = originalTargetRotation;
 103714                targetBody.Collider.RebuildRuntimeShapeOnly();
 715            }
 106716        }
 106717    }
 718
 719    private bool IsRotationalIntervalSeparated(LSCollider2D target, Fixed64 motionBound)
 720    {
 1298721        if (TryGetCirclePairSeparationGap(target, out Fixed64 closestPointGap))
 986722            return closestPointGap > motionBound;
 723
 312724        return ContinuousCollisionMath.AreBoundsSeparatedByMoreThan(
 312725            Collider.Bounds,
 312726            target.Bounds,
 312727            motionBound);
 728    }
 729
 730    private bool TryGetCirclePairSeparationGap(LSCollider2D target, out Fixed64 separationGap)
 731    {
 1298732        if (Collider is LSCircleCollider2D sourceCircle)
 48733            return TryGetCircleSeparationGap(sourceCircle, target, out separationGap);
 734
 1250735        if (target is LSCircleCollider2D targetCircle)
 938736            return TryGetCircleSeparationGap(targetCircle, Collider, out separationGap);
 737
 312738        separationGap = default;
 312739        return false;
 740    }
 741
 742    internal static bool TryGetCircleSeparationGap(
 743        LSCircleCollider2D circle,
 744        LSCollider2D other,
 745        out Fixed64 separationGap)
 746    {
 990747        LSCircleCollider2D? otherCircle = other as LSCircleCollider2D;
 990748        Fixed64 otherRadius = otherCircle?.ScaledRadius ?? Fixed64.Zero;
 749        Fixed64 distance;
 990750        if (otherCircle != null)
 751        {
 26752            if (!Vector2d.TryGetDistance(
 26753                    circle.Center,
 26754                    otherCircle.Center,
 26755                    out distance))
 756            {
 1757                separationGap = default;
 1758                return false;
 759            }
 760        }
 964761        else if (other.ContainsPoint(circle.Center))
 762        {
 1763            distance = Fixed64.Zero;
 764        }
 963765        else if (!other.TryGetClosestBoundaryAnchor(
 963766                     circle.Center,
 963767                     out _,
 963768                     out distance))
 769        {
 1770            separationGap = default;
 1771            return false;
 772        }
 773
 774        // An unrepresentable center distance or radius sum cannot certify
 775        // separation. The uncertainty scale below may saturate only upward.
 988776        if (!Fixed64.TryAdd(
 988777                circle.ScaledRadius,
 988778                otherRadius,
 988779                out Fixed64 combinedRadius))
 780        {
 1781            separationGap = default;
 1782            return false;
 783        }
 784
 987785        Fixed64 rawGap = distance - combinedRadius;
 987786        Fixed64 characteristicScale = distance
 987787            + combinedRadius
 987788            + Vector2d.GetMagnitude(other.Bounds.Size);
 987789        _ = ContinuousCollisionMath.TrySubtractClosestFeatureUncertainty(
 987790            rawGap,
 987791            characteristicScale,
 987792            out separationGap);
 793
 987794        return separationGap > Fixed64.Zero;
 795    }
 796
 797    private void SampleRotationalContinuousPairPose(
 798        Vector2d startPosition,
 799        Vector2d displacement,
 800        Fixed64 startRotation,
 801        Fixed64 angularDelta,
 802        Fixed64 sampleTime,
 803        SolidBody2D? targetBody,
 804        Fixed64 targetFrameFraction,
 805        bool samplesTargetMotion)
 806    {
 1796807        _position = startPosition + displacement * sampleTime;
 1796808        _rotation = startRotation + angularDelta * sampleTime;
 1796809        Collider.RebuildRuntimeShapeOnly();
 810
 1796811        if (!samplesTargetMotion)
 997812            return;
 813
 799814        targetBody!._position = targetBody.SampleContinuousCollisionPosition(targetFrameFraction);
 799815        targetBody._rotation = targetBody.SampleContinuousCollisionRotation(targetFrameFraction);
 799816        targetBody.Collider.RebuildRuntimeShapeOnly();
 799817    }
 818
 819    private bool IsMovingRotationalContinuousCollisionTarget(SolidBody2D target)
 820    {
 660821        if (ReferenceEquals(target, this) || !target.Active)
 374822            return false;
 823
 286824        if (target.IsKinematic)
 43825            target.EnsureContinuousCollisionFramePrepared(Context.LateSimulateToken);
 826
 286827        return (IsKinematic || !target.ShouldOwnContinuousCollisionMovingPair(
 286828                HasContinuousCollisionRotationalMotion))
 286829            && !ContinuousCollisionCandidateOrdering.IsIgnoredTarget(
 286830                target.Collider,
 286831                _continuousCollisionHandoffIgnoredCollider2D)
 286832            && !target.Collider.IsTrigger
 286833            && Context.Physics2D.RequireCollisionPair(Collider, target.Collider)
 286834            && (target.IsKinematic
 286835                ? target.HasContinuousCollisionMotion
 286836                : target.IsDynamic);
 837    }
 838
 839}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/2D/SolidBody2D.ContinuousCollision.Rotational.Mixed.cs

#LineLine coverage
 1//=======================================================================
 2// SolidBody2D.ContinuousCollision.Rotational.Mixed.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2026-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using FixedMathSharp;
 9using FixedMathSharp.Geometry;
 10using Gravitas.Colliders;
 11using Gravitas.CollisionHandling;
 12using Gravitas.Materials;
 13using Gravitas.Support;
 14using SwiftCollections;
 15using SwiftCollections.Query;
 16using System;
 17
 18namespace Gravitas;
 19
 20public sealed partial class SolidBody2D
 21{
 464522    private readonly SwiftList<LSCollider> _rotationalMixedStatic3DCandidates = new();
 464523    private readonly SwiftList<int> _rotationalMixedMoving3DCandidateIds = new();
 24
 25    internal void SetMixedContinuousCollisionSamplePose(
 26        Vector2d position,
 27        Fixed64 rotation)
 28    {
 255529        _position = position;
 255530        _rotation = rotation;
 255531        Collider.RebuildRuntimeShapeOnly();
 255532    }
 33
 34    internal FixedBoundVolume ResolveMixedContinuousCollisionTrajectoryBounds(Fixed64 radius)
 35    {
 50036        DynamicCcdPlanarBounds planarBounds =
 50037            ResolveContinuousCollisionTrajectoryBounds(radius);
 50038        return new FixedBoundVolume(
 50039            new Vector3d(
 50040                planarBounds.MinX,
 50041                Collider.MixedSlabCenterY - Collider.MixedHalfThickness,
 50042                planarBounds.MinZ),
 50043            new Vector3d(
 50044                planarBounds.MaxX,
 50045                Collider.MixedSlabCenterY + Collider.MixedHalfThickness,
 50046                planarBounds.MaxZ));
 47    }
 48
 49    private readonly struct RotationalMixed3DHit
 50    {
 51        public RotationalMixed3DHit(
 52            Fixed64 safeTime,
 53            MixedContact contact,
 54            bool hasContact,
 55            Fixed64 contactTime,
 56            LSCollider target)
 57        {
 10158            SafeTime = safeTime;
 10159            Contact = contact;
 10160            HasContact = hasContact;
 10161            ContactTime = contactTime;
 10162            Target = target;
 10163        }
 64
 65        public Fixed64 SafeTime { get; }
 66
 67        public MixedContact Contact { get; }
 68
 69        public bool HasContact { get; }
 70
 71        public Fixed64 ContactTime { get; }
 72
 73        public LSCollider Target { get; }
 74    }
 75
 76    private bool TryFindEarliestMixedRotationalContinuousCollision(
 77        Vector2d startPosition,
 78        Vector2d displacement,
 79        Fixed64 startRotation,
 80        Fixed64 angularDelta,
 81        Fixed64 angularDistance,
 82        Fixed64 pivotRadius,
 83        Fixed64 elapsedTime,
 84        Fixed64 remainingTime,
 85        out RotationalMixed3DHit hit)
 86    {
 37287        hit = default;
 37288        if (!Context.Settings.RuntimeMode.RunsMixedContacts())
 22289            return false;
 90
 15091        GatherMixedRotationalContinuousCollisionCandidates(
 15092            startPosition,
 15093            displacement,
 15094            pivotRadius);
 95
 15096        bool found = false;
 15097        Fixed64 earliestTime = Fixed64.One;
 15098        int earliestId = int.MaxValue;
 15099        MixedContact earliestContact = default;
 150100        bool earliestHasContact = false;
 150101        Fixed64 earliestContactTime = Fixed64.Zero;
 150102        LSCollider? earliestTarget = null;
 103
 328104        for (int i = 0; i < _rotationalMixedStatic3DCandidates.Count; i++)
 105        {
 14106            LSCollider target = _rotationalMixedStatic3DCandidates[i];
 14107            ConsiderMixedRotationalContinuousCollisionCandidate(
 14108                target,
 14109                startPosition,
 14110                displacement,
 14111                startRotation,
 14112                angularDelta,
 14113                angularDistance,
 14114                pivotRadius,
 14115                elapsedTime,
 14116                remainingTime,
 14117                ref found,
 14118                ref earliestTime,
 14119                ref earliestId,
 14120                ref earliestContact,
 14121                ref earliestHasContact,
 14122                ref earliestContactTime,
 14123                ref earliestTarget);
 124        }
 125
 588126        for (int i = 0; i < _rotationalMixedMoving3DCandidateIds.Count; i++)
 127        {
 144128            SolidBody targetBody = Context.Physics.GetContinuousCollisionCandidate(
 144129                _rotationalMixedMoving3DCandidateIds[i]);
 144130            if (!IsMovingMixedRotationalContinuousCollisionTarget(targetBody))
 131            {
 132                continue;
 133            }
 134
 98135            ConsiderMixedRotationalContinuousCollisionCandidate(
 98136                targetBody.Collider,
 98137                startPosition,
 98138                displacement,
 98139                startRotation,
 98140                angularDelta,
 98141                angularDistance,
 98142                pivotRadius,
 98143                elapsedTime,
 98144                remainingTime,
 98145                ref found,
 98146                ref earliestTime,
 98147                ref earliestId,
 98148                ref earliestContact,
 98149                ref earliestHasContact,
 98150                ref earliestContactTime,
 98151                ref earliestTarget);
 152        }
 153
 150154        if (!found)
 49155            return false;
 156
 101157        hit = new RotationalMixed3DHit(
 101158            earliestTime,
 101159            earliestContact,
 101160            earliestHasContact,
 101161            earliestContactTime,
 101162            earliestTarget!);
 101163        return true;
 164    }
 165
 166    private void GatherMixedRotationalContinuousCollisionCandidates(
 167        Vector2d startPosition,
 168        Vector2d displacement,
 169        Fixed64 pivotRadius)
 170    {
 150171        _rotationalMixedStatic3DCandidates.FastClear();
 150172        _rotationalMixedMoving3DCandidateIds.FastClear();
 150173        if (pivotRadius == Fixed64.MaxValue)
 174        {
 2175            int count = Context.Physics.ColliderCount;
 2176            _rotationalMixedStatic3DCandidates.EnsureCapacity(count);
 2177            _rotationalMixedMoving3DCandidateIds.EnsureCapacity(count);
 8178            for (int i = 0; i < count; i++)
 179            {
 2180                LSCollider target = Context.Physics.GetColliderByServiceIndex(i);
 2181                if (target.Body is SolidBody targetBody
 2182                    && IsMovingMixedRotationalContinuousCollisionTarget(targetBody))
 183                {
 1184                    _rotationalMixedMoving3DCandidateIds.Add(targetBody.DynamicId);
 1185                    continue;
 186                }
 187
 1188                _rotationalMixedStatic3DCandidates.Add(target);
 189            }
 190
 2191            return;
 192        }
 193
 148194        Vector3d start3D = new(
 148195            startPosition.X,
 148196            Collider.MixedSlabCenterY,
 148197            startPosition.Y);
 148198        Vector3d end3D = new(
 148199            startPosition.X + displacement.X,
 148200            Collider.MixedSlabCenterY,
 148201            startPosition.Y + displacement.Y);
 148202        FixedBoundVolume bounds = DynamicCcdCandidateIndex.CreateBoundsBetween(
 148203            start3D,
 148204            end3D,
 148205            new Vector3d(
 148206                pivotRadius,
 148207                Collider.MixedHalfThickness,
 148208                pivotRadius));
 148209        Context.MixedCollisions.Collect3DCandidatesInMixedBounds(
 148210            bounds.Min,
 148211            bounds.Max,
 148212            PhysicsLayerMask.All,
 148213            _rotationalMixedStatic3DCandidates,
 148214            staticStyleOnly: true,
 148215            cachePartitionRefresh: true);
 216
 148217        SwiftList<int> candidateIds =
 148218            Context.Physics.QueryContinuousCollisionCandidates(bounds);
 148219        _rotationalMixedMoving3DCandidateIds.EnsureCapacity(candidateIds.Count);
 582220        for (int i = 0; i < candidateIds.Count; i++)
 143221            _rotationalMixedMoving3DCandidateIds.Add(candidateIds[i]);
 148222    }
 223
 224    private void ConsiderMixedRotationalContinuousCollisionCandidate(
 225        LSCollider target,
 226        Vector2d startPosition,
 227        Vector2d displacement,
 228        Fixed64 startRotation,
 229        Fixed64 angularDelta,
 230        Fixed64 angularDistance,
 231        Fixed64 pivotRadius,
 232        Fixed64 elapsedTime,
 233        Fixed64 remainingTime,
 234        ref bool found,
 235        ref Fixed64 earliestTime,
 236        ref int earliestId,
 237        ref MixedContact earliestContact,
 238        ref bool earliestHasContact,
 239        ref Fixed64 earliestContactTime,
 240        ref LSCollider? earliestTarget)
 241    {
 112242        if (!IsValidMixedRotationalContinuousCollisionTarget(target)
 112243            || !TryFindEarliestMixedRotationalContinuousCollisionAgainstTarget(
 112244                target,
 112245                startPosition,
 112246                displacement,
 112247                startRotation,
 112248                angularDelta,
 112249                angularDistance,
 112250                pivotRadius,
 112251                elapsedTime,
 112252                remainingTime,
 112253                Fixed64.Zero,
 112254                Fixed64.One,
 112255                out Fixed64 candidateTime,
 112256                out MixedContact candidateContact,
 112257                out bool candidateHasContact,
 112258                out Fixed64 candidateContactTime)
 112259            || !ContinuousCollisionMath.ShouldReplaceContinuousCollisionHit(
 112260                candidateTime,
 112261                target.Id,
 112262                found,
 112263                earliestTime,
 112264                earliestId))
 265        {
 11266            return;
 267        }
 268
 101269        found = true;
 101270        earliestTime = candidateTime;
 101271        earliestId = target.Id;
 101272        earliestContact = candidateContact;
 101273        earliestHasContact = candidateHasContact;
 101274        earliestContactTime = candidateContactTime;
 101275        earliestTarget = target;
 101276    }
 277
 278    private bool TryFindEarliestMixedRotationalContinuousCollisionAgainstTarget(
 279        LSCollider target,
 280        Vector2d startPosition,
 281        Vector2d displacement,
 282        Fixed64 startRotation,
 283        Fixed64 angularDelta,
 284        Fixed64 angularDistance,
 285        Fixed64 pivotRadius,
 286        Fixed64 elapsedTime,
 287        Fixed64 remainingTime,
 288        Fixed64 searchStart,
 289        Fixed64 searchEnd,
 290        out Fixed64 safeTime,
 291        out MixedContact contact,
 292        out bool hasContact,
 293        out Fixed64 contactTime)
 294    {
 121295        safeTime = Fixed64.Zero;
 121296        contact = default;
 121297        hasContact = false;
 121298        contactTime = Fixed64.Zero;
 121299        SolidBody? targetBody = target.Body;
 121300        bool samplesTargetMotion = targetBody != null
 121301            && IsMovingMixedRotationalContinuousCollisionTarget(targetBody);
 121302        Vector3d originalTargetPosition = targetBody?.Position3d ?? Vector3d.Zero;
 121303        FixedQuaternion originalTargetRotation = targetBody?.Rotation
 121304            ?? FixedQuaternion.Identity;
 121305        if (samplesTargetMotion)
 109306            targetBody!.EnsureContinuousCollisionFramePrepared(Context.LateSimulateToken);
 307
 308        try
 309        {
 121310            Span<ContinuousCollisionMath.RotationalInterval> intervals =
 121311                stackalloc ContinuousCollisionMath.RotationalInterval[
 121312                    ContinuousCollisionMath.RotationalIntervalMaxDepth + 2];
 121313            int intervalCount = 1;
 121314            int processedNodeCount = 0;
 121315            bool hasKnownContact = false;
 121316            Fixed64 knownContactTime = Fixed64.One;
 121317            MixedContact knownContact = default;
 121318            intervals[0] = new ContinuousCollisionMath.RotationalInterval(
 121319                searchStart,
 121320                searchEnd,
 121321                depth: 0);
 322
 2194323            while (intervalCount > 0)
 324            {
 2185325                ContinuousCollisionMath.RotationalInterval interval = intervals[--intervalCount];
 2185326                Fixed64 midpoint =
 2185327                    (interval.LowerTime + interval.UpperTime) * Fixed64.Half;
 2185328                Fixed64 intervalSpan = interval.UpperTime - interval.LowerTime;
 2185329                SampleMixedRotationalContinuousPairPose(
 2185330                    startPosition,
 2185331                    displacement,
 2185332                    startRotation,
 2185333                    angularDelta,
 2185334                    midpoint,
 2185335                    elapsedTime,
 2185336                    remainingTime,
 2185337                    targetBody,
 2185338                    samplesTargetMotion);
 2185339                processedNodeCount++;
 340
 2185341                bool hasMotionBound =
 2185342                    ContinuousCollisionMath.TryResolveRotationalIntervalMotionBound(
 2185343                        displacement,
 2185344                        angularDistance,
 2185345                        pivotRadius,
 2185346                        intervalSpan,
 2185347                        out Fixed64 motionBound);
 2185348                if (hasMotionBound && samplesTargetMotion)
 349                {
 2007350                    Fixed64 lowerFraction = ResolveRotationalFrameFraction(
 2007351                        elapsedTime,
 2007352                        remainingTime,
 2007353                        interval.LowerTime);
 2007354                    Fixed64 upperFraction = ResolveRotationalFrameFraction(
 2007355                        elapsedTime,
 2007356                        remainingTime,
 2007357                        interval.UpperTime);
 2007358                    Fixed64 targetRadius = targetBody!.ResolveContinuousCollisionProxyRadius();
 2007359                    bool targetBoundResolved = targetBody.TryResolveContinuousCollisionMotionBound(
 2007360                        lowerFraction,
 2007361                        upperFraction,
 2007362                        targetRadius,
 2007363                        out Fixed64 targetMotionBound);
 2007364                    bool combinedBoundResolved = Fixed64.TryAdd(
 2007365                        motionBound,
 2007366                        targetMotionBound,
 2007367                        out motionBound);
 2007368                    hasMotionBound = targetBoundResolved & combinedBoundResolved;
 369                }
 370
 2185371                bool sampleHasContact = CollisionDetectionMixed.TryCollide(
 2185372                    target,
 2185373                    Collider,
 2185374                    out MixedContact sampleContact);
 2185375                if (sampleHasContact
 2185376                    && (!hasKnownContact || midpoint < knownContactTime))
 377                {
 724378                    hasKnownContact = true;
 724379                    knownContactTime = midpoint;
 724380                    knownContact = sampleContact;
 381                }
 382
 2185383                bool hasShapeGap = CollisionDetectionMixed.TryGetRotationalSeparationGap(
 2185384                    target,
 2185385                    Collider,
 2185386                    out Fixed64 shapeGap,
 2185387                    out bool shapeGapSupported);
 2185388                Fixed64 characteristicScale = target is LSSphereCollider sphere
 2185389                    ? FixedMath.Max(pivotRadius, sphere.ScaledRadius)
 2185390                    : pivotRadius;
 2185391                bool shapeGapCertifiesSeparation = shapeGapSupported
 2185392                    && hasShapeGap
 2185393                    && ContinuousCollisionMath.TrySubtractClosestFeatureUncertainty(
 2185394                        shapeGap,
 2185395                        characteristicScale,
 2185396                        out Fixed64 conservativeShapeGap)
 2185397                    && conservativeShapeGap > motionBound;
 2185398                bool fallbackBoundsCertifySeparation = !shapeGapSupported
 2185399                    && ContinuousCollisionMath.AreBoundsSeparatedByMoreThan(
 2185400                        target.Bounds,
 2185401                        Collider.MixedBounds3D,
 2185402                        motionBound);
 2185403                if (!sampleHasContact
 2185404                    && hasMotionBound
 2185405                    && (shapeGapCertifiesSeparation || fallbackBoundsCertifySeparation))
 406                {
 407                    continue;
 408                }
 409
 1622410                if (ContinuousCollisionMath.TryResolveRotationalSearchLimit(
 1622411                        interval,
 1622412                        processedNodeCount,
 1622413                        hasKnownContact,
 1622414                        knownContactTime,
 1622415                        out safeTime,
 1622416                        out contactTime,
 1622417                        out hasContact))
 418                {
 112419                    contact = knownContact;
 112420                    return true;
 421                }
 422
 1510423                int childDepth = interval.Depth + 1;
 1510424                if (sampleHasContact)
 425                {
 721426                    intervals[intervalCount++] =
 721427                        new ContinuousCollisionMath.RotationalInterval(
 721428                            interval.LowerTime,
 721429                            midpoint,
 721430                            childDepth);
 721431                    continue;
 432                }
 433
 789434                intervals[intervalCount++] =
 789435                    new ContinuousCollisionMath.RotationalInterval(
 789436                        midpoint,
 789437                        interval.UpperTime,
 789438                        childDepth);
 789439                intervals[intervalCount++] =
 789440                    new ContinuousCollisionMath.RotationalInterval(
 789441                        interval.LowerTime,
 789442                        midpoint,
 789443                        childDepth);
 444            }
 445
 9446            safeTime = knownContactTime;
 9447            contact = knownContact;
 9448            hasContact = hasKnownContact;
 9449            contactTime = knownContactTime;
 9450            return hasKnownContact;
 451        }
 452        finally
 453        {
 121454            if (targetBody != null)
 117455                targetBody.SetMixedContinuousCollisionSamplePose(
 117456                    originalTargetPosition,
 117457                    originalTargetRotation);
 121458        }
 121459    }
 460
 461    private void SampleMixedRotationalContinuousPairPose(
 462        Vector2d startPosition,
 463        Vector2d displacement,
 464        Fixed64 startRotation,
 465        Fixed64 angularDelta,
 466        Fixed64 sampleTime,
 467        Fixed64 elapsedTime,
 468        Fixed64 remainingTime,
 469        SolidBody? targetBody,
 470        bool samplesTargetMotion)
 471    {
 2185472        _position = startPosition + displacement * sampleTime;
 2185473        _rotation = startRotation + angularDelta * sampleTime;
 2185474        Collider.RebuildRuntimeShapeOnly();
 475
 2185476        if (!samplesTargetMotion)
 178477            return;
 478
 2007479        Fixed64 frameFraction = ResolveRotationalFrameFraction(
 2007480            elapsedTime,
 2007481            remainingTime,
 2007482            sampleTime);
 2007483        targetBody!.SetMixedContinuousCollisionSamplePose(
 2007484            targetBody.SampleContinuousCollisionPosition(frameFraction),
 2007485            targetBody.SampleContinuousCollisionRotation(frameFraction));
 2007486    }
 487
 488    private bool TryGetExactSphereCircleTranslationalContact(
 489        SolidBody target,
 490        ContinuousCollisionMotionSegment3D targetSegment,
 491        FixedSegment targetPath,
 492        Vector3d targetDisplacement,
 493        Fixed64 targetTravelDistance,
 494        FixedSegment2d sourcePath,
 495        Vector2d sourceDisplacement,
 496        Fixed64 sourceTravelDistance,
 497        Fixed64 sourceStartRotation,
 498        Fixed64 sourceAngularDelta,
 499        out bool requiresRotationalFallback,
 500        out Fixed64 relativeContactDistance,
 501        out Fixed64 sourceContactDistance,
 502        out Fixed64 localClosingSpeed,
 503        out MixedContact contact)
 504    {
 58505        requiresRotationalFallback = true;
 58506        relativeContactDistance = default;
 58507        sourceContactDistance = default;
 58508        localClosingSpeed = default;
 58509        contact = default;
 58510        if (Collider is not LSCircleCollider2D circle
 58511            || target.Collider is not LSSphereCollider sphere
 58512            || (targetSegment.AngularDistance != Fixed64.Zero
 58513                && sphere.ScaledOffset != Vector3d.Zero)
 58514            || (sourceAngularDelta != Fixed64.Zero
 58515                && circle.ScaledLocalOffset != Vector2d.Zero))
 516        {
 13517            return false;
 518        }
 519
 45520        Vector2d originalSourcePosition = _position;
 45521        Fixed64 originalSourceRotation = _rotation;
 45522        Vector3d originalTargetPosition = target.Position3d;
 45523        FixedQuaternion originalTargetRotation = target.Rotation;
 524        try
 525        {
 45526            _position = sourcePath.Start;
 45527            _rotation = sourceStartRotation;
 45528            Collider.RebuildRuntimeShapeOnly();
 45529            target.SetMixedContinuousCollisionSamplePose(
 45530                targetPath.Start,
 45531                targetSegment.StartRotation);
 45532            Vector3d cylinderStart = new(
 45533                circle.Center.X,
 45534                circle.MixedSlabCenterY,
 45535                circle.Center.Y);
 45536            Vector3d sphereStart = sphere.Center;
 45537            Vector3d cylinderDisplacement = sourceDisplacement.ToVector3d(Fixed64.Zero);
 45538            Vector3d relativeDisplacement =
 45539                ContinuousCollisionSweepRange.ValidateRelativeDisplacement(
 45540                    targetDisplacement,
 45541                    cylinderDisplacement,
 45542                    out Fixed64 totalDistance);
 45543            if (!WideFiniteAxisIntersection
 45544                    .TryGetSphericallyExpandedFiniteCylinderDirectionFirstDistanceFromHalfAxisLength(
 45545                        sphereStart,
 45546                        relativeDisplacement,
 45547                        cylinderStart,
 45548                        Vector3d.Up,
 45549                        circle.MixedHalfThickness,
 45550                        circle.ScaledRadius,
 45551                        sphere.ScaledRadius,
 45552                        totalDistance,
 45553                        out Fixed64 contactDistance))
 554            {
 1555                requiresRotationalFallback = false;
 1556                return false;
 557            }
 558
 44559            relativeContactDistance = contactDistance;
 44560            _ = Fixed64.TryMultiplyDivide(
 44561                sourceTravelDistance,
 44562                contactDistance,
 44563                totalDistance,
 44564                out sourceContactDistance);
 44565            Fixed64 candidateDistance = contactDistance;
 90566            for (int attempt = 0; attempt < 2; attempt++)
 567            {
 45568                _ = Fixed64.TryMultiplyDivide(
 45569                    sourceTravelDistance,
 45570                    candidateDistance,
 45571                    totalDistance,
 45572                    out Fixed64 sampleSourceDistance);
 45573                _ = Fixed64.TryMultiplyDivide(
 45574                    targetTravelDistance,
 45575                    candidateDistance,
 45576                    totalDistance,
 45577                    out Fixed64 targetContactDistance);
 45578                _position = sourcePath.GetPointAtDistance(
 45579                    sampleSourceDistance,
 45580                    sourceTravelDistance);
 45581                _rotation = sourceStartRotation;
 45582                Collider.RebuildRuntimeShapeOnly();
 45583                target.SetMixedContinuousCollisionSamplePose(
 45584                    targetPath.GetPointAtDistance(
 45585                        targetContactDistance,
 45586                        targetTravelDistance),
 45587                    targetSegment.StartRotation);
 45588                if (CollisionDetectionMixed.TryCollide(
 45589                    target.Collider,
 45590                    Collider,
 45591                    out contact))
 592                {
 44593                    bool closingResolved = Vector3d.TryDot(
 44594                            relativeDisplacement,
 44595                            contact.Normal3DTo2D,
 44596                            out localClosingSpeed);
 44597                    if (closingResolved
 44598                        && localClosingSpeed > Fixed64.Epsilon)
 599                    {
 42600                        return true;
 601                    }
 602
 2603                    if (closingResolved)
 2604                        requiresRotationalFallback = false;
 605
 2606                    break;
 607                }
 608
 1609                if (candidateDistance >= totalDistance)
 610                    break;
 611
 1612                candidateDistance = FixedMath.Min(
 1613                    totalDistance,
 1614                    candidateDistance + Fixed64.MinIncrement);
 615            }
 616
 2617            relativeContactDistance = default;
 2618            sourceContactDistance = default;
 2619            localClosingSpeed = default;
 2620            contact = default;
 2621            return false;
 622        }
 623        finally
 624        {
 45625            _position = originalSourcePosition;
 45626            _rotation = originalSourceRotation;
 45627            Collider.RebuildRuntimeShapeOnly();
 45628            target.SetMixedContinuousCollisionSamplePose(
 45629                originalTargetPosition,
 45630                originalTargetRotation);
 45631        }
 45632    }
 633
 634    private bool IsValidMixedRotationalContinuousCollisionTarget(LSCollider target)
 635    {
 374636        return target.IsActive
 374637            && !target.IsTrigger
 374638            && !ContinuousCollisionCandidateOrdering.IsIgnoredTarget(
 374639                target,
 374640                _continuousCollisionHandoffIgnoredCollider3D)
 374641            && Context.MixedCollisions.RequireCollisionPair(target, Collider);
 642    }
 643
 644    private bool IsMovingMixedRotationalContinuousCollisionTarget(SolidBody target)
 645    {
 263646        if (target.IsKinematic)
 26647            target.EnsureContinuousCollisionFramePrepared(Context.LateSimulateToken);
 648
 263649        return (IsKinematic || !target.ShouldOwnContinuousCollisionMovingPair(
 263650                HasContinuousCollisionRotationalMotion))
 263651            && IsValidMixedRotationalContinuousCollisionTarget(target.Collider)
 263652            && (target.IsKinematic
 263653                ? target.HasContinuousCollisionMotion
 263654                : target.IsDynamic);
 655    }
 656
 657    internal bool TryApplyMixedRotationalContinuousCollisionResponse(
 658        LSCollider targetCollider,
 659        MixedContact contact,
 660        Fixed64 localContactTime,
 661        Vector2d sourceSegmentStart,
 662        Vector2d sourceDisplacement,
 663        Fixed64 sourceSegmentStartRotation,
 664        Fixed64 sourceAngularDelta,
 665        Fixed64 elapsedTime,
 666        Fixed64 remainingTime)
 667    {
 51668        Fixed64 frameFraction = ResolveRotationalFrameFraction(
 51669            elapsedTime,
 51670            remainingTime,
 51671            localContactTime);
 51672        Fixed64 consumedTime = remainingTime * localContactTime;
 51673        Fixed64 impactElapsedTime = elapsedTime + consumedTime;
 51674        Fixed64 remainingAfterImpact = remainingTime - consumedTime;
 51675        Vector2d sourcePosition = sourceSegmentStart
 51676            + sourceDisplacement * localContactTime;
 51677        Fixed64 sourceRotation = CanonicalizeRotation(
 51678            sourceSegmentStartRotation + sourceAngularDelta * localContactTime);
 51679        Vector2d sourceLinearVelocity = IsKinematic
 51680            ? SampleContinuousCollisionLinearVelocity(frameFraction)
 51681            : _linearVelocity;
 51682        Fixed64 sourceAngularVelocity = IsKinematic
 51683            ? SampleContinuousCollisionAngularVelocity(frameFraction)
 51684            : _angularVelocity;
 685
 51686        SolidBody? target = targetCollider.Body;
 51687        Vector3d targetPosition = target?.SampleContinuousCollisionPosition(frameFraction)
 51688            ?? targetCollider.Center;
 51689        FixedQuaternion targetRotation = target?.SampleContinuousCollisionRotation(frameFraction)
 51690            ?? FixedQuaternion.Identity;
 51691        Vector3d targetLinearVelocity = target?.SampleContinuousCollisionLinearVelocity(frameFraction)
 51692            ?? Vector3d.Zero;
 51693        Vector3d targetAngularVelocity = target?.SampleContinuousCollisionAngularVelocity(frameFraction)
 51694            ?? Vector3d.Zero;
 51695        SolidBody? targetResponseBody = target?.HasSolverMobility == true ? target : null;
 51696        SolidBody2D? sourceResponseBody = HasSolverMobility ? this : null;
 51697        Fixed64 restitution = PhysicsMaterial.CombineRestitution(
 51698            targetCollider.Material,
 51699            Collider.Material);
 51700        ContactAnchor targetCenter = target == null
 51701            ? ContactAnchor.FromWorldPoint(targetPosition)
 51702            : new ContactAnchor(
 51703                targetPosition,
 51704                targetRotation,
 51705                target.LocalCenterOfMassOffset);
 51706        bool targetContactArmResolved = target == null
 51707            ? contact.Anchor3D.TryGetOffsetFrom(
 51708                targetPosition,
 51709                out Vector3d targetContactArm)
 51710            : contact.Anchor3D.TryGetOffsetFrom(
 51711                targetCenter,
 51712                out targetContactArm);
 51713        bool sourceContactArmResolved =
 51714            contact.TryGetPlanarOffset2DFrom(
 51715            sourcePosition,
 51716            sourceRotation,
 51717            LocalCenterOfMassOffset,
 51718            out Vector2d sourceContactArm);
 51719        ContactNormalVelocityDeltaResultMixed response = default;
 51720        bool responseResolved = targetContactArmResolved
 51721            && sourceContactArmResolved
 51722            && ContactNormalImpulseMixed.CanUseCompactResponse(
 51723                targetResponseBody,
 51724                targetLinearVelocity,
 51725                targetAngularVelocity,
 51726                targetContactArm,
 51727                sourceResponseBody,
 51728                sourceLinearVelocity,
 51729                sourceAngularVelocity,
 51730                sourceContactArm,
 51731                contact.Normal3DTo2D)
 51732            && ContactNormalImpulseMixed.TryCalculateVelocityDeltas(
 51733                targetResponseBody,
 51734                targetLinearVelocity,
 51735                targetAngularVelocity,
 51736                targetContactArm,
 51737                sourceResponseBody,
 51738                sourceLinearVelocity,
 51739                sourceAngularVelocity,
 51740                sourceContactArm,
 51741                contact.Normal3DTo2D,
 51742                restitution,
 51743                Context.Settings.RestitutionVelocityThreshold,
 51744                out response);
 51745        if (!responseResolved)
 746        {
 45747            responseResolved =
 45748                ContactNormalImpulseMixed.TryCalculateVelocityDeltasExact(
 45749                    targetResponseBody,
 45750                    targetLinearVelocity,
 45751                    targetAngularVelocity,
 45752                    contact.Anchor3D.GetLeverFrom(targetCenter),
 45753                    sourceResponseBody,
 45754                    sourceLinearVelocity,
 45755                    sourceAngularVelocity,
 45756                    contact.GetPlanarXZLeverFrom(
 45757                        sourcePosition,
 45758                        sourceRotation,
 45759                        LocalCenterOfMassOffset),
 45760                    contact.Normal3DTo2D,
 45761                    restitution,
 45762                    Context.Settings.RestitutionVelocityThreshold,
 45763                    out response);
 764        }
 51765        if (!responseResolved
 51766            || !response.IsClosing
 51767            || (response.HasRepresentableNormalVelocity
 51768                && response.NormalVelocity >= -Fixed64.Epsilon))
 769        {
 2770            return false;
 771        }
 772
 49773        Vector3d postTargetLinearVelocity = targetLinearVelocity;
 49774        Vector3d postTargetAngularVelocity = targetAngularVelocity;
 49775        Vector3d targetResolvedPosition = default;
 49776        if (sourceResponseBody != null
 49777            && !(CanApplyCollisionVelocityDeltas(
 49778                    response.LinearVelocityDelta2D,
 49779                    response.AngularVelocityDelta2D)
 49780                & CanAppendContinuousCollisionFrameSegment(impactElapsedTime)
 49781                & Context.Physics2D.CanAdmitContinuousCollisionCandidateRefresh(this)))
 782        {
 1783            return false;
 784        }
 785
 48786        if (targetResponseBody != null)
 787        {
 42788            bool targetLinearVelocityResolved = Vector3d.TryAdd(
 42789                targetLinearVelocity,
 42790                response.LinearVelocityDelta3D,
 42791                out postTargetLinearVelocity);
 42792            bool targetAngularVelocityResolved = Vector3d.TryAdd(
 42793                targetAngularVelocity,
 42794                response.AngularVelocityDelta3D,
 42795                out postTargetAngularVelocity);
 42796            bool targetHandoffAdmissible = targetResponseBody.CanApplyContinuousCollisionHandoff(
 42797                targetPosition,
 42798                targetRotation,
 42799                remainingAfterImpact,
 42800                out targetResolvedPosition);
 42801            if (!(targetLinearVelocityResolved
 42802                    & targetAngularVelocityResolved
 42803                    & targetHandoffAdmissible))
 804            {
 1805                return false;
 806            }
 807        }
 808
 47809        if (sourceResponseBody != null)
 6810            _ = Context.Physics2D.TryReserveContinuousCollisionCandidateRefresh(this);
 811
 47812        if (targetResponseBody != null)
 813        {
 814            // Dirty overlay capacity is the registered-body count, so a valid
 815            // registered target cannot exhaust this reservation.
 41816            _ = Context.Physics.TryReserveContinuousCollisionCandidateRefresh(
 41817                targetResponseBody);
 41818            targetResponseBody.ApplyContinuousCollisionHandoffReserved(
 41819                targetResolvedPosition,
 41820                targetRotation,
 41821                postTargetLinearVelocity,
 41822                postTargetAngularVelocity,
 41823                remainingAfterImpact,
 41824                ignoredCollider2D: Collider);
 825        }
 826
 47827        if (sourceResponseBody != null)
 828        {
 6829            ApplyCollisionLinearVelocityDelta(response.LinearVelocityDelta2D);
 6830            ApplyCollisionAngularVelocityDelta(response.AngularVelocityDelta2D);
 831        }
 832
 47833        return true;
 834    }
 835}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/2D/SolidBody2D.ContinuousCollision.Rotational.Response.cs

#LineLine coverage
 1//=======================================================================
 2// SolidBody2D.ContinuousCollision.Rotational.Response.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2026-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using FixedMathSharp;
 9using Gravitas.CollisionHandling;
 10using Gravitas.Materials;
 11
 12namespace Gravitas;
 13
 14public partial class SolidBody2D
 15{
 16    internal bool TryApplyRotationalContinuousCollisionResponse(
 17        SolidBody2D target,
 18        Contact2D contact,
 19        Fixed64 contactTime,
 20        Vector2d startPosition,
 21        Fixed64 startRotation,
 22        Vector2d displacement,
 23        Fixed64 angularDelta,
 24        Fixed64 elapsedTime,
 25        Fixed64 remainingTime)
 26    {
 4027        Fixed64 frameFraction = ResolveRotationalFrameFraction(
 4028            elapsedTime,
 4029            remainingTime,
 4030            contactTime);
 4031        Vector2d sourcePositionAtImpact = startPosition + displacement * contactTime;
 4032        Fixed64 sourceRotationAtImpact = CanonicalizeRotation(
 4033            startRotation + angularDelta * contactTime);
 4034        Vector2d targetPositionAtImpact = target.SampleContinuousCollisionPosition(frameFraction);
 4035        Fixed64 targetRotationAtImpact = target.SampleContinuousCollisionRotation(frameFraction);
 4036        Vector2d sourceLinearVelocity = IsKinematic
 4037            ? displacement / remainingTime
 4038            : _linearVelocity;
 4039        Fixed64 sourceAngularVelocity = IsKinematic
 4040            ? angularDelta / remainingTime
 4041            : _angularVelocity;
 4042        Vector2d targetLinearVelocity = target.SampleContinuousCollisionLinearVelocity(frameFraction);
 4043        Fixed64 targetAngularVelocity = target.SampleContinuousCollisionAngularVelocity(frameFraction);
 4044        SolidBody2D? sourceResponseBody = HasSolverMobility ? this : null;
 4045        SolidBody2D? targetResponseBody = target.HasSolverMobility ? target : null;
 4046        Fixed64 restitution = PhysicsMaterial.CombineRestitution(
 4047            Collider.Material,
 4048            target.Collider.Material);
 4049        var sourceCenter = new ContactAnchor2D(
 4050            sourcePositionAtImpact,
 4051            sourceRotationAtImpact,
 4052            _localCenterOfMassOffset);
 4053        var targetCenter = new ContactAnchor2D(
 4054            targetPositionAtImpact,
 4055            targetRotationAtImpact,
 4056            target._localCenterOfMassOffset);
 4057        ContactLever2D sourceContactArm =
 4058            ContactLever2D.Create(contact.AnchorA, sourceCenter);
 4059        ContactLever2D targetContactArm =
 4060            ContactLever2D.Create(contact.AnchorB, targetCenter);
 4061        ContactNormalVelocityDeltaResult2D response = default;
 4062        bool responseResolved = !sourceContactArm.IsExact
 4063            && !targetContactArm.IsExact
 4064            && ExactContactLever2D.CanUseCompactResponse(
 4065                sourceResponseBody,
 4066                sourceLinearVelocity,
 4067                sourceAngularVelocity,
 4068                sourceContactArm.Vector,
 4069                targetResponseBody,
 4070                targetLinearVelocity,
 4071                targetAngularVelocity,
 4072                targetContactArm.Vector,
 4073                contact.Normal)
 4074            && ContactNormalImpulse2D.TryCalculateVelocityDeltas(
 4075                sourceResponseBody,
 4076                sourceLinearVelocity,
 4077                sourceAngularVelocity,
 4078                sourceContactArm.Vector,
 4079                targetResponseBody,
 4080                targetLinearVelocity,
 4081                targetAngularVelocity,
 4082                targetContactArm.Vector,
 4083                contact.Normal,
 4084                restitution,
 4085                Context.Settings.RestitutionVelocityThreshold,
 4086                out response);
 4087        if (!responseResolved)
 88        {
 2689            responseResolved =
 2690                ContactNormalImpulse2D.TryCalculateVelocityDeltasExact(
 2691                    sourceResponseBody,
 2692                    sourceLinearVelocity,
 2693                    sourceAngularVelocity,
 2694                    contact.AnchorA.GetXZLeverFrom(sourceCenter),
 2695                    targetResponseBody,
 2696                    targetLinearVelocity,
 2697                    targetAngularVelocity,
 2698                    contact.AnchorB.GetXZLeverFrom(targetCenter),
 2699                    contact.Normal,
 26100                    restitution,
 26101                    Context.Settings.RestitutionVelocityThreshold,
 26102                    out response);
 103        }
 40104        if (!responseResolved
 40105            || !response.IsClosing
 40106            || (response.HasRepresentableNormalVelocity
 40107                && response.NormalVelocity >= -Fixed64.Epsilon))
 108        {
 5109            return false;
 110        }
 111
 35112        Vector2d targetPostLinearVelocity = targetLinearVelocity;
 35113        Fixed64 targetPostAngularVelocity = targetAngularVelocity;
 35114        Vector2d targetResolvedPosition = default;
 35115        Fixed64 impactElapsedTime = elapsedTime + remainingTime * contactTime;
 35116        Fixed64 remainingAfterImpact = remainingTime * (Fixed64.One - contactTime);
 35117        if (sourceResponseBody != null)
 118        {
 23119            bool sourceResponseAdmissible = CanApplyCollisionVelocityDeltas(
 23120                    response.LinearVelocityDeltaA,
 23121                    response.AngularVelocityDeltaA)
 23122                & CanAppendContinuousCollisionFrameSegment(impactElapsedTime)
 23123                & (targetResponseBody == null
 23124                    ? Context.Physics2D.CanAdmitContinuousCollisionCandidateRefresh(this)
 23125                    : Context.Physics2D.CanAdmitContinuousCollisionCandidateRefresh(
 23126                        this,
 23127                        target));
 23128            if (!sourceResponseAdmissible)
 1129                return false;
 130        }
 131
 34132        if (targetResponseBody != null)
 133        {
 18134            bool targetLinearVelocityResolved = Vector2d.TryAdd(
 18135                targetLinearVelocity,
 18136                response.LinearVelocityDeltaB,
 18137                out targetPostLinearVelocity);
 18138            bool targetAngularVelocityResolved = Fixed64.TryAdd(
 18139                targetAngularVelocity,
 18140                response.AngularVelocityDeltaB,
 18141                out targetPostAngularVelocity);
 18142            bool targetHandoffAdmissible = target.CanApplyContinuousCollisionHandoffState(
 18143                targetPositionAtImpact,
 18144                targetRotationAtImpact,
 18145                remainingAfterImpact,
 18146                out targetResolvedPosition);
 18147            if (!(targetLinearVelocityResolved
 18148                    & targetAngularVelocityResolved
 18149                    & targetHandoffAdmissible))
 150            {
 1151                return false;
 152            }
 153        }
 154
 33155        if (sourceResponseBody != null)
 156        {
 157            // The admission check above is immediately followed by this
 158            // reservation in the single-threaded arbiter, so failure is not a
 159            // runtime state. Reserve the already-proven body set once.
 22160            _ = targetResponseBody == null
 22161                ? Context.Physics2D.TryReserveContinuousCollisionCandidateRefresh(this)
 22162                : Context.Physics2D.TryReserveContinuousCollisionCandidateRefresh(this, target);
 163        }
 164
 33165        if (targetResponseBody != null)
 166        {
 17167            target.ApplyContinuousCollisionHandoffStateReserved(
 17168                targetResolvedPosition,
 17169                targetRotationAtImpact,
 17170                targetPostLinearVelocity,
 17171                targetPostAngularVelocity,
 17172                remainingAfterImpact,
 17173                ignoredCollider2D: Collider);
 174        }
 175
 33176        if (sourceResponseBody != null)
 177        {
 22178            ApplyCollisionLinearVelocityDelta(response.LinearVelocityDeltaA);
 22179            ApplyCollisionAngularVelocityDelta(response.AngularVelocityDeltaA);
 180        }
 181
 33182        return true;
 183    }
 184
 185    [System.Runtime.CompilerServices.MethodImpl(
 186        System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
 187    private Fixed64 ResolveRotationalFrameFraction(
 188        Fixed64 elapsedTime,
 189        Fixed64 remainingTime,
 190        Fixed64 localTime) =>
 9506191        FixedMath.Clamp01((elapsedTime + remainingTime * localTime) / Context.DeltaTime);
 192
 193    private void StopRotationalContinuousCollision(Vector2d contactNormal)
 194    {
 3195        _angularVelocity = Fixed64.Zero;
 3196        _angularAccelerationStore = Fixed64.Zero;
 3197        _deltaAngularAcceleration = Fixed64.Zero;
 3198        RefreshAngularSpeed();
 3199        RemoveClosingContinuousCollisionVelocity(contactNormal);
 3200    }
 201}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/2D/SolidBody2D.cs

#LineLine coverage
 1//=======================================================================
 2// SolidBody2D.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2026–present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using Chronicler;
 9using FixedMathSharp;
 10using Gravitas.Colliders;
 11using Gravitas.CollisionHandling;
 12using Gravitas.Queries;
 13using SwiftCollections;
 14using System.Runtime.CompilerServices;
 15
 16namespace Gravitas;
 17
 18/// <summary>
 19/// Represents deterministic pure 2D body state owned by one world context.
 20/// </summary>
 21public sealed partial class SolidBody2D : IRecordable
 22{
 23    private Vector2d _position;
 24    private Fixed64 _rotation;
 25    private Vector2d _linearVelocity;
 26    private Vector2d _linearAccelerationStore;
 27    private Vector2d _deltaAcceleration;
 28    private Fixed64 _linearSpeed;
 29    private Fixed64 _mass;
 30    private Vector2d _localCenterOfMassOffset;
 31    private bool _centerOfMassOffsetExplicit;
 32    private Fixed64 _momentOfInertia;
 33    private Fixed64 _inverseMomentOfInertia;
 34    private Fixed64 _angularVelocity;
 35    private Fixed64 _angularAccelerationStore;
 36    private Fixed64 _deltaAngularAcceleration;
 37    private Fixed64 _angularSpeed;
 38    private bool _isSleeping;
 39    private BodyMotionType _motionType;
 40    private int _sleepFrameCount;
 464541    private bool _sleepEnabled = true;
 464542    private int _sleepFrameThreshold = 16;
 464543    private Fixed64 _sleepLinearSpeedThreshold = (Fixed64)0.001f;
 464544    private Fixed64 _sleepAngularSpeedThreshold = (Fixed64)0.001f;
 45    private ContinuousCollisionMode _continuousCollisionMode = ContinuousCollisionMode.Inherit;
 464546    private int _continuousCollisionFrameToken = int.MinValue;
 464547    private readonly SwiftList<ContinuousCollisionMotionSegment2D> _continuousCollisionTrajectory =
 464548        new(PhysicsSettings.DefaultContinuousCollisionMaxToiIterations + 1);
 49    private bool _continuousCollisionHandoffPending;
 464550    private int _continuousCollisionHandoffToken = int.MinValue;
 51    private Fixed64 _continuousCollisionHandoffRemainingTime;
 464552    private readonly SwiftList<Physics2DHit> _continuousCollisionHits = new();
 464553    private readonly SwiftList<int> _rotationalContinuousCollisionCandidateIds = new();
 464554    private readonly SwiftList<PhysicsMixedHit> _continuousMixedCollisionHits = new();
 55
 56    /// <summary>Creates a pure 2D body bound to a host agent and collider.</summary>
 464557    public SolidBody2D(IMatterAgent agent, LSCollider2D collider)
 58    {
 464559        SwiftThrowHelper.ThrowIfNull(agent, nameof(agent));
 464560        SwiftThrowHelper.ThrowIfNull(collider, nameof(collider));
 464561        Agent = agent;
 464562        Context = agent.Context;
 464563        Collider = collider;
 464564    }
 65
 66    /// <summary>Gets the host agent that owns this body.</summary>
 67    public IMatterAgent Agent { get; }
 68
 69    /// <summary>Gets the world context supplied by the host agent.</summary>
 70    public GravitasWorldContext Context { get; }
 71
 72    /// <summary>Gets the runtime 2D collider attached to this body.</summary>
 73    public LSCollider2D Collider { get; }
 74
 75    /// <summary>
 76    /// Gets the ephemeral simulated-body slot, or <c>-1</c> for a static or
 77    /// unregistered body.
 78    /// </summary>
 79    public int DynamicId { get; internal set; } = -1;
 80
 81    /// <summary>Gets whether the body is initialized and registered.</summary>
 82    public bool Active { get; private set; }
 83
 84    private BodyFreezeAxes2D _freezeAxes;
 85
 86    /// <summary>
 87    /// Gets or sets the planar translational and yaw rotational degrees of
 88    /// freedom frozen for pure 2D solver response, integration, CCD, and
 89    /// rotational CCD.
 90    /// </summary>
 91    public BodyFreezeAxes2D FreezeAxes
 92    {
 93        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 10694        get => _freezeAxes;
 95        set
 96        {
 13297            SwiftThrowHelper.ThrowIfArgument(
 13298                (value & ~BodyFreezeAxes2D.All) != BodyFreezeAxes2D.None,
 13299                nameof(value),
 132100                "Unsupported 2D freeze axis bits.");
 101
 132102            if (_freezeAxes == value)
 14103                return;
 104
 118105            _freezeAxes = value;
 118106            ApplyFreezeConstraintsToMotion();
 118107            RefreshPartitionMobility();
 118108        }
 109    }
 110
 111    /// <summary>
 112    /// Gets whether both planar translation axes are frozen.
 113    /// </summary>
 114    public bool IsPositionFullyFrozen
 115    {
 116        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 99500117        get => (_freezeAxes & BodyFreezeAxes2D.Position) == BodyFreezeAxes2D.Position;
 118    }
 119
 120    /// <summary>
 121    /// Gets this body's explicit solver-controlled, host-controlled, or static
 122    /// runtime role.
 123    /// </summary>
 271124    public BodyMotionType MotionType => _motionType;
 125
 126    /// <summary>
 127    /// Gets whether the solver controls this body.
 128    /// </summary>
 109513129    public bool IsDynamic => _motionType == BodyMotionType.Dynamic;
 130
 131    /// <summary>
 132    /// Gets whether this body is excluded from per-frame motion.
 133    /// </summary>
 13086134    public bool IsStatic => _motionType == BodyMotionType.Static;
 135
 136    /// <summary>
 137    /// Gets whether the host controls this body's pose.
 138    /// </summary>
 31908139    public bool IsKinematic => _motionType == BodyMotionType.Kinematic;
 140
 141    /// <summary>
 142    /// Changes this registered body's runtime role between fixed-step
 143    /// transactions.
 144    /// </summary>
 145    /// <remarks>
 146    /// The transition preserves body, collider, pair, joint, and host identity,
 147    /// but clears incompatible motion, sleep, CCD, and solver-cache state before
 148    /// repartitioning. Freeze axes remain independent of the selected role.
 149    /// </remarks>
 150    /// <exception cref="System.ArgumentOutOfRangeException">
 151    /// <paramref name="motionType"/> is undefined.
 152    /// </exception>
 153    /// <exception cref="System.InvalidOperationException">
 154    /// The body is not currently registered, its context registration has been
 155    /// reset, or a simulation transaction or callback is active.
 156    /// </exception>
 157    public void SetMotionType(BodyMotionType motionType)
 158    {
 60159        if (!PrepareMotionTypeTransition(motionType))
 3160            return;
 161
 49162        CommitMotionTypeTransition(motionType);
 49163    }
 164
 165    internal bool PrepareMotionTypeTransition(BodyMotionType motionType)
 166    {
 131167        motionType.ThrowIfInvalid(nameof(motionType));
 130168        ThrowIfRuntimeRegistrationMissing();
 126169        if (_motionType == motionType)
 57170            return false;
 171
 69172        Context.ThrowIfFixedStepMutationNotAllowed();
 67173        Collider.ValidateCurrentRuntimeTransform();
 65174        if (motionType != BodyMotionType.Dynamic)
 47175            PublishAuthoritativePose();
 176
 65177        return true;
 178    }
 179
 180    internal void CommitMotionTypeTransition(BodyMotionType motionType)
 181    {
 64182        ReconcileMotionTypeRegistration(motionType);
 183
 64184        ClearMotionForMotionTypeTransition();
 64185        _isSleeping = false;
 64186        _sleepFrameCount = 0;
 64187        InvalidateContinuousCollisionFrame();
 64188        ApplyFreezeConstraintsToMotion();
 64189        RefreshMassPropertiesFromColliderShape();
 64190        RefreshPartitionMobility();
 64191    }
 192
 193    internal void ApplyLoadedMotionType(BodyMotionType motionType)
 194    {
 56195        motionType.ThrowIfInvalid(nameof(motionType));
 56196        if (_motionType == motionType)
 38197            return;
 198
 18199        if (!Active)
 200        {
 4201            _motionType = motionType;
 4202            return;
 203        }
 204
 14205        ReconcileMotionTypeRegistration(motionType);
 14206        InvalidateContinuousCollisionFrame();
 14207    }
 208
 209    internal void PreflightLoadedMotionType(BodyMotionType motionType)
 210    {
 60211        motionType.ThrowIfInvalid(nameof(motionType));
 59212        if (!Active)
 6213            return;
 214
 53215        ThrowIfRuntimeRegistrationMissing();
 51216        Context.ThrowIfFixedStepMutationNotAllowed();
 51217        Collider.ValidateCurrentRuntimeTransform();
 51218    }
 219
 220    private void ThrowIfRuntimeRegistrationMissing()
 221    {
 198222        SwiftThrowHelper.ThrowIfTrue(
 198223            !Active,
 198224            nameof(SolidBody2D),
 198225            "2D body runtime state cannot change before initialization or after deactivation.");
 196226        SwiftThrowHelper.ThrowIfTrue(
 196227            !Context.Physics2D.TryGetColliderById(Collider.Id, out LSCollider2D? registeredCollider)
 196228                || !ReferenceEquals(registeredCollider, Collider),
 196229            nameof(SolidBody2D),
 196230            "2D body runtime state cannot change after its registration has been reset or replaced.");
 191231    }
 232
 233    private void ReconcileMotionTypeRegistration(BodyMotionType motionType)
 234    {
 78235        BodyMotionType previousMotionType = _motionType;
 78236        Context.Physics2D.ClearWarmStartCachesForCollider(Collider);
 78237        Context.Constraints2D.ClearSolverCachesForBody(this);
 78238        Context.Physics2D.InvalidateContinuousCollisionStateForMotionTypeChange(this, DynamicId);
 239
 78240        _motionType = motionType;
 78241        Context.Physics2D.RefreshBodyMotionTypeRegistration(this, previousMotionType);
 78242        Context.Physics2D.RefreshColliderServiceRefreshRegistration(Collider);
 78243    }
 244
 245    private void ClearMotionForMotionTypeTransition()
 246    {
 64247        _linearVelocity = Vector2d.Zero;
 64248        _linearAccelerationStore = Vector2d.Zero;
 64249        _deltaAcceleration = Vector2d.Zero;
 64250        _linearSpeed = Fixed64.Zero;
 64251        _angularVelocity = Fixed64.Zero;
 64252        _angularAccelerationStore = Fixed64.Zero;
 64253        _deltaAngularAcceleration = Fixed64.Zero;
 64254        _angularSpeed = Fixed64.Zero;
 64255    }
 256
 257    /// <summary>
 258    /// Selects the deterministic tunneling guard used when this pure 2D body commits frame movement.
 259    /// </summary>
 260    /// <exception cref="System.ArgumentOutOfRangeException">The value is not a declared continuous-collision mode.</exc
 261    public ContinuousCollisionMode ContinuousCollisionMode
 262    {
 263        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 108264        get => _continuousCollisionMode;
 265        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 266        set
 267        {
 388268            value.ThrowIfInvalid(nameof(value));
 384269            _continuousCollisionMode = value;
 384270        }
 271    }
 272
 273    /// <summary>
 274    /// Gets the number of continuous-collision impacts consumed by the most recent 2D late simulation step.
 275    /// </summary>
 276    public int LastContinuousCollisionToiIterationCount { get; private set; }
 277
 278    /// <summary>
 279    /// Gets whether the most recent 2D late simulation step reached the configured continuous-collision TOI iteration l
 280    /// </summary>
 281    public bool LastContinuousCollisionToiIterationLimitReached { get; private set; }
 282
 283    /// <summary>
 284    /// Gets whether pure 2D yaw rotation is frozen.
 285    /// </summary>
 286    public bool IsRotationFullyFrozen
 287    {
 288        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 22465289        get => (_freezeAxes & BodyFreezeAxes2D.Rotation) == BodyFreezeAxes2D.Rotation;
 290    }
 291
 292    /// <summary>Gets or sets the body mass used to derive solver mass properties.</summary>
 293    public Fixed64 Mass
 294    {
 295        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 104296        get => _mass;
 297        set
 298        {
 4701299            if (_mass == value)
 47300                return;
 301
 4654302            _mass = value;
 4654303            RefreshMassPropertiesFromColliderShape();
 4654304        }
 305    }
 306
 307    /// <summary>Gets the reciprocal mass, or zero when the mass is non-positive.</summary>
 94374308    public Fixed64 InverseMass => _mass > Fixed64.Zero ? Fixed64.One / _mass : Fixed64.Zero;
 309
 310    /// <summary>
 311    /// Gets whether solver-side response may translate this pure 2D body.
 312    /// </summary>
 92311313    public bool CanTranslate => Active && DynamicId >= 0 && IsDynamic && !IsPositionFullyFrozen && InverseMass > Fixed64
 314
 315    /// <summary>
 316    /// Gets whether solver-side response may rotate this pure 2D body around its yaw axis.
 317    /// </summary>
 28132318    public bool CanRotate => Active
 28132319        && DynamicId >= 0
 28132320        && IsDynamic
 28132321        && !IsRotationFullyFrozen
 28132322        && _inverseMomentOfInertia > Fixed64.Zero;
 323
 59205324    internal bool HasSolverMobility => CanTranslate || CanRotate;
 325
 326    /// <summary>
 327    /// Gets the inverse mass that should be used by pure 2D and mixed response.
 328    /// </summary>
 3901329    public Fixed64 EffectiveInverseMass => CanTranslate ? InverseMass : Fixed64.Zero;
 330
 331    /// <summary>
 332    /// Gets the inverse scalar moment that should be used by pure 2D angular response.
 333    /// </summary>
 14096334    public Fixed64 EffectiveInverseMomentOfInertia => CanRotate ? _inverseMomentOfInertia : Fixed64.Zero;
 335
 336    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 337    internal Vector2d ProjectLinearMotion(Vector2d value)
 338    {
 29312339        if (value == Vector2d.Zero || IsPositionFullyFrozen)
 14597340            return Vector2d.Zero;
 341
 14715342        Fixed64 x = (_freezeAxes & BodyFreezeAxes2D.PositionX) == BodyFreezeAxes2D.PositionX ? Fixed64.Zero : value.X;
 14715343        Fixed64 y = (_freezeAxes & BodyFreezeAxes2D.PositionY) == BodyFreezeAxes2D.PositionY ? Fixed64.Zero : value.Y;
 14715344        return new Vector2d(x, y);
 345    }
 346
 347    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 348    private Vector2d ProjectLinearEndpoint(Vector2d start, Vector2d end)
 349    {
 2238350        Fixed64 x = (_freezeAxes & BodyFreezeAxes2D.PositionX) == BodyFreezeAxes2D.PositionX ? start.X : end.X;
 2238351        Fixed64 y = (_freezeAxes & BodyFreezeAxes2D.PositionY) == BodyFreezeAxes2D.PositionY ? start.Y : end.Y;
 2238352        return new Vector2d(x, y);
 353    }
 354
 355    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 356    internal Fixed64 GetConstrainedInverseMass(Vector2d axis)
 357    {
 10379358        if (!CanTranslate || axis == Vector2d.Zero)
 1991359            return Fixed64.Zero;
 360
 8388361        Fixed64 axisMagnitudeSquared = axis.MagnitudeSquared;
 8388362        if (axisMagnitudeSquared <= Fixed64.Epsilon)
 2363            return Fixed64.Zero;
 364
 8386365        Vector2d allowedAxis = ProjectLinearMotion(axis);
 8386366        Fixed64 allowedScale = Vector2d.Dot(allowedAxis, axis) / axisMagnitudeSquared;
 8386367        return allowedScale > Fixed64.Zero ? InverseMass * allowedScale : Fixed64.Zero;
 368    }
 369
 370    /// <summary>Gets the scalar moment of inertia around the yaw axis.</summary>
 10371    public Fixed64 MomentOfInertia => _momentOfInertia;
 372
 373    /// <summary>Gets the reciprocal yaw moment of inertia.</summary>
 7374    public Fixed64 InverseMomentOfInertia => _inverseMomentOfInertia;
 375
 376    /// <summary>Gets the signed yaw velocity.</summary>
 13103377    public Fixed64 AngularVelocity => _angularVelocity;
 378
 379    /// <summary>Gets the accumulated yaw acceleration.</summary>
 8380    public Fixed64 AngularAcceleration => _angularAccelerationStore;
 381
 382    /// <summary>Gets the absolute yaw speed.</summary>
 16383    public Fixed64 AngularSpeed => _angularSpeed;
 384
 385    /// <summary>Gets the authoritative planar X/Z position.</summary>
 386    public Vector2d Position
 387    {
 388        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 24673389        get => _position;
 390    }
 391
 392    /// <summary>Gets the authoritative yaw rotation.</summary>
 393    public Fixed64 Rotation
 394    {
 395        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 23785396        get => _rotation;
 397    }
 398
 399    /// <summary>Gets the authoritative planar linear velocity.</summary>
 12476400    public Vector2d LinearVelocity => _linearVelocity;
 401
 402    /// <summary>Gets the magnitude of <see cref="LinearVelocity"/>.</summary>
 639403    public Fixed64 LinearSpeed => _linearSpeed;
 404
 405    /// <summary>Gets or sets the planar gravity acceleration applied to this body.</summary>
 406    public Vector2d Gravity { get; set; } = Vector2d.Zero;
 407
 4645408    private Fixed64 _gravityScale = Fixed64.One;
 409
 410    /// <summary>
 411    /// Multiplies this body's planar gravity vector. Zero disables body-authored gravity acceleration.
 412    /// </summary>
 413    public Fixed64 GravityScale
 414    {
 415        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 104416        get => _gravityScale;
 417        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 418        set
 419        {
 60420            SwiftThrowHelper.ThrowIfArgument(
 60421                value < Fixed64.Zero,
 60422                nameof(value),
 60423                "2D gravity scale cannot be negative.");
 59424            _gravityScale = value;
 59425        }
 426    }
 427
 428    /// <summary>
 429    /// Enables deterministic sleep evaluation for this body. Disabling sleep wakes a sleeping body immediately.
 430    /// </summary>
 431    public bool SleepEnabled
 432    {
 3165433        get => _sleepEnabled;
 434        set
 435        {
 64436            if (_sleepEnabled == value)
 53437                return;
 438
 11439            _sleepEnabled = value;
 11440            if (!value)
 10441                Wake();
 11442        }
 443    }
 444
 445    /// <summary>
 446    /// Number of consecutive fixed frames below sleep thresholds required before the body sleeps.
 447    /// </summary>
 448    public int SleepFrameThreshold
 449    {
 3746450        get => _sleepFrameThreshold;
 451        set
 452        {
 63453            SwiftThrowHelper.ThrowIfNegative(value, nameof(value));
 62454            _sleepFrameThreshold = value;
 62455        }
 456    }
 457
 458    /// <summary>
 459    /// Linear speed at or below which the body can count toward sleeping.
 460    /// </summary>
 461    public Fixed64 SleepLinearSpeedThreshold
 462    {
 2787463        get => _sleepLinearSpeedThreshold;
 464        set
 465        {
 59466            SwiftThrowHelper.ThrowIfArgument(
 59467                value < Fixed64.Zero,
 59468                nameof(value),
 59469                "2D sleep linear speed threshold cannot be negative.");
 58470            _sleepLinearSpeedThreshold = value;
 58471        }
 472    }
 473
 474    /// <summary>
 475    /// Angular speed at or below which the body can count toward sleeping.
 476    /// </summary>
 477    public Fixed64 SleepAngularSpeedThreshold
 478    {
 479        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 108480        get => _sleepAngularSpeedThreshold;
 481        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 482        set
 483        {
 61484            SwiftThrowHelper.ThrowIfArgument(
 61485                value < Fixed64.Zero,
 61486                nameof(value),
 61487                "2D angular sleep threshold cannot be negative.");
 61488            _sleepAngularSpeedThreshold = value;
 61489        }
 490    }
 491
 492    /// <summary>Gets whether the body is sleeping.</summary>
 49066493    public bool IsSleeping => _isSleeping;
 494
 48447495    internal bool IsAwakeForCollision => HasSolverMobility && !IsSleeping;
 496
 497    /// <summary>Initializes and registers the body at an authoritative planar pose.</summary>
 498    public void Initialize(
 499        Vector2d position,
 500        Fixed64 rotation = default,
 501        BodyMotionType motionType = BodyMotionType.Dynamic)
 502    {
 4660503        motionType.ThrowIfInvalid(nameof(motionType));
 4660504        SwiftThrowHelper.ThrowIfTrue(Active, nameof(SolidBody2D), "2D body is already initialized.");
 4660505        SwiftThrowHelper.ThrowIfArgument(
 4660506            Collider.Id >= 0
 4660507                || (Collider.HasHostBinding && !ReferenceEquals(Collider.Body, this)),
 4660508            nameof(Collider),
 4660509            "Body collider must be unregistered and free of another host binding before initialization.");
 4658510        Fixed64 canonicalRotation = CanonicalizeRotation(rotation);
 4658511        Collider.PreflightBodyInitialization(this, position, canonicalRotation);
 512
 4652513        _position = position;
 4652514        _rotation = canonicalRotation;
 4652515        _linearVelocity = Vector2d.Zero;
 4652516        _linearAccelerationStore = Vector2d.Zero;
 4652517        _deltaAcceleration = Vector2d.Zero;
 4652518        _linearSpeed = Fixed64.Zero;
 4652519        _angularVelocity = Fixed64.Zero;
 4652520        _angularAccelerationStore = Fixed64.Zero;
 4652521        _deltaAngularAcceleration = Fixed64.Zero;
 4652522        _angularSpeed = Fixed64.Zero;
 4652523        _isSleeping = false;
 4652524        _motionType = motionType;
 4652525        _sleepFrameCount = 0;
 4652526        InvalidateContinuousCollisionFrame();
 4652527        ResetGroundingForInitialize(position);
 4652528        Active = true;
 4652529        Collider.Initialize(this);
 4652530        RefreshMassPropertiesFromColliderShape();
 4652531        Context.Physics2D.AssimilateBody(this, motionType);
 4652532        CheckGroundForSimulation();
 4652533    }
 534
 535    /// <summary>
 536    /// Gets a world-space point from the authoritative 2D body pose and committed collider scale.
 537    /// </summary>
 538    public Vector2d GetWorldPoint(Vector2d point)
 539    {
 4540        bool transformed = TryGetWorldPoint(point, out Vector2d result);
 4541        SwiftThrowHelper.ThrowIfTrue(
 4542            !transformed,
 4543            nameof(Collider),
 4544            "Cannot get the world point because the 2D collider has no committed scale or the final coordinate is not re
 3545        return result;
 546    }
 547
 548    /// <summary>
 549    /// Attempts to get a world-space point from the authoritative 2D body pose and committed collider scale.
 550    /// </summary>
 551    public bool TryGetWorldPoint(Vector2d point, out Vector2d result)
 552    {
 215553        if (!Collider.TryGetCommittedOwnerScale(out Vector2d scale))
 554        {
 2555            result = Vector2d.Zero;
 2556            return false;
 557        }
 558
 213559        return Vector2d.TryTransformScaledPoint(
 213560            Position,
 213561            point,
 213562            scale,
 213563            Rotation,
 213564            out result);
 565    }
 566
 567    /// <summary>
 568    /// Gets a body-local point from the authoritative 2D body pose and committed collider scale.
 569    /// </summary>
 570    public Vector2d GetLocalPoint(Vector2d point)
 571    {
 3572        bool transformed = TryGetLocalPoint(point, out Vector2d result);
 3573        SwiftThrowHelper.ThrowIfTrue(
 3574            !transformed,
 3575            nameof(Collider),
 3576            "Cannot get the local point because the 2D collider has no committed scale, its scale is singular, or the fi
 2577        return result;
 578    }
 579
 580    /// <summary>
 581    /// Attempts to get a body-local point from the authoritative 2D body pose and committed collider scale.
 582    /// </summary>
 583    public bool TryGetLocalPoint(Vector2d point, out Vector2d result)
 584    {
 214585        if (!Collider.TryGetCommittedOwnerScale(out Vector2d scale))
 586        {
 2587            result = Vector2d.Zero;
 2588            return false;
 589        }
 590
 212591        return Vector2d.TryInverseTransformScaledPoint(
 212592            Position,
 212593            point,
 212594            scale,
 212595            Rotation,
 212596            out result);
 597    }
 598
 599    /// <summary>
 600    /// Resets authoritative 2D pose and clears accumulated linear/angular motion for deterministic fixture reuse.
 601    /// </summary>
 602    /// <param name="position">The new X/Z-plane position.</param>
 603    /// <param name="rotation">The new yaw rotation in radians.</param>
 604    public void ResetPosition(Vector2d position = default, Fixed64 rotation = default)
 605    {
 92606        PreflightStaticPoseChange();
 91607        Fixed64 canonicalRotation = CanonicalizeRotation(rotation);
 91608        SwiftThrowHelper.ThrowIfTrue(
 91609            !Collider.TryPrepareBodyPose(position, canonicalRotation),
 91610            nameof(position),
 91611            "The requested 2D body pose produces collider geometry outside the representable coordinate domain.");
 90612        _linearVelocity = Vector2d.Zero;
 90613        _linearAccelerationStore = Vector2d.Zero;
 90614        _deltaAcceleration = Vector2d.Zero;
 90615        _linearSpeed = Fixed64.Zero;
 90616        _angularVelocity = Fixed64.Zero;
 90617        _angularAccelerationStore = Fixed64.Zero;
 90618        _deltaAngularAcceleration = Fixed64.Zero;
 90619        _angularSpeed = Fixed64.Zero;
 90620        bool wasSleeping = _isSleeping;
 90621        _isSleeping = false;
 90622        _sleepFrameCount = 0;
 90623        _position = position;
 90624        _rotation = canonicalRotation;
 90625        InvalidateContinuousCollisionFrame();
 90626        ResetGroundingForInitialize(position);
 627
 90628        if (!Active)
 1629            return;
 630
 89631        Collider.PublishPreparedExplicitBodyPose();
 89632        RefreshStaticColliderAfterExplicitPoseChange();
 89633        if (wasSleeping)
 1634            Context.Collisions2D.RefreshPartitionAwakeState(Collider);
 89635    }
 636
 637    /// <summary>Puts an eligible body to sleep and clears its motion.</summary>
 638    public void Sleep()
 639    {
 211640        if (!CanSleep)
 2641            return;
 642
 209643        _isSleeping = true;
 209644        _sleepFrameCount = SleepFrameThreshold;
 209645        _linearVelocity = Vector2d.Zero;
 209646        _linearAccelerationStore = Vector2d.Zero;
 209647        _deltaAcceleration = Vector2d.Zero;
 209648        _linearSpeed = Fixed64.Zero;
 209649        _angularVelocity = Fixed64.Zero;
 209650        _angularAccelerationStore = Fixed64.Zero;
 209651        _deltaAngularAcceleration = Fixed64.Zero;
 209652        _angularSpeed = Fixed64.Zero;
 209653        Context.Collisions2D.RefreshPartitionAwakeState(Collider);
 209654    }
 655
 656    /// <summary>Wakes the body and resumes collision participation.</summary>
 657    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 658    public void Wake()
 659    {
 1174660        bool wasSleeping = _isSleeping;
 1174661        _sleepFrameCount = 0;
 1174662        _isSleeping = false;
 1174663        if (Active && wasSleeping)
 68664            Context.Collisions2D.RefreshPartitionAwakeState(Collider);
 1174665    }
 666
 667    internal void WakeFromCollision()
 668    {
 2730669        if (!_isSleeping)
 2648670            return;
 671
 82672        _sleepFrameCount = 0;
 82673        _isSleeping = false;
 82674        Context.Collisions2D.RefreshPartitionAwakeState(Collider);
 82675    }
 676
 6677    internal void LateSimulate() => LateSimulate(updateSleepState: true, updateColliderState: true);
 678
 679    internal void LateSimulate(bool updateSleepState, bool updateColliderState)
 680    {
 2327681        if (!Active)
 1682            return;
 683
 2326684        CaptureGroundedStepState();
 685        try
 686        {
 2326687            LastContinuousCollisionToiIterationCount = 0;
 2326688            LastContinuousCollisionToiIterationLimitReached = false;
 689
 2326690            if (TryConsumeContinuousCollisionHandoff(updateSleepState, updateColliderState))
 39691                return;
 692
 2287693            EnsureContinuousCollisionFramePrepared(Context.LateSimulateToken);
 694
 2287695            if (IsKinematic)
 166696                UpdateKinematicPositionAndRotation(updateColliderState);
 697
 2287698            if (!HasSolverMobility)
 699            {
 171700                _linearAccelerationStore = Vector2d.Zero;
 171701                _deltaAcceleration = Vector2d.Zero;
 171702                _angularAccelerationStore = Fixed64.Zero;
 171703                _deltaAngularAcceleration = Fixed64.Zero;
 171704                return;
 705            }
 706
 2116707            if (_isSleeping)
 65708                return;
 709
 2051710            Fixed64 startRotation = _rotation;
 2051711            Fixed64 proposedRotation = startRotation + _angularVelocity * Context.DeltaTime;
 712
 2051713            Vector2d startPosition = _position;
 2051714            Vector2d proposedPosition = startPosition + _linearVelocity * Context.DeltaTime;
 2051715            if (ShouldUseRotationalContinuousCollisionArbiter(
 2051716                    startPosition,
 2051717                    proposedPosition,
 2051718                    startRotation,
 2051719                    proposedRotation,
 2051720                    forceContinuous: false))
 721            {
 154722                TryResolveRotationalContinuousCollision(
 154723                    startPosition,
 154724                    ref proposedPosition,
 154725                    startRotation,
 154726                    ref proposedRotation);
 727            }
 728            else
 729            {
 1897730                TryResolveContinuousCollision(startPosition, ref proposedPosition);
 731            }
 2050732            proposedPosition = ProjectLinearEndpoint(startPosition, proposedPosition);
 2050733            _position = proposedPosition;
 2050734            _rotation = CanonicalizeRotation(proposedRotation);
 2050735            if (updateColliderState)
 10736                Collider.Rebuild();
 737
 2050738            if (updateSleepState)
 2739                UpdateSleepState();
 2050740        }
 741        finally
 742        {
 2326743            CompleteGroundedStepState();
 2326744        }
 2325745    }
 746
 747    internal void UpdateSleepStateAfterPhysicsStep()
 748    {
 2268749        if (!_isSleeping)
 2224750            UpdateSleepState();
 2268751    }
 752
 753    internal void OnVisualize()
 754    {
 6755        if (IsKinematic)
 1756            return;
 757
 5758        SetHostWorldPose(Agent.Transform, _position, _rotation);
 5759    }
 760
 761    private void UpdateKinematicPositionAndRotation(bool updateColliderState)
 762    {
 166763        Vector2d startPosition = _position;
 166764        Fixed64 startRotation = _rotation;
 166765        FixedTransform transform = Agent.Transform;
 166766        Vector2d requestedHostPosition = transform.WorldPositionXZ;
 166767        Fixed64 requestedHostRotation = CanonicalizeRotation(
 166768            transform.WorldRotationXZRadians);
 166769        Vector2d kinematicPosition = ContinuousCollisionFrameEnd;
 166770        Fixed64 kinematicRotation = ContinuousCollisionFrameTargetRotation;
 166771        if (startPosition == kinematicPosition && startRotation == kinematicRotation)
 772        {
 6773            SetHostWorldPose(transform, kinematicPosition, kinematicRotation);
 6774            return;
 775        }
 776
 160777        Wake();
 160778        Vector2d resolvedPosition = kinematicPosition;
 160779        Fixed64 resolvedRotation = kinematicRotation;
 160780        if (ShouldUseRotationalContinuousCollisionArbiter(
 160781                startPosition,
 160782                resolvedPosition,
 160783                startRotation,
 160784                resolvedRotation,
 160785                forceContinuous: false))
 786        {
 75787            TryResolveKinematicRotationalContinuousCollision(
 75788                startPosition,
 75789                ref resolvedPosition,
 75790                startRotation,
 75791                ref resolvedRotation);
 792        }
 793        else
 794        {
 85795            TryResolveKinematicContinuousCollision(startPosition, ref resolvedPosition);
 796        }
 797
 160798        resolvedRotation = CanonicalizeRotation(resolvedRotation);
 160799        if (resolvedPosition != requestedHostPosition
 160800            || resolvedRotation != requestedHostRotation)
 52801            SetHostWorldPose(transform, resolvedPosition, resolvedRotation);
 802
 160803        _position = resolvedPosition;
 160804        _rotation = resolvedRotation;
 160805        if (updateColliderState)
 3806            Collider.Rebuild();
 160807    }
 808
 809    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 810    private static Fixed64 CanonicalizeRotation(Fixed64 rotation) =>
 13450811        PlanarRotation.Canonicalize(rotation);
 812
 813    private static void SetHostWorldPose(
 814        FixedTransform transform,
 815        Vector2d position,
 816        Fixed64 rotation)
 817    {
 164818        Vector3d currentWorldPosition = transform.WorldPosition;
 164819        Vector3d worldPosition = new(position.X, currentWorldPosition.Y, position.Y);
 164820        FixedQuaternion worldRotation = FixedQuaternion.FromAxisAngle(Vector3d.Up, -rotation);
 164821        SwiftThrowHelper.ThrowIfTrue(
 164822            !transform.TrySetWorldPose(worldPosition, worldRotation),
 164823            nameof(FixedTransform),
 164824            "Host transform cannot represent the requested world-space 2D pose.");
 164825    }
 826
 827    private void PublishAuthoritativePose() =>
 47828        SetHostWorldPose(Agent.Transform, _position, _rotation);
 829
 830    /// <summary>
 831    /// Removes this body and its collider from the pure 2D runtime service.
 832    /// </summary>
 833    public void Deactivate()
 834    {
 134835        if (!ReferenceEquals(Collider.Body, this))
 11836            return;
 837
 123838        DiscardContinuousCollisionHandoff();
 123839        InvalidateContinuousCollisionFrame();
 123840        Context.Physics2D.DessimilateBody(this);
 123841        Active = false;
 123842        DynamicId = -1;
 123843        Collider.IsActive = false;
 123844        Collider.ClearPhysicsState();
 123845        Collider.ClearBindingState();
 123846    }
 847}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/2D/SolidBody2D.Grounding.cs

#LineLine coverage
 1//=======================================================================
 2// SolidBody2D.Grounding.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2026-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using FixedMathSharp;
 9using Gravitas.Colliders;
 10using Gravitas.CollisionHandling;
 11using Gravitas.Queries;
 12using SwiftCollections;
 13using System;
 14using System.Runtime.CompilerServices;
 15
 16namespace Gravitas;
 17
 18public sealed partial class SolidBody2D
 19{
 120    private static readonly Vector2d DefaultGroundUpDirection = Vector2d.Forward;
 21
 22    private GroundingMode _groundingMode = GroundingMode.Automatic;
 23    private GroundProbeMode2D _groundProbeMode = GroundProbeMode2D.Auto;
 464524    private bool _useGravityDerivedGroundUpDirection = true;
 464525    private Vector2d _groundUpDirection = DefaultGroundUpDirection;
 26    private Fixed64 _groundProbeRadius;
 464527    private int _lastGroundCheckFrame = int.MinValue;
 28    private const int GroundCheckFrameThreshold = 10;
 464529    private readonly Fixed64 _groundCheckPositionThreshold = Fixed64.FromFraction(1, 100);
 464530    private readonly SwiftList<Physics2DHit> _groundProbeHits = new();
 31
 32    private bool _isGrounded;
 33    private bool _wasGrounded;
 34    private bool _groundedTransitionCapturedForStep;
 35    private long _groundingStateVersion;
 36    private Vector2d _groundNormal;
 37    private bool _hasGroundPoint;
 38    private Vector2d _groundPoint;
 39    private Vector2d _lastGroundedPosition;
 40    private LSCollider2D? _groundCollider;
 41    private uint _groundColliderBroadPhaseVersion;
 42
 43    private bool _hasGroundContactCandidate;
 44    private Fixed64 _groundContactCandidateUpDot;
 45    private Fixed64 _groundContactCandidateDepth;
 46    private int _groundContactCandidateColliderId;
 47    private ulong _groundContactCandidateId;
 48    private Vector2d _groundContactCandidateNormal;
 49    private bool _groundContactCandidateHasPoint;
 50    private Vector2d _groundContactCandidatePoint;
 51    private ColliderLifetimeToken2D _groundContactCandidateRegistration;
 52
 53    /// <summary>
 54    /// Selects whether Gravitas owns planar support detection or preserves host-supplied state.
 55    /// </summary>
 12556    public GroundingMode GroundingMode => _groundingMode;
 57
 58    /// <summary>
 59    /// Selects the pure 2D query primitive used when probing for planar support.
 60    /// </summary>
 61    public GroundProbeMode2D GroundProbeMode
 62    {
 63        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 10764        get => _groundProbeMode;
 65        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1366        set => _groundProbeMode = value;
 67    }
 68
 69    /// <summary>
 70    /// Gets or sets whether non-zero planar gravity defines the support up direction.
 71    /// </summary>
 72    public bool UseGravityDerivedGroundUpDirection
 73    {
 74        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 10375        get => _useGravityDerivedGroundUpDirection;
 76        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 477        set => _useGravityDerivedGroundUpDirection = value;
 78    }
 79
 80    /// <summary>
 81    /// Gets or sets the fallback support up direction in the X/Z simulation plane.
 82    /// </summary>
 83    public Vector2d GroundUpDirection
 84    {
 85        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 10386        get => _groundUpDirection;
 87        set
 88        {
 489            SwiftThrowHelper.ThrowIfArgument(
 490                value.MagnitudeSquared <= Fixed64.Epsilon,
 491                nameof(value),
 492                "2D ground up direction must be non-zero.");
 493            _groundUpDirection = value.Normalized;
 494        }
 95    }
 96
 97    /// <summary>
 98    /// Optional explicit radius for swept-circle support probes. A zero value derives the radius from the collider.
 99    /// </summary>
 100    public Fixed64 GroundProbeRadius
 101    {
 102        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 105103        get => _groundProbeRadius;
 104        set
 105        {
 7106            SwiftThrowHelper.ThrowIfArgument(value < Fixed64.Zero, nameof(value), "2D ground probe radius cannot be nega
 7107            _groundProbeRadius = value;
 7108        }
 109    }
 110
 111    /// <summary>
 112    /// Probe distance used while the body is already grounded.
 113    /// </summary>
 114    public Fixed64 GroundedDistanceRay { get; set; } = Fixed64.Half;
 115
 116    /// <summary>
 117    /// Probe distance used while the body is airborne.
 118    /// </summary>
 119    public Fixed64 GroundDownDistanceOnAir { get; set; } = Fixed64.Half;
 120
 121    /// <summary>
 122    /// Minimum dot product between a candidate support normal and resolved up direction.
 123    /// </summary>
 124    public Fixed64 GroundMinNormalDot { get; set; } = Fixed64.Half;
 125
 126    /// <summary>
 127    /// Gets whether this body currently has planar support.
 128    /// </summary>
 129    public bool IsGrounded
 130    {
 69131        get => _isGrounded;
 132        private set
 133        {
 6981134            if (_isGrounded == value)
 6905135                return;
 136
 76137            _isGrounded = value;
 76138            OnGrounded?.Invoke(value);
 26139        }
 140    }
 141
 142    /// <summary>
 143    /// Gets whether this body was grounded before the latest authoritative grounding refresh or manual grounding change
 144    /// </summary>
 16145    public bool WasGrounded => _wasGrounded;
 146
 147    /// <summary>
 148    /// Gets the current support normal in the X/Z simulation plane.
 149    /// </summary>
 35150    public Vector2d GroundNormal => _groundNormal;
 151
 152    /// <summary>
 153    /// Gets whether the current support point can be represented in the X/Z
 154    /// simulation plane.
 155    /// </summary>
 13156    public bool HasGroundPoint => _hasGroundPoint;
 157
 158    /// <summary>
 159    /// Gets the current support point in the X/Z simulation plane.
 160    /// </summary>
 161    /// <exception cref="InvalidOperationException">
 162    /// No grounded point is available or the conceptual contact point is
 163    /// outside the scalar range.
 164    /// </exception>
 165    public Vector2d GroundPoint =>
 33166        _hasGroundPoint
 33167            ? _groundPoint
 33168            : throw new InvalidOperationException(
 33169                "No representable ground point is available. Use TryGetGroundPoint.");
 170
 171    /// <summary>
 172    /// Attempts to get the current support point.
 173    /// </summary>
 174    public bool TryGetGroundPoint(out Vector2d point)
 175    {
 3176        point = _hasGroundPoint ? _groundPoint : default;
 3177        return _hasGroundPoint;
 178    }
 179
 180    /// <summary>
 181    /// Gets the body position captured when the current support state was accepted.
 182    /// </summary>
 4183    public Vector2d LastGroundedPosition => _lastGroundedPosition;
 184
 185    /// <summary>
 186    /// Raised when grounded state changes.
 187    /// </summary>
 188    public Action<bool>? OnGrounded;
 189
 190    /// <summary>
 191    /// Gives the host ownership of grounded state and disables automatic support refresh.
 192    /// </summary>
 193    public void UseManualGrounding(bool clearGrounding = true)
 194    {
 8195        _groundingMode = GroundingMode.Manual;
 8196        if (!clearGrounding)
 2197            return;
 198
 6199        CaptureGroundedTransitionState();
 6200        ClearGrounding();
 6201    }
 202
 203    /// <summary>
 204    /// Returns grounded-state ownership to Gravitas and optionally refreshes support immediately.
 205    /// </summary>
 206    public void UseAutomaticGrounding(bool checkGroundImmediately = true)
 207    {
 4208        _groundingMode = GroundingMode.Automatic;
 4209        if (checkGroundImmediately && Active)
 210        {
 2211            CaptureGroundedTransitionState();
 2212            CheckGround(force: true);
 213        }
 4214    }
 215
 216    /// <summary>
 217    /// Sets host-owned planar grounded state for manual support sources such as deterministic tilemaps.
 218    /// </summary>
 219    public void SetManualGrounding(Vector2d groundPoint, Vector2d groundNormal)
 220    {
 8221        SwiftThrowHelper.ThrowIfArgument(
 8222            groundNormal.MagnitudeSquared <= Fixed64.Epsilon,
 8223            nameof(groundNormal),
 8224            "Manual 2D grounding normal must be non-zero.");
 225
 8226        _groundingMode = GroundingMode.Manual;
 8227        CaptureGroundedTransitionState();
 8228        SetGroundingState(groundPoint, groundNormal.Normalized, null);
 8229    }
 230
 231    /// <summary>
 232    /// Clears host-owned planar grounded state while leaving automatic support disabled.
 233    /// </summary>
 234    public void ClearManualGrounding()
 235    {
 2236        _groundingMode = GroundingMode.Manual;
 2237        CaptureGroundedTransitionState();
 2238        ClearGrounding();
 2239    }
 240
 241    /// <summary>
 242    /// Forces an immediate automatic support probe when Gravitas owns grounding.
 243    /// </summary>
 244    public void CheckGround()
 245    {
 27246        if (_groundingMode == GroundingMode.Manual)
 1247            return;
 248
 26249        CaptureGroundedTransitionState();
 26250        CheckGround(force: true);
 26251    }
 252
 253    internal void BeginAutomaticGroundingRefresh()
 254    {
 2287255        if (!CanUseAutomaticGrounding)
 256        {
 165257            ClearGroundingForAutomaticRefresh();
 165258            return;
 259        }
 260
 2122261        CaptureGroundedStepState();
 2122262        ClearGroundContactCandidate();
 2122263    }
 264
 265    internal void TryAcceptContactGroundCandidate(
 266        LSCollider2D ownCollider,
 267        LSCollider2D otherCollider,
 268        ManifoldContact2D contact,
 269        bool ownColliderIsA)
 270    {
 994271        if (!CanUseAutomaticGrounding || !ReferenceEquals(ownCollider, Collider))
 454272            return;
 273
 540274        if (!IsValidGroundCollider(otherCollider))
 44275            return;
 276
 496277        Vector2d normal = ownColliderIsA ? -contact.Normal : contact.Normal;
 496278        if (normal.MagnitudeSquared <= Fixed64.Epsilon)
 1279            return;
 280
 495281        normal = normal.Normalized;
 495282        Fixed64 upDot = Vector2d.Dot(normal, ResolveGroundUpDirection());
 495283        if (upDot < GroundMinNormalDot)
 453284            return;
 285
 286        Vector2d point;
 42287        bool hasPoint = ownColliderIsA
 42288            ? contact.TryGetPointA(out point)
 42289            : contact.TryGetPointB(out point);
 42290        int otherId = otherCollider.Id;
 42291        if (_hasGroundContactCandidate)
 292        {
 10293            int compare = CompareGroundContactCandidate(
 10294                upDot,
 10295                contact.Depth,
 10296                otherId,
 10297                contact.ContactId);
 10298            if (compare >= 0)
 6299                return;
 300        }
 301
 36302        _hasGroundContactCandidate = true;
 36303        _groundContactCandidateUpDot = upDot;
 36304        _groundContactCandidateDepth = contact.Depth;
 36305        _groundContactCandidateColliderId = otherId;
 36306        _groundContactCandidateId = contact.ContactId;
 36307        _groundContactCandidateNormal = normal;
 36308        _groundContactCandidateHasPoint = hasPoint;
 36309        _groundContactCandidatePoint = point;
 36310        _groundContactCandidateRegistration = new ColliderLifetimeToken2D(otherCollider);
 36311    }
 312
 313    internal void CompleteAutomaticGroundingRefresh()
 314    {
 2286315        if (!CanUseAutomaticGrounding)
 316        {
 165317            CompleteGroundedStepState();
 165318            return;
 319        }
 320
 2121321        ColliderLifetimeToken2D candidateRegistration = _groundContactCandidateRegistration;
 2121322        if (_hasGroundContactCandidate
 2121323            && candidateRegistration.IsActive
 2121324            && IsValidGroundCollider(candidateRegistration.Collider))
 325        {
 29326            var bodyRegistration = new ColliderLifetimeToken2D(Collider);
 29327            long groundingStateVersion = SetGroundingState(
 29328                _groundContactCandidatePoint,
 29329                _groundContactCandidateNormal,
 29330                candidateRegistration.Collider,
 29331                _groundContactCandidateHasPoint);
 29332            if (!RevalidateAutomaticGroundingWrite(
 29333                    groundingStateVersion,
 29334                    bodyRegistration,
 29335                    candidateRegistration))
 336            {
 1337                return;
 338            }
 339
 28340            ClearGroundContactCandidate();
 28341            CompleteGroundedStepState();
 28342            return;
 343        }
 344
 2092345        ClearGroundContactCandidate();
 2092346        CheckGround(force: false);
 2092347        CompleteGroundedStepState();
 2092348    }
 349
 4652350    internal void CheckGroundForSimulation() => CheckGround(force: false);
 351
 352    internal Vector2d RemoveIntoGroundComponent(Vector2d value)
 353    {
 4256354        value = ProjectLinearMotion(value);
 4256355        if (!_isGrounded || _groundNormal.MagnitudeSquared <= Fixed64.Epsilon)
 4244356            return value;
 357
 12358        Fixed64 intoGround = Vector2d.Dot(value, _groundNormal);
 12359        return intoGround < Fixed64.Zero
 12360            ? ProjectLinearMotion(value - _groundNormal * intoGround)
 12361            : value;
 362    }
 363
 364    private bool CanUseAutomaticGrounding =>
 12401365        Active && CanTranslate && _groundingMode == GroundingMode.Automatic;
 366
 367    private void CheckGround(bool force)
 368    {
 6772369        if (_groundingMode == GroundingMode.Manual)
 1370            return;
 371
 6771372        if (!CanUseAutomaticGrounding)
 373        {
 1528374            ClearGrounding();
 1528375            return;
 376        }
 377
 5243378        LSCollider2D? cachedGroundCollider = _groundCollider;
 5243379        bool frameGuard = !force
 5243380            && _isGrounded
 5243381            && cachedGroundCollider != null
 5243382            && IsValidGroundCollider(cachedGroundCollider)
 5243383            && cachedGroundCollider.BroadPhaseVersion == _groundColliderBroadPhaseVersion
 5243384            && Vector2d.Distance(_lastGroundedPosition, _position) < _groundCheckPositionThreshold
 5243385            && Context.FrameCount - _lastGroundCheckFrame < GroundCheckFrameThreshold;
 5243386        if (frameGuard)
 2387            return;
 388
 5241389        _lastGroundCheckFrame = Context.FrameCount;
 5241390        Vector2d up = ResolveGroundUpDirection();
 5241391        Vector2d down = -up;
 5241392        Fixed64 distance = _isGrounded ? GroundedDistanceRay : GroundDownDistanceOnAir;
 5241393        if (distance <= Fixed64.Zero)
 394        {
 8395            ClearGrounding();
 8396            return;
 397        }
 398
 5233399        GroundProbeMode2D mode = ResolveGroundProbeMode();
 5233400        Fixed64 radius = mode == GroundProbeMode2D.SweptCircle
 5233401            ? ResolveGroundProbeRadius()
 5233402            : Fixed64.Zero;
 5233403        Vector2d start = _position;
 5233404        Vector2d end = start + down * distance;
 5233405        bool foundGround = TryFindGroundHit(mode, radius, start, end, out Physics2DHit hit);
 5233406        Context.Diagnostics.EmitGroundProbe(this, mode, start, end, radius, foundGround, hit);
 407
 5233408        if (!foundGround)
 409        {
 5192410            ClearGrounding();
 5192411            return;
 412        }
 413
 41414        var bodyRegistration = new ColliderLifetimeToken2D(Collider);
 41415        var supportRegistration = new ColliderLifetimeToken2D(hit.Collider);
 41416        bool hasGroundPoint = hit.TryGetPoint(out Vector2d groundPoint);
 41417        long groundingStateVersion = SetGroundingState(
 41418            groundPoint,
 41419            hit.Normal.Normalized,
 41420            hit.Collider,
 41421            hasGroundPoint);
 41422        RevalidateAutomaticGroundingWrite(groundingStateVersion, bodyRegistration, supportRegistration);
 41423    }
 424
 425    private bool TryFindGroundHit(
 426        GroundProbeMode2D mode,
 427        Fixed64 radius,
 428        Vector2d start,
 429        Vector2d end,
 430        out Physics2DHit hit)
 431    {
 5233432        if (mode == GroundProbeMode2D.SweptCircle && radius > Fixed64.Epsilon)
 5214433            return TryFindGroundHitWithSweptCircle(start, end, radius, out hit);
 434
 19435        return TryFindGroundHitWithRay(start, end, out hit);
 436    }
 437
 438    private bool TryFindGroundHitWithRay(Vector2d start, Vector2d end, out Physics2DHit hit)
 439    {
 19440        int hitCount = Context.Query2D.RaycastAll(start, end, Context.Settings.GroundCheckLayerMask, _groundProbeHits);
 62441        for (int i = 0; i < hitCount; i++)
 442        {
 18443            Physics2DHit current = _groundProbeHits[i];
 18444            if (!IsValidGroundHit(current))
 445                continue;
 446
 6447            hit = current;
 6448            return true;
 449        }
 450
 13451        hit = default;
 13452        return false;
 453    }
 454
 455    private bool TryFindGroundHitWithSweptCircle(Vector2d start, Vector2d end, Fixed64 radius, out Physics2DHit hit)
 456    {
 5214457        int hitCount = Context.Query2D.SweepCircleAgainstStaticAll(
 5214458            start,
 5214459            end,
 5214460            radius,
 5214461            Context.Settings.GroundCheckLayerMask,
 5214462            _groundProbeHits,
 5214463            Collider,
 5214464            includeTriggers: false);
 465
 11384466        for (int i = 0; i < hitCount; i++)
 467        {
 513468            Physics2DHit current = _groundProbeHits[i];
 513469            if (!IsValidGroundHit(current))
 470                continue;
 471
 35472            hit = current;
 35473            return true;
 474        }
 475
 5179476        hit = default;
 5179477        return false;
 478    }
 479
 480    private bool IsValidGroundHit(Physics2DHit hit)
 481    {
 531482        LSCollider2D hitCollider = hit.Collider;
 531483        if (ReferenceEquals(hitCollider, Collider))
 10484            return false;
 485
 521486        if (!IsValidGroundCollider(hitCollider))
 7487            return false;
 488
 514489        Vector2d up = ResolveGroundUpDirection();
 514490        return Vector2d.Dot(hit.Normal.Normalized, up) >= GroundMinNormalDot;
 491    }
 492
 493    private bool IsValidGroundCollider(LSCollider2D collider)
 494    {
 1154495        if (!collider.IsActive
 1154496            || collider.IsTrigger
 1154497            || !Context.Settings.GroundCheckLayerMask.Includes(collider.Layer)
 1154498            || !ColliderCollisionFilter.AllowsPhysicalPair(Collider, collider))
 499        {
 13500            return false;
 501        }
 502
 1141503        SolidBody2D? body = collider.Body;
 1141504        return collider.IsStatic || body!.IsKinematic;
 505    }
 506
 507    private GroundProbeMode2D ResolveGroundProbeMode()
 508    {
 5233509        if (_groundProbeMode != GroundProbeMode2D.Auto)
 19510            return _groundProbeMode;
 511
 5214512        return ResolveGroundProbeRadius() > Fixed64.Epsilon
 5214513            ? GroundProbeMode2D.SweptCircle
 5214514            : GroundProbeMode2D.Ray;
 515    }
 516
 517    private Fixed64 ResolveGroundProbeRadius()
 518    {
 10428519        if (_groundProbeRadius > Fixed64.Zero)
 11520            return _groundProbeRadius;
 521
 10417522        return Collider switch
 10417523        {
 10417524            LSCircleCollider2D or
 10417525            LSCapsuleCollider2D or
 10417526            LSAABBoxCollider2D or
 10417527            LSPolygonCollider2D or
 10417528            LSCompoundCollider2D =>
 10413529                Collider.CanonicalGroundProbeRadius,
 4530            _ => Fixed64.Zero
 10417531        };
 532    }
 533
 534    private Vector2d ResolveGroundUpDirection()
 535    {
 6250536        if (_useGravityDerivedGroundUpDirection && Gravity.MagnitudeSquared > Fixed64.Epsilon)
 6537            return (-Gravity).Normalized;
 538
 6244539        return _groundUpDirection;
 540    }
 541
 542    private void CaptureGroundedTransitionState()
 543    {
 4643544        _wasGrounded = _isGrounded;
 4643545        _groundedTransitionCapturedForStep = true;
 4643546    }
 547
 548    private void CaptureGroundedStepState()
 549    {
 4609550        if (!_groundedTransitionCapturedForStep)
 4599551            CaptureGroundedTransitionState();
 4609552    }
 553
 4772554    private void CompleteGroundedStepState() => _groundedTransitionCapturedForStep = false;
 555
 556    private void ClearGrounding()
 557    {
 6903558        _groundingStateVersion++;
 6903559        _groundNormal = Vector2d.Zero;
 6903560        _hasGroundPoint = false;
 6903561        _groundPoint = Vector2d.Zero;
 6903562        _groundCollider = null;
 6903563        _groundColliderBroadPhaseVersion = 0;
 6903564        IsGrounded = false;
 6903565    }
 566
 567    private void ClearGroundingForAutomaticRefresh()
 568    {
 165569        if (_groundingMode != GroundingMode.Automatic)
 4570            return;
 571
 161572        CaptureGroundedStepState();
 161573        ClearGrounding();
 161574        CompleteGroundedStepState();
 161575    }
 576
 577    private long SetGroundingState(
 578        Vector2d groundPoint,
 579        Vector2d groundNormal,
 580        LSCollider2D? groundCollider,
 581        bool hasGroundPoint = true)
 582    {
 78583        long groundingStateVersion = ++_groundingStateVersion;
 78584        _hasGroundPoint = hasGroundPoint;
 78585        _groundPoint = groundPoint;
 78586        _groundNormal = groundNormal;
 78587        _lastGroundedPosition = _position;
 78588        _groundCollider = groundCollider;
 78589        _groundColliderBroadPhaseVersion = groundCollider?.BroadPhaseVersion ?? 0;
 78590        IsGrounded = true;
 78591        return groundingStateVersion;
 592    }
 593
 594    private void ResetGroundingForInitialize(Vector2d position)
 595    {
 4742596        _groundingStateVersion++;
 4742597        _isGrounded = false;
 4742598        _wasGrounded = false;
 4742599        _groundedTransitionCapturedForStep = false;
 4742600        _groundNormal = Vector2d.Zero;
 4742601        _hasGroundPoint = false;
 4742602        _groundPoint = Vector2d.Zero;
 4742603        _lastGroundedPosition = position;
 4742604        _groundCollider = null;
 4742605        _groundColliderBroadPhaseVersion = 0;
 4742606        _lastGroundCheckFrame = int.MinValue;
 4742607        ClearGroundContactCandidate();
 4742608    }
 609
 610    private bool RevalidateAutomaticGroundingWrite(
 611        long groundingStateVersion,
 612        in ColliderLifetimeToken2D bodyRegistration,
 613        in ColliderLifetimeToken2D supportRegistration)
 614    {
 70615        if (!bodyRegistration.IsCurrentLifetime)
 1616            return false;
 617
 69618        if (_groundingStateVersion != groundingStateVersion)
 3619            return true;
 620
 66621        if (_groundingMode == GroundingMode.Automatic
 66622            && (!bodyRegistration.IsActive
 66623                || !CanUseAutomaticGrounding
 66624                || !supportRegistration.IsActive
 66625                || !IsValidGroundCollider(supportRegistration.Collider)))
 626        {
 6627            ClearGrounding();
 628        }
 629
 66630        return bodyRegistration.IsCurrentLifetime;
 631    }
 632
 633    private void ClearGroundContactCandidate()
 634    {
 9038635        _hasGroundContactCandidate = false;
 9038636        _groundContactCandidateUpDot = Fixed64.Zero;
 9038637        _groundContactCandidateDepth = Fixed64.Zero;
 9038638        _groundContactCandidateColliderId = int.MaxValue;
 9038639        _groundContactCandidateId = ulong.MaxValue;
 9038640        _groundContactCandidateNormal = Vector2d.Zero;
 9038641        _groundContactCandidateHasPoint = false;
 9038642        _groundContactCandidatePoint = Vector2d.Zero;
 9038643        _groundContactCandidateRegistration = default;
 9038644    }
 645
 646    private int CompareGroundContactCandidate(
 647        Fixed64 upDot,
 648        Fixed64 depth,
 649        int colliderId,
 650        ulong contactId)
 651    {
 10652        if (upDot != _groundContactCandidateUpDot)
 3653            return upDot > _groundContactCandidateUpDot ? -1 : 1;
 654
 7655        if (depth != _groundContactCandidateDepth)
 2656            return depth > _groundContactCandidateDepth ? -1 : 1;
 657
 5658        if (colliderId != _groundContactCandidateColliderId)
 2659            return colliderId < _groundContactCandidateColliderId ? -1 : 1;
 660
 3661        if (contactId != _groundContactCandidateId)
 2662            return contactId < _groundContactCandidateId ? -1 : 1;
 663
 1664        return 0;
 665    }
 666}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/2D/SolidBody2D.Motion.cs

#LineLine coverage
 1//=======================================================================
 2// SolidBody2D.Motion.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2026-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using FixedMathSharp;
 9using System.Runtime.CompilerServices;
 10
 11namespace Gravitas;
 12
 13public sealed partial class SolidBody2D
 14{
 15    /// <summary>
 16    /// Queues a planar force in mass-distance-per-time-squared units for
 17    /// integration during the next fixed step.
 18    /// </summary>
 19    /// <param name="force">The X/Z-plane force to apply.</param>
 20    public void AddForce(Vector2d force)
 21    {
 18422        Vector2d accelerationDelta = ProjectLinearMotion(force * EffectiveInverseMass);
 18423        if (accelerationDelta == Vector2d.Zero)
 324            return;
 25
 18126        Wake();
 18127        _deltaAcceleration += accelerationDelta;
 18128    }
 29
 30    /// <summary>
 31    /// Queues a yaw torque in mass-distance-squared-per-time-squared units for
 32    /// integration during the next fixed step.
 33    /// </summary>
 34    /// <param name="torque">The signed yaw torque applied to the body.</param>
 35    public void AddTorque(Fixed64 torque)
 36    {
 2037        Fixed64 accelerationDelta = torque * EffectiveInverseMomentOfInertia;
 2038        if (accelerationDelta == Fixed64.Zero)
 639            return;
 40
 1441        Wake();
 1442        _deltaAngularAcceleration += accelerationDelta;
 1443    }
 44
 45    /// <summary>
 46    /// Applies an X/Z-plane linear impulse immediately as a velocity change.
 47    /// The impulse is expressed in mass-distance-per-time units and does not
 48    /// advance the fixed-step simulation or apply a time-step factor.
 49    /// </summary>
 50    /// <param name="impulse">The planar linear impulse to apply.</param>
 51    public void AddLinearImpulse(Vector2d impulse)
 52    {
 2153        Vector2d velocityDelta = ProjectLinearMotion(impulse * EffectiveInverseMass);
 2154        if (velocityDelta == Vector2d.Zero)
 255            return;
 56
 1957        Wake();
 1958        _linearVelocity += velocityDelta;
 1959        RefreshLinearSpeed();
 1960    }
 61
 62    /// <summary>
 63    /// Applies an immediate yaw-velocity change derived from the supplied
 64    /// angular impulse and inverse moment of inertia. The impulse is expressed
 65    /// in mass-distance-squared-per-time units and does not apply a time-step
 66    /// factor.
 67    /// </summary>
 68    /// <param name="impulse">The signed yaw impulse applied to the body.</param>
 69    public void AddAngularImpulse(Fixed64 impulse)
 70    {
 6171        Fixed64 velocityDelta = impulse * EffectiveInverseMomentOfInertia;
 6172        if (velocityDelta == Fixed64.Zero)
 573            return;
 74
 5675        Wake();
 5676        _angularVelocity += velocityDelta;
 5677        RefreshAngularSpeed();
 5678    }
 79
 80    /// <summary>Sets the authoritative planar position and refreshes collider state.</summary>
 81    public void SetPosition(Vector2d position)
 82    {
 43283        PreflightStaticPoseChange();
 42984        SwiftThrowHelper.ThrowIfTrue(
 42985            !Collider.TryPrepareBodyPose(position, _rotation),
 42986            nameof(position),
 42987            "The requested 2D body position produces collider geometry outside the representable coordinate domain.");
 42788        if (_position != position)
 34489            Wake();
 90
 42791        _position = position;
 42792        Collider.PublishPreparedExplicitBodyPose();
 42793        RefreshStaticColliderAfterExplicitPoseChange();
 42794    }
 95
 96    /// <summary>Sets the authoritative yaw rotation and refreshes collider state.</summary>
 97    public void SetRotation(Fixed64 rotation)
 98    {
 9999        PreflightStaticPoseChange();
 98100        Fixed64 canonicalRotation = CanonicalizeRotation(rotation);
 98101        SwiftThrowHelper.ThrowIfTrue(
 98102            !Collider.TryPrepareBodyPose(_position, canonicalRotation),
 98103            nameof(rotation),
 98104            "The requested 2D body rotation produces collider geometry outside the representable coordinate domain.");
 97105        if (_rotation != canonicalRotation)
 66106            Wake();
 107
 97108        _rotation = canonicalRotation;
 97109        Collider.PublishPreparedExplicitBodyPose();
 97110        RefreshStaticColliderAfterExplicitPoseChange();
 97111    }
 112
 113    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 114    private void RefreshStaticColliderAfterExplicitPoseChange()
 115    {
 613116        if (Active && IsStatic && Context.Settings.RuntimeMode.RunsMixedContacts())
 3117            Context.MixedCollisions.Refresh2DColliderPartition(Collider);
 613118    }
 119
 120    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 121    private void PreflightStaticPoseChange()
 122    {
 623123        if (Active && IsStatic)
 124        {
 15125            ThrowIfRuntimeRegistrationMissing();
 14126            Context.ThrowIfFixedStepMutationNotAllowed();
 11127            Collider.ValidateCurrentRuntimeTransform();
 128        }
 618129    }
 130
 131    private void RefreshPartitionMobility()
 132    {
 182133        if (!Active)
 3134            return;
 135
 179136        if (Collider.IsPartitioned)
 177137            Collider.Simulate();
 138
 179139        Context.Collisions2D.RefreshColliderPartition(Collider);
 179140        if (Context.Settings.RuntimeMode.RunsMixedContacts())
 46141            Context.MixedCollisions.Refresh2DColliderPartition(Collider);
 179142    }
 143
 144    internal void ApplyCollisionLinearVelocityDelta(Vector2d velocityDelta)
 145    {
 1716146        velocityDelta = ProjectLinearMotion(velocityDelta);
 1716147        if (!CanTranslate || velocityDelta == Vector2d.Zero)
 151148            return;
 149
 1565150        WakeFromCollision();
 1565151        _linearVelocity += velocityDelta;
 1565152        RefreshLinearSpeed();
 1565153    }
 154
 155    internal void ApplyCollisionAngularVelocityDelta(Fixed64 velocityDelta)
 156    {
 1688157        if (!CanRotate || velocityDelta == Fixed64.Zero)
 1448158            return;
 159
 240160        WakeFromCollision();
 240161        _angularVelocity += velocityDelta;
 240162        RefreshAngularSpeed();
 240163    }
 164
 165    internal bool CanApplyCollisionVelocityDeltas(
 166        Vector2d linearVelocityDelta,
 167        Fixed64 angularVelocityDelta)
 168    {
 579169        linearVelocityDelta = ProjectLinearMotion(linearVelocityDelta);
 579170        bool linearVelocityFits = Vector2d.TryAdd(
 579171            _linearVelocity,
 579172            linearVelocityDelta,
 579173            out _);
 579174        bool angularVelocityFits = Fixed64.TryAdd(
 579175            _angularVelocity,
 579176            angularVelocityDelta,
 579177            out _);
 579178        return (!CanTranslate | linearVelocityFits)
 579179            & (!CanRotate | angularVelocityFits);
 180    }
 181
 182    internal void ApplyCollisionPositionCorrection(Vector2d positionCorrection)
 183    {
 501184        positionCorrection = ProjectLinearMotion(positionCorrection);
 501185        if (!CanTranslate || positionCorrection == Vector2d.Zero)
 1186            return;
 187
 500188        _position += positionCorrection;
 500189        Collider.Rebuild();
 500190    }
 191
 2438192    private bool CanSleep => SleepEnabled && HasSolverMobility;
 193
 194    private void ApplyFreezeConstraintsToMotion()
 195    {
 182196        _linearVelocity = ProjectLinearMotion(_linearVelocity);
 182197        _linearAccelerationStore = ProjectLinearMotion(_linearAccelerationStore);
 182198        _deltaAcceleration = ProjectLinearMotion(_deltaAcceleration);
 182199        RefreshLinearSpeed();
 200
 182201        if (!CanRotate)
 202        {
 130203            _angularVelocity = Fixed64.Zero;
 130204            _angularAccelerationStore = Fixed64.Zero;
 130205            _deltaAngularAcceleration = Fixed64.Zero;
 130206            _angularSpeed = Fixed64.Zero;
 207        }
 182208    }
 209
 210    private void UpdateSleepState()
 211    {
 2227212        if (!CanSleep)
 213        {
 166214            _sleepFrameCount = 0;
 166215            return;
 216        }
 217
 2061218        if (_linearSpeed > SleepLinearSpeedThreshold || _angularSpeed > _sleepAngularSpeedThreshold)
 219        {
 656220            _sleepFrameCount = 0;
 656221            return;
 222        }
 223
 1405224        if (_sleepFrameCount < SleepFrameThreshold)
 1404225            _sleepFrameCount++;
 226
 1405227        if (_sleepFrameCount >= SleepFrameThreshold)
 3228            Sleep();
 1405229    }
 230
 231    private void RefreshLinearSpeed()
 232    {
 4290233        _linearSpeed = _linearVelocity.Magnitude;
 4290234        if (_linearSpeed <= Fixed64.Epsilon)
 235        {
 1836236            _linearVelocity = Vector2d.Zero;
 1836237            _linearSpeed = Fixed64.Zero;
 238        }
 4290239    }
 240
 241    private void RefreshAngularSpeed()
 242    {
 2675243        _angularSpeed = _angularVelocity.Abs();
 2675244        if (_angularSpeed <= Fixed64.Epsilon)
 245        {
 2174246            _angularVelocity = Fixed64.Zero;
 2174247            _angularSpeed = Fixed64.Zero;
 248        }
 2675249    }
 250
 251    internal void RefreshMassPropertiesFromColliderShape()
 252    {
 17170253        if (!_centerOfMassOffsetExplicit)
 17063254            _localCenterOfMassOffset = Collider.CalculateLocalCenterOfMassOffset();
 255
 17170256        if (_mass <= Fixed64.Zero)
 257        {
 31258            _momentOfInertia = Fixed64.Zero;
 31259            _inverseMomentOfInertia = Fixed64.Zero;
 31260            return;
 261        }
 262
 17139263        _momentOfInertia = Collider.CalculateMomentOfInertia(_mass, _localCenterOfMassOffset);
 17139264        _inverseMomentOfInertia = _momentOfInertia > Fixed64.Zero
 17139265            ? Fixed64.One / _momentOfInertia
 17139266            : Fixed64.Zero;
 17139267    }
 268
 269}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/2D/SolidBody2D.ReplayHash.cs

#LineLine coverage
 1//=======================================================================
 2// SolidBody2D.ReplayHash.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2026-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using Chronicler;
 9using FixedMathSharp.Chronicler;
 10
 11namespace Gravitas;
 12
 13public sealed partial class SolidBody2D
 14{
 15    internal void ContributeReplayHash(
 16        ref ChronicleHashWriter writer,
 17        GravitasReplayHashMode mode)
 18    {
 62219        writer.WriteSection("body.2d", 4);
 62220        writer.WriteInt32(DynamicId);
 62221        writer.WriteBool(Active);
 62222        writer.WriteEnum(_freezeAxes);
 62223        writer.WriteEnum(_motionType);
 62224        writer.WriteVector2d(_position);
 62225        writer.WriteFixed64(_rotation);
 62226        writer.WriteVector2d(_localCenterOfMassOffset);
 62227        writer.WriteBool(_centerOfMassOffsetExplicit);
 62228        writer.WriteVector2d(_linearVelocity);
 62229        writer.WriteVector2d(_linearAccelerationStore);
 62230        writer.WriteVector2d(_deltaAcceleration);
 62231        writer.WriteFixed64(_linearSpeed);
 62232        writer.WriteFixed64(_angularVelocity);
 62233        writer.WriteFixed64(_angularAccelerationStore);
 62234        writer.WriteFixed64(_deltaAngularAcceleration);
 62235        writer.WriteFixed64(_angularSpeed);
 62236        writer.WriteBool(_isSleeping);
 62237        writer.WriteInt32(_sleepFrameCount);
 62238        writer.WriteFixed64(_mass);
 62239        writer.WriteVector2d(Gravity);
 62240        writer.WriteFixed64(_gravityScale);
 62241        writer.WriteBool(SleepEnabled);
 62242        writer.WriteInt32(SleepFrameThreshold);
 62243        writer.WriteFixed64(SleepLinearSpeedThreshold);
 62244        writer.WriteFixed64(_sleepAngularSpeedThreshold);
 62245        writer.WriteEnum(_groundingMode);
 62246        writer.WriteEnum(_groundProbeMode);
 62247        writer.WriteBool(_useGravityDerivedGroundUpDirection);
 62248        writer.WriteVector2d(_groundUpDirection);
 62249        writer.WriteFixed64(_groundProbeRadius);
 62250        writer.WriteFixed64(GroundedDistanceRay);
 62251        writer.WriteFixed64(GroundDownDistanceOnAir);
 62252        writer.WriteFixed64(GroundMinNormalDot);
 62253        writer.WriteBool(_isGrounded);
 62254        writer.WriteBool(_wasGrounded);
 62255        writer.WriteVector2d(_groundNormal);
 62256        writer.WriteBool(_hasGroundPoint);
 62257        writer.WriteVector2d(_groundPoint);
 62258        writer.WriteVector2d(_lastGroundedPosition);
 62259        writer.WriteEnum(_continuousCollisionMode);
 60
 62261        writer.WriteSection("body.2d.ccd-authoritative", 3);
 62262        writer.WriteBool(_continuousCollisionHandoffPending);
 62263        if (_continuousCollisionHandoffPending)
 64        {
 4065            writer.WriteInt32(_continuousCollisionHandoffToken);
 4066            writer.WriteFixed64(_continuousCollisionHandoffRemainingTime);
 4067            writer.WriteInt32(_continuousCollisionHandoffIgnoredCollider3D?.ReplayOrdinal ?? -1);
 4068            writer.WriteInt32(_continuousCollisionHandoffIgnoredCollider2D?.ReplayOrdinal ?? -1);
 4069            WriteContinuousCollisionTrajectory(ref writer);
 70        }
 71
 62272        if (mode != GravitasReplayHashMode.AuthoritativeWithSolverCaches)
 40273            return;
 74
 22075        writer.WriteSection("body.2d.solver-caches", 3);
 22076        writer.WriteInt32(_continuousCollisionFrameToken);
 22077        WriteContinuousCollisionTrajectory(ref writer);
 22078        writer.WriteInt32(_continuousCollisionHandoffToken);
 22079        writer.WriteFixed64(_continuousCollisionHandoffRemainingTime);
 22080        writer.WriteInt32(LastContinuousCollisionToiIterationCount);
 22081        writer.WriteBool(LastContinuousCollisionToiIterationLimitReached);
 22082        writer.WriteFixed64(_momentOfInertia);
 22083        writer.WriteFixed64(_inverseMomentOfInertia);
 22084        writer.WriteInt32(_continuousCollisionHits.Count);
 22085        writer.WriteInt32(_continuousMixedCollisionHits.Count);
 22086    }
 87
 88    private void WriteContinuousCollisionTrajectory(ref ChronicleHashWriter writer)
 89    {
 26090        writer.WriteInt32(_continuousCollisionTrajectory.Count);
 101891        for (int i = 0; i < _continuousCollisionTrajectory.Count; i++)
 92        {
 24993            Gravitas.CollisionHandling.ContinuousCollisionMotionSegment2D segment =
 24994                _continuousCollisionTrajectory[i];
 24995            writer.WriteFixed64(segment.StartFraction);
 24996            writer.WriteFixed64(segment.EndFraction);
 24997            writer.WriteVector2d(segment.StartPosition);
 24998            writer.WriteVector2d(segment.EndPosition);
 24999            writer.WriteVector2d(segment.Displacement);
 249100            writer.WriteFixed64(segment.StartRotation);
 249101            writer.WriteFixed64(segment.AngularDelta);
 249102            writer.WriteFixed64(segment.AngularVelocity);
 103        }
 260104    }
 105}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/2D/SolidBody2D.Serialization.cs

#LineLine coverage
 1//=======================================================================
 2// SolidBody2D.Serialization.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2026-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using Chronicler;
 9using FixedMathSharp;
 10
 11namespace Gravitas;
 12
 13public sealed partial class SolidBody2D
 14{
 15    /// <summary>Records or restores deterministic body state through Chronicler.</summary>
 16    public void RecordData(IChronicler chronicler)
 17    {
 10018        bool active = Active;
 10019        BodyFreezeAxes2D freezeAxes = FreezeAxes;
 10020        BodyMotionType motionType = MotionType;
 10021        Fixed64 mass = Mass;
 10022        Vector2d gravity = Gravity;
 10023        Fixed64 gravityScale = GravityScale;
 10024        bool sleepEnabled = SleepEnabled;
 10025        int sleepFrameThreshold = SleepFrameThreshold;
 10026        Fixed64 sleepLinearSpeedThreshold = SleepLinearSpeedThreshold;
 10027        Fixed64 sleepAngularSpeedThreshold = SleepAngularSpeedThreshold;
 10028        GroundingMode groundingMode = GroundingMode;
 10029        GroundProbeMode2D groundProbeMode = GroundProbeMode;
 10030        bool useGravityDerivedGroundUpDirection = UseGravityDerivedGroundUpDirection;
 10031        Vector2d groundUpDirection = GroundUpDirection;
 10032        Fixed64 groundProbeRadius = GroundProbeRadius;
 10033        Fixed64 groundedDistanceRay = GroundedDistanceRay;
 10034        Fixed64 groundDownDistanceOnAir = GroundDownDistanceOnAir;
 10035        Fixed64 groundMinNormalDot = GroundMinNormalDot;
 10036        ContinuousCollisionMode continuousCollisionMode = ContinuousCollisionMode;
 37
 10038        RecordValues.Look(chronicler, ref active, "Active", false);
 10039        RecordValues.Look(chronicler, ref freezeAxes, "FreezeAxes", BodyFreezeAxes2D.None);
 10040        RecordValues.Look(chronicler, ref motionType, "MotionType", BodyMotionType.Dynamic);
 10041        if (chronicler.Mode == SerializationMode.Loading)
 42        {
 6043            PreflightLoadedMotionType(motionType);
 5744            SwiftThrowHelper.ThrowIfArgument(
 5745                (freezeAxes & ~BodyFreezeAxes2D.All) != BodyFreezeAxes2D.None,
 5746                nameof(freezeAxes),
 5747                "Unsupported 2D freeze axis bits.");
 48        }
 49
 9650        RecordValues.Look(chronicler, ref _position, "Position");
 9651        RecordValues.Look(chronicler, ref _rotation, "Rotation");
 9652        RecordValues.Look(chronicler, ref _localCenterOfMassOffset, "LocalCenterOfMassOffset");
 9653        RecordValues.Look(chronicler, ref _centerOfMassOffsetExplicit, "CenterOfMassOffsetExplicit", false);
 9654        RecordValues.Look(chronicler, ref _linearVelocity, "LinearVelocity");
 9655        RecordValues.Look(chronicler, ref _linearAccelerationStore, "LinearAccelerationStore");
 9656        RecordValues.Look(chronicler, ref _deltaAcceleration, "DeltaAcceleration");
 9657        RecordValues.Look(chronicler, ref _linearSpeed, "LinearSpeed");
 9658        RecordValues.Look(chronicler, ref _angularVelocity, "AngularVelocity");
 9659        RecordValues.Look(chronicler, ref _angularAccelerationStore, "AngularAccelerationStore");
 9660        RecordValues.Look(chronicler, ref _deltaAngularAcceleration, "DeltaAngularAcceleration");
 9661        RecordValues.Look(chronicler, ref _angularSpeed, "AngularSpeed");
 9662        RecordValues.Look(chronicler, ref _isSleeping, "IsSleeping");
 9663        RecordValues.Look(chronicler, ref _sleepFrameCount, "SleepFrameCount");
 9664        RecordValues.Look(chronicler, ref mass, "Mass");
 9665        RecordValues.Look(chronicler, ref gravity, "Gravity", Vector2d.Zero);
 9666        RecordValues.Look(chronicler, ref gravityScale, "GravityScale", Fixed64.One);
 9667        RecordValues.Look(chronicler, ref sleepEnabled, "SleepEnabled", true);
 9668        RecordValues.Look(chronicler, ref sleepFrameThreshold, "SleepFrameThreshold", 16);
 9669        RecordValues.Look(chronicler, ref sleepLinearSpeedThreshold, "SleepLinearSpeedThreshold", (Fixed64)0.001f);
 9670        RecordValues.Look(chronicler, ref sleepAngularSpeedThreshold, "SleepAngularSpeedThreshold", (Fixed64)0.001f);
 9671        RecordValues.Look(chronicler, ref groundingMode, "GroundingMode", GroundingMode.Automatic);
 9672        RecordValues.Look(chronicler, ref groundProbeMode, "GroundProbeMode", GroundProbeMode2D.Auto);
 9673        RecordValues.Look(chronicler, ref useGravityDerivedGroundUpDirection, "UseGravityDerivedGroundUpDirection", true
 9674        RecordValues.Look(chronicler, ref groundUpDirection, "GroundUpDirection", Vector2d.Forward);
 9675        RecordValues.Look(chronicler, ref groundProbeRadius, "GroundProbeRadius");
 9676        RecordValues.Look(chronicler, ref groundedDistanceRay, "GroundedDistanceRay", Fixed64.Half);
 9677        RecordValues.Look(chronicler, ref groundDownDistanceOnAir, "GroundDownDistanceOnAir", Fixed64.Half);
 9678        RecordValues.Look(chronicler, ref groundMinNormalDot, "GroundMinNormalDot", Fixed64.Half);
 9679        RecordValues.Look(chronicler, ref _isGrounded, "IsGrounded");
 9680        RecordValues.Look(chronicler, ref _wasGrounded, "WasGrounded");
 9681        RecordValues.Look(chronicler, ref _groundNormal, "GroundNormal");
 9682        RecordValues.Look(chronicler, ref _hasGroundPoint, "HasGroundPoint");
 9683        RecordValues.Look(chronicler, ref _groundPoint, "GroundPoint");
 9684        RecordValues.Look(chronicler, ref _lastGroundedPosition, "LastGroundedPosition");
 9685        RecordValues.Look(chronicler, ref continuousCollisionMode, "ContinuousCollisionMode", ContinuousCollisionMode.In
 86
 9687        if (chronicler.Mode == SerializationMode.Loading)
 88        {
 5689            ApplyLoadedMotionType(motionType);
 5690            DiscardContinuousCollisionHandoff();
 5691            InvalidateContinuousCollisionFrame();
 5692            ContinuousCollisionMode = continuousCollisionMode;
 5493            _rotation = CanonicalizeRotation(_rotation);
 5494            _freezeAxes = freezeAxes;
 5495            Mass = mass;
 5496            Gravity = gravity;
 5497            GravityScale = gravityScale;
 5498            SleepEnabled = sleepEnabled;
 5499            SleepFrameThreshold = sleepFrameThreshold;
 54100            SleepLinearSpeedThreshold = sleepLinearSpeedThreshold;
 54101            SleepAngularSpeedThreshold = sleepAngularSpeedThreshold;
 54102            _groundingMode = groundingMode;
 54103            _groundProbeMode = groundProbeMode;
 54104            _useGravityDerivedGroundUpDirection = useGravityDerivedGroundUpDirection;
 54105            _groundUpDirection = groundUpDirection.MagnitudeSquared > Fixed64.Epsilon
 54106                ? groundUpDirection.Normalized
 54107                : DefaultGroundUpDirection;
 54108            _groundProbeRadius = groundProbeRadius < Fixed64.Zero ? Fixed64.Zero : groundProbeRadius;
 54109            GroundedDistanceRay = groundedDistanceRay;
 54110            GroundDownDistanceOnAir = groundDownDistanceOnAir;
 54111            GroundMinNormalDot = groundMinNormalDot;
 54112            _groundNormal = _groundNormal.MagnitudeSquared > Fixed64.Epsilon
 54113                ? _groundNormal.Normalized
 54114                : Vector2d.Zero;
 54115            _groundedTransitionCapturedForStep = false;
 54116            _groundCollider = null;
 54117            _groundColliderBroadPhaseVersion = 0;
 54118            ClearGroundContactCandidate();
 119        }
 120
 94121        Collider.RecordData(chronicler);
 122
 94123        if (chronicler.Mode == SerializationMode.Loading)
 124        {
 54125            RefreshMassPropertiesFromColliderShape();
 54126            ApplyLoadedFreezeConstraintsToMotion();
 54127            ApplyLoadedState();
 54128            if (!active || Collider.Id < 0)
 8129                Deactivate();
 54130            _groundingStateVersion++;
 131        }
 94132    }
 133
 134    private void ApplyLoadedState()
 135    {
 54136        SetHostWorldPose(Agent.Transform, _position, _rotation);
 54137        Collider.Rebuild();
 54138    }
 139
 140    private void ApplyLoadedFreezeConstraintsToMotion()
 141    {
 54142        _linearVelocity = ProjectLinearMotion(_linearVelocity);
 54143        _linearAccelerationStore = ProjectLinearMotion(_linearAccelerationStore);
 54144        _deltaAcceleration = ProjectLinearMotion(_deltaAcceleration);
 54145        RefreshLinearSpeed();
 146
 54147        if (IsRotationFullyFrozen)
 148        {
 2149            _angularVelocity = Fixed64.Zero;
 2150            _angularAccelerationStore = Fixed64.Zero;
 2151            _deltaAngularAcceleration = Fixed64.Zero;
 2152            _angularSpeed = Fixed64.Zero;
 2153            return;
 154        }
 155
 52156        RefreshAngularSpeed();
 52157    }
 158}

Methods/Properties

get_LocalCenterOfMassOffset()
set_LocalCenterOfMassOffset(FixedMathSharp.Vector2d)
get_WorldCenterOfMass()
TryGetWorldCenterOfMass(FixedMathSharp.Vector2d&)
TryGetOffsetFromCenterOfMass(Gravitas.CollisionHandling.ContactAnchor2D,FixedMathSharp.Vector2d&)
GetCenterOfMassAnchor()
ResetCenterOfMassFromCollider()
get_ContinuousCollisionFrameStart()
get_ContinuousCollisionFrameDisplacement()
get_ContinuousCollisionFrameRotation()
get_ContinuousCollisionFrameEnd()
get_ContinuousCollisionFrameTargetRotation()
get_ContinuousCollisionTrajectoryCount()
GetContinuousCollisionTrajectorySegment(System.Int32)
get_HasContinuousCollisionMotion()
ShouldOwnContinuousCollisionMovingPair(System.Boolean)
get_HasContinuousCollisionRotationalMotion()
SampleContinuousCollisionPosition(FixedMathSharp.Fixed64)
SampleContinuousCollisionRotation(FixedMathSharp.Fixed64)
SampleContinuousCollisionLinearVelocity(FixedMathSharp.Fixed64)
SampleContinuousCollisionAngularVelocity(FixedMathSharp.Fixed64)
ResolveLegacyContinuousCollisionFrameStart()
ResolveLegacyContinuousCollisionFrameDisplacement()
EnsureContinuousCollisionFramePrepared(System.Int32)
PrepareMovableContinuousCollisionMotion()
SetContinuousCollisionFrameSegment(Gravitas.CollisionHandling.ContinuousCollisionMotionSegment2D)
AppendContinuousCollisionFrameSegment(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64)
AppendContinuousCollisionFrameSegment(FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
CanAppendContinuousCollisionFrameSegment(FixedMathSharp.Fixed64)
TryResolveContinuousCollisionMotionBound(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
ResolveContinuousCollisionTrajectoryBounds(FixedMathSharp.Fixed64)
RemoveSupersededContinuousCollisionFrameSegments(FixedMathSharp.Fixed64,FixedMathSharp.Vector2d)
ResolveContinuousCollisionSegment(FixedMathSharp.Fixed64)
GetContinuousCollisionTrajectorySegmentIndex(FixedMathSharp.Fixed64)
GetContinuousCollisionTrajectorySegmentRange(FixedMathSharp.Fixed64,System.Int32&,System.Int32&)
InvalidateContinuousCollisionFrame()
ApplyContinuousCollisionHandoff(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,Gravitas.Colliders.LSCollider,Gravitas.Colliders.LSCollider2D)
ApplyContinuousCollisionHandoff(FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.Colliders.LSCollider,Gravitas.Colliders.LSCollider2D)
ApplyContinuousCollisionHandoffState(FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.Colliders.LSCollider,Gravitas.Colliders.LSCollider2D)
ApplyContinuousCollisionHandoffStateReserved(FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.Colliders.LSCollider,Gravitas.Colliders.LSCollider2D)
CanApplyContinuousCollisionHandoffState(FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d&)
TryConsumeContinuousCollisionHandoff(System.Boolean,System.Boolean)
DiscardContinuousCollisionHandoff()
TryResolveContinuousCollision(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d&)
TryResolveContinuousCollision(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d&,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Boolean)
TryApplyContinuousCollisionDynamicResponse(FixedMathSharp.Vector2d,Gravitas.CollisionHandling.ContinuousCollisionTargetKind,Gravitas.Colliders.LSCollider2D,Gravitas.Colliders.LSCollider,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
TryApplyContinuousCollisionDynamicResponse(Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
TryApplyContinuousCollisionMixed3DResponse(Gravitas.SolidBody,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
ApplyContinuousCollisionSourceResponse(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64)
UpdateContinuousCollisionFrameTrajectory(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64)
ResolveContinuousCollisionFrameFraction(FixedMathSharp.Fixed64)
ResolveContinuousCollisionRestitution(Gravitas.SolidBody2D,FixedMathSharp.Fixed64)
ResolveContinuousCollisionRestitution(Gravitas.SolidBody,FixedMathSharp.Fixed64)
RemoveClosingContinuousCollisionVelocity(FixedMathSharp.Vector2d)
ShouldUseContinuousCollision(Gravitas.ContinuousCollisionMode&)
ResolveContinuousCollisionMode()
ResolveContinuousCollisionProxyRadius()
ResolveMixedContinuousCollisionProxyRadius()
IsValidContinuousCollisionHit(Gravitas.Queries.Physics2DHit)
IsClosingContinuousCollisionHit(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d)
IsValidContinuousCollisionTarget(Gravitas.Colliders.LSCollider2D)
IsValidMixedContinuousCollisionHit(Gravitas.Colliders.LSCollider)
TryGetFirstContinuousCollisionHit(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d&,FixedMathSharp.Fixed64&,Gravitas.CollisionHandling.ContinuousCollisionTargetKind&,Gravitas.Colliders.LSCollider2D&,Gravitas.Colliders.LSCollider&)
TryGetFirstValidContinuousCollisionHit(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,System.Int32,Gravitas.Queries.Physics2DHit&)
TryGetFirstValidMixedContinuousCollisionHit(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,System.Int32,Gravitas.Queries.PhysicsMixedHit&)
TryGetFirstDynamicContinuousCollisionHit(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.Queries.Physics2DHit&,FixedMathSharp.Fixed64&)
TryGetDynamicRelativeContinuousCollisionHit(Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.Queries.Physics2DHit&,FixedMathSharp.Fixed64&)
TranslateContactAnchor(Gravitas.CollisionHandling.ContactAnchor2D,FixedMathSharp.Geometry.FixedSegment2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
TryGetFirstDynamicMixedContinuousCollisionHit(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,Gravitas.DynamicMixedIntervalHit&)
TryGetDynamicMixed3DContinuousCollisionHit(Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.DynamicMixedIntervalHit&)
IsEligibleDynamicContinuousCollisionTarget(Gravitas.SolidBody2D)
IsEligibleDynamicMixed3DTarget(Gravitas.SolidBody)
TryResolveKinematicContinuousCollision(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d&)
TryGetFirstKinematicStaticContinuousCollisionHit(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
TryApplyKinematicDynamicContinuousCollisionPushes(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Boolean&,FixedMathSharp.Fixed64&)
GatherKinematicDynamic2DContinuousCollisionHits(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
GatherKinematicDynamicMixed3DContinuousCollisionHits(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Boolean&,FixedMathSharp.Fixed64&)
ApplyKinematicContinuousCollisionHandoff(Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
ApplyKinematicContinuousCollisionHandoff(Gravitas.SolidBody,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
HasNearbyRotationalContinuousCollisionTarget(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64)
IsRotatingContinuousCollisionTarget(Gravitas.SolidBody2D)
ShouldUseRotationalContinuousCollisionArbiter(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Boolean)
TryResolveRotationalContinuousCollision(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d&,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
TryResolveRotationalContinuousCollision(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d&,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Boolean)
TryResolveKinematicRotationalContinuousCollision(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d&,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
GatherRotationalContinuousCollisionCandidates(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64)
GatherAllRegisteredRotationalContinuousCollisionCandidates()
TryFindEarliestRotationalContinuousCollision(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&,Gravitas.CollisionHandling.Contact2D&,System.Boolean&,FixedMathSharp.Fixed64&,Gravitas.Colliders.LSCollider2D&)
TryFindEarliestRotationalContinuousCollisionAgainstTarget(Gravitas.Colliders.LSCollider2D,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&,Gravitas.CollisionHandling.Contact2D&,System.Boolean&,FixedMathSharp.Fixed64&)
IsRotationalIntervalSeparated(Gravitas.Colliders.LSCollider2D,FixedMathSharp.Fixed64)
TryGetCirclePairSeparationGap(Gravitas.Colliders.LSCollider2D,FixedMathSharp.Fixed64&)
TryGetCircleSeparationGap(Gravitas.Colliders.LSCircleCollider2D,Gravitas.Colliders.LSCollider2D,FixedMathSharp.Fixed64&)
SampleRotationalContinuousPairPose(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.SolidBody2D,FixedMathSharp.Fixed64,System.Boolean)
IsMovingRotationalContinuousCollisionTarget(Gravitas.SolidBody2D)
.ctor(Gravitas.IMatterAgent,Gravitas.Colliders.LSCollider2D)
SetMixedContinuousCollisionSamplePose(FixedMathSharp.Vector2d,FixedMathSharp.Fixed64)
ResolveMixedContinuousCollisionTrajectoryBounds(FixedMathSharp.Fixed64)
.ctor(FixedMathSharp.Fixed64,Gravitas.CollisionHandling.MixedContact,System.Boolean,FixedMathSharp.Fixed64,Gravitas.Colliders.LSCollider)
TryFindEarliestMixedRotationalContinuousCollision(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.SolidBody2D/RotationalMixed3DHit&)
GatherMixedRotationalContinuousCollisionCandidates(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64)
ConsiderMixedRotationalContinuousCollisionCandidate(Gravitas.Colliders.LSCollider,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Boolean&,FixedMathSharp.Fixed64&,System.Int32&,Gravitas.CollisionHandling.MixedContact&,System.Boolean&,FixedMathSharp.Fixed64&,Gravitas.Colliders.LSCollider&)
TryFindEarliestMixedRotationalContinuousCollisionAgainstTarget(Gravitas.Colliders.LSCollider,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&,Gravitas.CollisionHandling.MixedContact&,System.Boolean&,FixedMathSharp.Fixed64&)
SampleMixedRotationalContinuousPairPose(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.SolidBody,System.Boolean)
TryGetExactSphereCircleTranslationalContact(Gravitas.SolidBody,Gravitas.CollisionHandling.ContinuousCollisionMotionSegment3D,FixedMathSharp.Geometry.FixedSegment,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Geometry.FixedSegment2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Boolean&,FixedMathSharp.Fixed64&,FixedMathSharp.Fixed64&,FixedMathSharp.Fixed64&,Gravitas.CollisionHandling.MixedContact&)
IsValidMixedRotationalContinuousCollisionTarget(Gravitas.Colliders.LSCollider)
IsMovingMixedRotationalContinuousCollisionTarget(Gravitas.SolidBody)
TryApplyMixedRotationalContinuousCollisionResponse(Gravitas.Colliders.LSCollider,Gravitas.CollisionHandling.MixedContact,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
TryApplyRotationalContinuousCollisionResponse(Gravitas.SolidBody2D,Gravitas.CollisionHandling.Contact2D,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
ResolveRotationalFrameFraction(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
StopRotationalContinuousCollision(FixedMathSharp.Vector2d)
.ctor(Gravitas.IMatterAgent,Gravitas.Colliders.LSCollider2D)
get_FreezeAxes()
set_FreezeAxes(Gravitas.BodyFreezeAxes2D)
get_IsPositionFullyFrozen()
get_MotionType()
get_IsDynamic()
get_IsStatic()
get_IsKinematic()
SetMotionType(Gravitas.BodyMotionType)
PrepareMotionTypeTransition(Gravitas.BodyMotionType)
CommitMotionTypeTransition(Gravitas.BodyMotionType)
ApplyLoadedMotionType(Gravitas.BodyMotionType)
PreflightLoadedMotionType(Gravitas.BodyMotionType)
ThrowIfRuntimeRegistrationMissing()
ReconcileMotionTypeRegistration(Gravitas.BodyMotionType)
ClearMotionForMotionTypeTransition()
get_ContinuousCollisionMode()
set_ContinuousCollisionMode(Gravitas.ContinuousCollisionMode)
get_IsRotationFullyFrozen()
get_Mass()
set_Mass(FixedMathSharp.Fixed64)
get_InverseMass()
get_CanTranslate()
get_CanRotate()
get_HasSolverMobility()
get_EffectiveInverseMass()
get_EffectiveInverseMomentOfInertia()
ProjectLinearMotion(FixedMathSharp.Vector2d)
ProjectLinearEndpoint(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d)
GetConstrainedInverseMass(FixedMathSharp.Vector2d)
get_MomentOfInertia()
get_InverseMomentOfInertia()
get_AngularVelocity()
get_AngularAcceleration()
get_AngularSpeed()
get_Position()
get_Rotation()
get_LinearVelocity()
get_LinearSpeed()
get_GravityScale()
set_GravityScale(FixedMathSharp.Fixed64)
get_SleepEnabled()
set_SleepEnabled(System.Boolean)
get_SleepFrameThreshold()
set_SleepFrameThreshold(System.Int32)
get_SleepLinearSpeedThreshold()
set_SleepLinearSpeedThreshold(FixedMathSharp.Fixed64)
get_SleepAngularSpeedThreshold()
set_SleepAngularSpeedThreshold(FixedMathSharp.Fixed64)
get_IsSleeping()
get_IsAwakeForCollision()
Initialize(FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,Gravitas.BodyMotionType)
GetWorldPoint(FixedMathSharp.Vector2d)
TryGetWorldPoint(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d&)
GetLocalPoint(FixedMathSharp.Vector2d)
TryGetLocalPoint(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d&)
ResetPosition(FixedMathSharp.Vector2d,FixedMathSharp.Fixed64)
Sleep()
Wake()
WakeFromCollision()
LateSimulate()
LateSimulate(System.Boolean,System.Boolean)
UpdateSleepStateAfterPhysicsStep()
OnVisualize()
UpdateKinematicPositionAndRotation(System.Boolean)
CanonicalizeRotation(FixedMathSharp.Fixed64)
SetHostWorldPose(FixedMathSharp.FixedTransform,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64)
PublishAuthoritativePose()
Deactivate()
.cctor()
.ctor(Gravitas.IMatterAgent,Gravitas.Colliders.LSCollider2D)
get_GroundingMode()
get_GroundProbeMode()
set_GroundProbeMode(Gravitas.GroundProbeMode2D)
get_UseGravityDerivedGroundUpDirection()
set_UseGravityDerivedGroundUpDirection(System.Boolean)
get_GroundUpDirection()
set_GroundUpDirection(FixedMathSharp.Vector2d)
get_GroundProbeRadius()
set_GroundProbeRadius(FixedMathSharp.Fixed64)
get_IsGrounded()
set_IsGrounded(System.Boolean)
get_WasGrounded()
get_GroundNormal()
get_HasGroundPoint()
get_GroundPoint()
TryGetGroundPoint(FixedMathSharp.Vector2d&)
get_LastGroundedPosition()
UseManualGrounding(System.Boolean)
UseAutomaticGrounding(System.Boolean)
SetManualGrounding(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d)
ClearManualGrounding()
CheckGround()
BeginAutomaticGroundingRefresh()
TryAcceptContactGroundCandidate(Gravitas.Colliders.LSCollider2D,Gravitas.Colliders.LSCollider2D,Gravitas.CollisionHandling.ManifoldContact2D,System.Boolean)
CompleteAutomaticGroundingRefresh()
CheckGroundForSimulation()
RemoveIntoGroundComponent(FixedMathSharp.Vector2d)
get_CanUseAutomaticGrounding()
CheckGround(System.Boolean)
TryFindGroundHit(Gravitas.GroundProbeMode2D,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,Gravitas.Queries.Physics2DHit&)
TryFindGroundHitWithRay(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,Gravitas.Queries.Physics2DHit&)
TryFindGroundHitWithSweptCircle(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,Gravitas.Queries.Physics2DHit&)
IsValidGroundHit(Gravitas.Queries.Physics2DHit)
IsValidGroundCollider(Gravitas.Colliders.LSCollider2D)
ResolveGroundProbeMode()
ResolveGroundProbeRadius()
ResolveGroundUpDirection()
CaptureGroundedTransitionState()
CaptureGroundedStepState()
CompleteGroundedStepState()
ClearGrounding()
ClearGroundingForAutomaticRefresh()
SetGroundingState(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,Gravitas.Colliders.LSCollider2D,System.Boolean)
ResetGroundingForInitialize(FixedMathSharp.Vector2d)
RevalidateAutomaticGroundingWrite(System.Int64,Gravitas.Colliders.ColliderLifetimeToken2D&,Gravitas.Colliders.ColliderLifetimeToken2D&)
ClearGroundContactCandidate()
CompareGroundContactCandidate(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Int32,System.UInt64)
AddForce(FixedMathSharp.Vector2d)
AddTorque(FixedMathSharp.Fixed64)
AddLinearImpulse(FixedMathSharp.Vector2d)
AddAngularImpulse(FixedMathSharp.Fixed64)
SetPosition(FixedMathSharp.Vector2d)
SetRotation(FixedMathSharp.Fixed64)
RefreshStaticColliderAfterExplicitPoseChange()
PreflightStaticPoseChange()
RefreshPartitionMobility()
ApplyCollisionLinearVelocityDelta(FixedMathSharp.Vector2d)
ApplyCollisionAngularVelocityDelta(FixedMathSharp.Fixed64)
CanApplyCollisionVelocityDeltas(FixedMathSharp.Vector2d,FixedMathSharp.Fixed64)
ApplyCollisionPositionCorrection(FixedMathSharp.Vector2d)
get_CanSleep()
ApplyFreezeConstraintsToMotion()
UpdateSleepState()
RefreshLinearSpeed()
RefreshAngularSpeed()
RefreshMassPropertiesFromColliderShape()
ContributeReplayHash(Chronicler.ChronicleHashWriter&,Gravitas.GravitasReplayHashMode)
WriteContinuousCollisionTrajectory(Chronicler.ChronicleHashWriter&)
RecordData(Chronicler.IChronicler)
ApplyLoadedState()
ApplyLoadedFreezeConstraintsToMotion()