< Summary

Information
Class: Gravitas.SolidBody
Assembly: Gravitas
File(s): File 1: /home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.CenterOfMass.cs
File 2: /home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.ContinuousCollision.cs
File 3: /home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.ContinuousCollision.Dynamic.cs
File 4: /home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.ContinuousCollision.Helpers.cs
File 5: /home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.ContinuousCollision.Hits.cs
File 6: /home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.ContinuousCollision.Kinematic.cs
File 7: /home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.ContinuousCollision.Rotational.cs
File 8: /home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.ContinuousCollision.Rotational.Mixed.cs
File 9: /home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.ContinuousCollision.Rotational.Response.cs
File 10: /home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.ContinuousCollision.Rotational.Search.cs
File 11: /home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.cs
File 12: /home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.Grounding.cs
File 13: /home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.Motion.cs
File 14: /home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.ReplayHash.cs
File 15: /home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.Serialization.cs
File 16: /home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.Visualization.cs
Line coverage
100%
Covered lines: 4715
Uncovered lines: 0
Coverable lines: 4715
Total lines: 7799
Line coverage: 100%
Branch coverage
100%
Covered branches: 1432
Total branches: 1432
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%88100%
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%1212100%
File 2: InvalidateContinuousCollisionTrajectory()100%11100%
File 2: SetSingleContinuousCollisionSegment(...)100%11100%
File 2: AppendContinuousCollisionSegment(...)100%11100%
File 2: AppendContinuousCollisionSegment(...)100%44100%
File 2: CanAppendContinuousCollisionSegment(...)100%44100%
File 2: TryResolveContinuousCollisionMotionBound(...)100%88100%
File 2: ResolveContinuousCollisionTrajectoryBounds(...)100%22100%
File 2: RemoveSupersededContinuousCollisionSegments(...)100%88100%
File 2: ResolveContinuousCollisionSegment(...)100%11100%
File 2: GetContinuousCollisionTrajectorySegmentIndex(...)100%44100%
File 2: GetContinuousCollisionTrajectorySegmentRange(...)100%22100%
File 3: ApplyContinuousCollisionHandoff(...)100%22100%
File 3: ApplyContinuousCollisionHandoff(...)100%22100%
File 3: ApplyContinuousCollisionHandoffReserved(...)100%1010100%
File 3: CanApplyContinuousCollisionHandoff(...)100%22100%
File 3: TryConsumeContinuousCollisionHandoff(...)100%11100%
File 3: TryConsumeQueuedContinuousCollisionHandoff(...)100%11100%
File 3: TryConsumeContinuousCollisionHandoff(...)100%2424100%
File 3: NotifyAuthoritativeMovement()100%22100%
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: TryApplyContinuousCollisionMixed2DResponse(...)100%1616100%
File 3: UpdateContinuousCollisionFrameTrajectory(...)100%11100%
File 3: ResolveContinuousCollisionFrameFraction(...)100%11100%
File 3: ResolveContinuousCollisionRestitution(...)100%22100%
File 3: ResolveContinuousCollisionRestitution(...)100%22100%
File 3: RemoveClosingContinuousCollisionVelocity(...)100%22100%
File 4: IsEligibleDynamicContinuousCollisionTarget(...)100%88100%
File 4: IsEligibleDynamicMixed2DTarget(...)100%88100%
File 4: ShouldUseContinuousCollision(...)100%22100%
File 4: ResolveContinuousCollisionMode()100%1010100%
File 4: ResolveContinuousCollisionProxyRadius()100%22100%
File 4: IsClosingContinuousCollisionHit(...)100%11100%
File 4: IsValidContinuousCollisionTarget(...)100%88100%
File 4: IsValidMixedContinuousCollisionHit(...)100%88100%
File 5: TryGetFirstContinuousCollisionHit(...)100%2222100%
File 5: TryGetFirstValidContinuousCollisionHit(...)100%1616100%
File 5: QueryStaticContinuousCollisionHits(...)100%44100%
File 5: TryGetFirstValidMixedContinuousCollisionHit(...)100%66100%
File 5: TryRefineShapeExactContinuousCollisionHit(...)100%44100%
File 5: TryRefineContinuousCollisionAgainstTargetSphere(...)100%22100%
File 5: ApplyShapeExactContinuousContactSlop(...)100%22100%
File 5: PrepareExactConvexSourceSweep(...)100%44100%
File 5: IsExactConvexSourceSupported(...)100%1414100%
File 5: ContinuousCollisionHitComesBefore(...)100%22100%
File 5: TryGetFirstDynamicContinuousCollisionHit(...)100%88100%
File 5: TryGetDynamicRelativeContinuousCollisionHit(...)100%4848100%
File 5: TrySweepRelativeSourceSphere(...)100%22100%
File 5: TryGetFirstDynamicMixedContinuousCollisionHit(...)100%88100%
File 5: TryGetDynamicMixed2DContinuousCollisionHit(...)100%2828100%
File 6: TryResolveKinematicContinuousCollision(...)100%1818100%
File 6: TryGetFirstKinematicStaticContinuousCollisionHit(...)100%1010100%
File 6: TryApplyKinematicDynamicContinuousCollisionPushes(...)100%88100%
File 6: GatherKinematicDynamic3DContinuousCollisionHits(...)100%88100%
File 6: GatherKinematicDynamicMixed2DContinuousCollisionHits(...)100%1212100%
File 6: ApplyKinematicContinuousCollisionHandoff(...)100%44100%
File 6: ApplyKinematicContinuousCollisionHandoff(...)100%66100%
File 7: TryResolveRotationalContinuousCollision(...)100%11100%
File 7: TryResolveRotationalContinuousCollision(...)100%7070100%
File 7: TryResolveKinematicRotationalContinuousCollision(...)100%6868100%
File 7: TryFindEarliestRotationalContinuousCollisionAgainstTarget(...)100%3636100%
File 7: IsRotationalIntervalSeparated(...)100%22100%
File 7: TryGetSpherePairSeparationGap(...)100%44100%
File 7: TryGetSphereSeparationGap(...)100%1818100%
File 7: SampleDynamicRotationalContinuousPose(...)100%11100%
File 7: SampleKinematicRotationalContinuousPose(...)100%11100%
File 7: ResolveKinematicAngularDistanceRadians(...)100%11100%
File 7: TrySampleRotationalContinuousCollision(...)100%44100%
File 7: ResolveSourceContactNormal(...)100%22100%
File 7: IsMovingRotationalContinuousCollisionTarget(...)100%1616100%
File 7: ResolveRotationalFrameFraction(...)100%11100%
File 7: OrderRotationalContinuousCollisionPair(...)100%22100%
File 7: IntegrateAngularRotation(...)100%11100%
File 7: IntegrateAngularRotation(...)100%11100%
File 8: .ctor(...)100%11100%
File 8: SetMixedContinuousCollisionSamplePose(...)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%44100%
File 8: TryGetExactSphereCircleTranslationalContact(...)100%2626100%
File 8: IsValidMixedRotationalContinuousCollisionTarget(...)100%66100%
File 8: IsMovingMixedRotationalContinuousCollisionTarget(...)100%1010100%
File 8: TryApplyMixedRotationalContinuousCollisionResponse(...)100%4848100%
File 9: TryApplyRotationalContinuousCollisionResponse(...)100%6464100%
File 9: TryCalculateExactRotationalVelocityDeltas(...)100%11100%
File 9: ResolveContinuousCollisionInverseInertia(...)100%22100%
File 9: StopRotationalContinuousCollision(...)100%11100%
File 10: HasNearbyRotationalContinuousCollisionTarget(...)100%1010100%
File 10: IsRotatingContinuousCollisionTarget(...)100%66100%
File 10: GatherRotationalContinuousCollisionCandidates(...)100%66100%
File 10: GatherAllRegisteredRotationalContinuousCollisionCandidates()100%88100%
File 10: TryFindEarliestRotationalContinuousCollision(...)100%2626100%
File 11: .ctor(...)100%22100%
File 11: get_DynamicId()100%11100%
File 11: get_FreezeAxes()100%11100%
File 11: set_FreezeAxes(...)100%44100%
File 11: get_IsPositionFullyFrozen()100%11100%
File 11: get_MotionType()100%11100%
File 11: get_IsDynamic()100%11100%
File 11: get_IsStatic()100%11100%
File 11: get_IsKinematic()100%11100%
File 11: SetMotionType(...)100%22100%
File 11: PrepareMotionTypeTransition(...)100%44100%
File 11: CommitMotionTypeTransition(...)100%11100%
File 11: ApplyLoadedMotionType(...)100%44100%
File 11: PreflightLoadedMotionType(...)100%22100%
File 11: ThrowIfRuntimeRegistrationMissing()100%22100%
File 11: ReconcileMotionTypeRegistration(...)100%11100%
File 11: SetDynamicId(...)100%11100%
File 11: get_ContinuousCollisionMode()100%11100%
File 11: set_ContinuousCollisionMode(...)100%11100%
File 11: get_PositionTransform()100%11100%
File 11: get_RotationTransform()100%11100%
File 11: get_PositionChangePending()100%22100%
File 11: SetPosition2d(...)100%22100%
File 11: get_Position3d()100%11100%
File 11: set_Position3d(...)100%22100%
File 11: get_HeightPos()100%11100%
File 11: set_HeightPos(...)100%22100%
File 11: .cctor()100%11100%
File 11: get_VisualPosition()100%11100%
File 11: get_LastVisualPosition()100%11100%
File 11: get_RotationChangePending()100%22100%
File 11: get_Rotation()100%11100%
File 11: set_Rotation(...)100%22100%
File 11: get_Forward()100%11100%
File 11: get_Up()100%11100%
File 11: get_Right()100%11100%
File 11: get_VisualRotation()100%11100%
File 11: get_LastVisualRotation()100%11100%
File 11: get_IsRotationFullyFrozen()100%11100%
File 11: get_SettingVisuals()100%11100%
File 11: get_LinearVelocity()100%11100%
File 11: get_AngularVelocity()100%11100%
File 11: get_InverseInertiaTensor()100%11100%
File 11: get_CanTranslate()100%88100%
File 11: get_CanRotate()100%88100%
File 11: get_HasSolverMobility()100%22100%
File 11: get_EffectiveInverseMass()100%22100%
File 11: get_EffectiveInverseInertiaTensor()100%22100%
File 11: ProjectLinearMotion(...)100%1010100%
File 11: ProjectLinearEndpoint(...)100%66100%
File 11: ProjectAngularMotion(...)100%1010100%
File 11: GetConstrainedInverseMass(...)100%88100%
File 11: ApplyConstrainedInverseInertia(...)100%44100%
File 11: GetConstrainedInverseInertiaTensor()100%11100%
File 11: get_GravityScale()100%11100%
File 11: set_GravityScale(...)100%11100%
File 11: get_IsAtRest()100%22100%
File 11: get_IsSleeping()100%11100%
File 11: get_SleepEnabled()100%11100%
File 11: set_SleepEnabled(...)100%44100%
File 11: get_SleepFrameThreshold()100%11100%
File 11: set_SleepFrameThreshold(...)100%11100%
File 11: get_SleepLinearSpeedThreshold()100%11100%
File 11: set_SleepLinearSpeedThreshold(...)100%11100%
File 11: get_SleepAngularSpeedThreshold()100%11100%
File 11: set_SleepAngularSpeedThreshold(...)100%11100%
File 11: get_IsAwakeForCollision()100%22100%
File 11: get_LinearSpeed()100%11100%
File 11: get_LinearAcceleration()100%11100%
File 11: get_AngularSpeed()100%11100%
File 11: get_AngularAcceleration()100%11100%
File 11: get_InverseMass()100%22100%
File 11: get_Weight()100%11100%
File 11: get_World()100%11100%
File 11: Initialize(...)100%66100%
File 11: LateSimulate()100%11100%
File 11: LateSimulate(...)100%2222100%
File 11: UpdateSleepStateAfterPhysicsStep()100%22100%
File 11: UpdateKinematicPositionAndRotation()100%1212100%
File 11: Sleep()100%44100%
File 11: Wake()100%22100%
File 11: WakeFromCollision()100%22100%
File 11: get_CanSleep()100%22100%
File 11: SetTransformWorldPose(...)100%22100%
File 11: Deactivate()100%22100%
File 11: UpdateRotation(...)100%22100%
File 11: GetWorldPoint(...)100%11100%
File 11: TryGetWorldPoint(...)100%22100%
File 11: GetLocalPoint(...)100%11100%
File 11: TryGetLocalPoint(...)100%22100%
File 11: ResetPosition(...)100%22100%
File 12: .ctor(...)100%11100%
File 12: get_GroundNormal()100%11100%
File 12: get_HitPlatform()100%11100%
File 12: get_HasHitPoint()100%11100%
File 12: get_HitPoint()100%44100%
File 12: TryGetHitPoint(...)100%11100%
File 12: get_IsGrounded()100%11100%
File 12: set_IsGrounded(...)100%44100%
File 12: get_WasGrounded()100%11100%
File 12: get_LastGroundedPosition()100%11100%
File 12: SkipGrounding(...)100%11100%
File 12: UseManualGrounding(...)100%22100%
File 12: UseAutomaticGrounding(...)100%44100%
File 12: SetManualGrounding(...)100%11100%
File 12: ClearManualGrounding()100%11100%
File 12: SkipGroundingCoroutine()100%11100%
File 12: CheckGround()100%11100%
File 12: CheckGroundForSimulation()100%11100%
File 12: CaptureGroundedTransitionState()100%11100%
File 12: CaptureGroundedStepState()100%22100%
File 12: CompleteGroundedStepState()100%11100%
File 12: CheckGround(...)100%2222100%
File 12: TryFindGroundHit(...)100%66100%
File 12: TryFindGroundHitWithRay(...)100%44100%
File 12: TryFindGroundHitWithSweptSphere(...)100%66100%
File 12: IsValidGroundHit(...)100%66100%
File 12: ResolveGroundProbeMode()100%1616100%
File 12: ResolveGroundProbeRadius()100%1212100%
File 12: ClearGrounding()100%11100%
File 12: ApplyGroundedHeightOrReset()100%44100%
File 12: SetGroundingState(...)100%22100%
File 12: SetGroundingState(...)100%11100%
File 12: RefreshGroundNormalForce()100%44100%
File 12: ResetGroundCalculations()100%11100%
File 13: get_CanUseAngularInertia()100%22100%
File 13: UpdateSleepState()100%1010100%
File 13: ClearMotionForSleep()100%11100%
File 13: ApplyFreezeConstraintsToMotion()100%11100%
File 13: RefreshPartitionAwakeState()100%22100%
File 13: RefreshPartitionMobility()100%66100%
File 13: AddTorque(...)100%22100%
File 13: AddForce(...)100%22100%
File 13: AddLinearImpulse(...)100%22100%
File 13: AddAngularImpulse(...)100%22100%
File 13: ApplyCollisionLinearVelocityDelta(...)100%44100%
File 13: ApplyCollisionAngularVelocityDelta(...)100%44100%
File 13: CanApplyCollisionVelocityDeltas(...)100%11100%
File 13: TryPrepareCollisionVelocityState(...)100%44100%
File 13: ApplyCollisionVelocityState(...)100%88100%
File 13: ApplyCollisionPositionCorrection(...)100%44100%
File 13: SetPosition(...)100%22100%
File 13: SetHeight(...)100%22100%
File 13: SetRotation(...)100%22100%
File 13: PrepareExplicitBodyPose(...)100%22100%
File 13: PublishExplicitBodyPose(...)100%22100%
File 13: PreflightStaticPoseChange()100%44100%
File 13: PreflightResetPoseChange()100%44100%
File 13: ProcessMovable()100%11100%
File 13: ApplyLinearForces()100%22100%
File 13: ApplyDragForce()100%11100%
File 13: ApplyFrictionForce()100%66100%
File 13: UpdateLinearVelocity()100%44100%
File 13: RefreshLinearMotionState(...)100%88100%
File 13: ApplyAngularTorques()100%11100%
File 13: ResolveAngularAccelerationForStep()100%66100%
File 13: UpdateAngularVelocity()100%11100%
File 13: ResolveAngularVelocityForStep(...)100%44100%
File 13: RefreshAngularMotionState(...)100%66100%
File 13: ResolveGroundDynamicFriction()100%11100%
File 13: NonKinematicUpdate(...)100%1010100%
File 13: PositionBasedOnForce()100%11100%
File 13: ApplyResolvedPosition(...)100%66100%
File 13: RotationBasedOnTorque(...)100%11100%
File 13: RefreshMassPropertiesFromColliderShape()100%22100%
File 13: UpdateInertiaTensorOrientation()100%44100%
File 13: RefreshInertiaTensor()100%22100%
File 13: ApplyGyroscopicPrecession(...)100%44100%
File 14: ContributeReplayHash(...)100%88100%
File 14: WriteContinuousCollisionTrajectory(...)100%22100%
File 15: RecordData(...)100%1010100%
File 15: ApplyLoadedState()100%44100%
File 16: OnVisualize()100%1616100%
File 16: ResolveVisualRotationStep()100%22100%
File 16: SetPositionTransformWorldPosition(...)100%11100%
File 16: SetRotationTransformWorldRotation(...)100%11100%
File 16: PublishAuthoritativePose()100%11100%
File 16: CheckChangedValues()100%44100%
File 16: SetVisualPosition(...)100%11100%
File 16: SetVisualRotation(...)100%11100%
File 16: StoreVisualRotation(...)100%11100%

File(s)

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.CenterOfMass.cs

#LineLine coverage
 1//=======================================================================
 2// SolidBody.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;
 12
 13namespace Gravitas;
 14
 15public partial class SolidBody
 16{
 17    private Vector3d _localCenterOfMassOffset;
 18    private bool _centerOfMassOffsetExplicit;
 19
 20    /// <summary>
 21    /// Gets or sets the authoritative body-local center-of-mass offset used by response and inertia.
 22    /// </summary>
 23    public Vector3d LocalCenterOfMassOffset
 24    {
 376625        get => _localCenterOfMassOffset;
 26        set
 27        {
 2528            if (_localCenterOfMassOffset == value && _centerOfMassOffsetExplicit)
 129                return;
 30
 2431            _localCenterOfMassOffset = value;
 2432            _centerOfMassOffsetExplicit = true;
 2433            if (!Active)
 134                return;
 35
 2336            Wake();
 2337            RefreshInertiaTensor();
 2338        }
 39    }
 40
 41    /// <summary>
 42    /// Gets the authoritative world-space center of mass.
 43    /// </summary>
 44    /// <exception cref="InvalidOperationException">
 45    /// The center lies outside the representable coordinate domain. Use
 46    /// <see cref="TryGetWorldCenterOfMass(out Vector3d)"/> when that is possible.
 47    /// </exception>
 48    public Vector3d WorldCenterOfMass
 49    {
 50        get
 51        {
 9552            bool succeeded = TryGetWorldCenterOfMass(out Vector3d center);
 9553            SwiftThrowHelper.ThrowIfTrue(
 9554                !succeeded,
 9555                nameof(WorldCenterOfMass),
 9556                "The world center of mass is outside the representable coordinate domain.");
 9457            return center;
 58        }
 59    }
 60
 61    /// <summary>
 62    /// Attempts to materialize the authoritative world-space center of mass
 63    /// without saturation.
 64    /// </summary>
 65    public bool TryGetWorldCenterOfMass(out Vector3d center)
 9866        => Rotation.TryTransformPoint(
 9867            Position3d,
 9868            _localCenterOfMassOffset,
 9869            out center);
 70
 71    internal bool TryGetOffsetFromCenterOfMass(
 72        ContactAnchor anchor,
 73        out Vector3d offset)
 9972774        => anchor.TryGetOffsetFrom(GetCenterOfMassAnchor(), out offset);
 75
 76    [System.Runtime.CompilerServices.MethodImpl(
 77        System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
 78    internal ContactAnchor GetCenterOfMassAnchor() =>
 11746279        new(
 11746280            Position3d,
 11746281            Rotation,
 11746282            _localCenterOfMassOffset);
 83
 84    /// <summary>
 85    /// Clears an explicit center-of-mass override and derives the offset from the bound collider again.
 86    /// </summary>
 87    public void ResetCenterOfMassFromCollider()
 88    {
 189        _centerOfMassOffsetExplicit = false;
 190        RefreshMassPropertiesFromColliderShape();
 191    }
 92}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.ContinuousCollision.cs

#LineLine coverage
 1//=======================================================================
 2// SolidBody.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 SwiftCollections.Query;
 12using System.Runtime.CompilerServices;
 13
 14namespace Gravitas;
 15
 16public partial class SolidBody
 17{
 18    private LSCollider? _continuousCollisionHandoffIgnoredCollider3D;
 19    private LSCollider2D? _continuousCollisionHandoffIgnoredCollider2D;
 20
 21    internal Vector3d ContinuousCollisionFrameStart
 22    {
 23        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1367424        get => ResolveLegacyContinuousCollisionFrameStart();
 25    }
 26
 27    internal Vector3d ContinuousCollisionFrameDisplacement
 28    {
 29        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 730        get => ResolveLegacyContinuousCollisionFrameDisplacement();
 31    }
 32
 33    internal FixedQuaternion ContinuousCollisionFrameRotation
 34    {
 35        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 736        get => _continuousCollisionTrajectory[0].StartRotation;
 37    }
 38
 39    internal Vector3d ContinuousCollisionFrameEnd
 40    {
 41        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 2001142        get => _continuousCollisionTrajectory[_continuousCollisionTrajectory.Count - 1].EndPosition;
 43    }
 44
 45    internal FixedQuaternion ContinuousCollisionFrameTargetRotation
 46    {
 47        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 638748        get => _continuousCollisionTrajectory[_continuousCollisionTrajectory.Count - 1].TargetRotation;
 49    }
 50
 51    internal int ContinuousCollisionTrajectoryCount =>
 32952        _continuousCollisionTrajectory.Count;
 53
 54    internal ContinuousCollisionMotionSegment3D GetContinuousCollisionTrajectorySegment(
 55        int index) =>
 19356        _continuousCollisionTrajectory[index];
 57
 58    internal bool HasContinuousCollisionMotion
 59    {
 60        get
 61        {
 1297862            for (int i = 0; i < _continuousCollisionTrajectory.Count; i++)
 63            {
 643864                ContinuousCollisionMotionSegment3D segment =
 643865                    _continuousCollisionTrajectory[i];
 643866                if (segment.Displacement.MagnitudeSquared > Fixed64.Epsilon
 643867                    || segment.AngularDistance > Fixed64.Epsilon)
 68                {
 638769                    return true;
 70                }
 71            }
 72
 5173            return false;
 74        }
 75    }
 76
 77    internal bool ShouldOwnContinuousCollisionMovingPair(bool otherHasRotationalMotion)
 78    {
 100979        if (!IsKinematic || !ShouldUseContinuousCollision(out ContinuousCollisionMode mode))
 86180            return false;
 81
 14882        EnsureContinuousCollisionFramePrepared(Context.LateSimulateToken);
 14883        if (!HasContinuousCollisionMotion)
 584            return false;
 14385        if (mode == ContinuousCollisionMode.Continuous)
 13886            return true;
 87
 588        Fixed64 proxyRadius = ResolveContinuousCollisionProxyRadius();
 589        Vector3d displacement = ContinuousCollisionFrameDisplacement;
 90
 591        Fixed64 angularDistance = ResolveKinematicAngularDistanceRadians(
 592            ContinuousCollisionFrameRotation,
 593            ContinuousCollisionFrameTargetRotation);
 594        bool requiresTargetSampling = angularDistance <= Fixed64.Epsilon
 595            && otherHasRotationalMotion;
 596        return requiresTargetSampling
 597            | angularDistance * proxyRadius > proxyRadius
 598            | !ContinuousCollisionMath.IsWithinProxyRadius(
 599                displacement,
 5100                displacement.MagnitudeSquared,
 5101                proxyRadius);
 102    }
 103
 104    internal bool HasContinuousCollisionRotationalMotion
 105    {
 106        get
 107        {
 5252108            for (int i = 0; i < _continuousCollisionTrajectory.Count; i++)
 109            {
 1588110                if (_continuousCollisionTrajectory[i].AngularDistance > Fixed64.Epsilon)
 79111                    return true;
 112            }
 113
 1038114            return false;
 115        }
 116    }
 117
 118    internal Vector3d SampleContinuousCollisionPosition(Fixed64 frameFraction) =>
 4163119        _continuousCollisionTrajectory.Count == 0
 4163120            ? Position3d
 4163121            : ResolveContinuousCollisionSegment(frameFraction).SamplePosition(frameFraction);
 122
 123    internal FixedQuaternion SampleContinuousCollisionRotation(Fixed64 frameFraction) =>
 4513124        _continuousCollisionTrajectory.Count == 0
 4513125            ? Rotation
 4513126            : ResolveContinuousCollisionSegment(frameFraction).SampleRotation(
 4513127                frameFraction,
 4513128                Context.DeltaTime);
 129
 130    internal Vector3d SampleContinuousCollisionLinearVelocity(Fixed64 frameFraction)
 131    {
 328132        if (_continuousCollisionTrajectory.Count == 0)
 2133            return ProjectLinearMotion(_linearVelocity);
 134
 326135        ContinuousCollisionMotionSegment3D segment =
 326136            ResolveContinuousCollisionSegment(frameFraction);
 326137        Fixed64 duration = (segment.EndFraction - segment.StartFraction) * Context.DeltaTime;
 326138        return ProjectLinearMotion(segment.Displacement / duration);
 139    }
 140
 141    internal Vector3d SampleContinuousCollisionAngularVelocity(Fixed64 frameFraction) =>
 304142        _continuousCollisionTrajectory.Count == 0
 304143            ? _angularVelocity
 304144            : ResolveContinuousCollisionSegment(frameFraction).AngularVelocity;
 145
 146    private Vector3d ResolveLegacyContinuousCollisionFrameStart()
 147    {
 13674148        ContinuousCollisionMotionSegment3D segment =
 13674149            _continuousCollisionTrajectory[_continuousCollisionTrajectory.Count - 1];
 13674150        Vector3d frameDisplacement = ResolveLegacyContinuousCollisionFrameDisplacement();
 13674151        return segment.StartPosition - frameDisplacement * segment.StartFraction;
 152    }
 153
 154    private Vector3d ResolveLegacyContinuousCollisionFrameDisplacement()
 155    {
 13681156        ContinuousCollisionMotionSegment3D segment =
 13681157            _continuousCollisionTrajectory[_continuousCollisionTrajectory.Count - 1];
 13681158        Fixed64 remainingFraction = Fixed64.One - segment.StartFraction;
 13681159        return segment.Displacement / remainingFraction;
 160    }
 161
 162    internal void EnsureContinuousCollisionFramePrepared(int token)
 163    {
 27153164        if (_continuousCollisionFrameToken == token)
 13459165            return;
 166
 13694167        Vector3d startPosition = Position3d;
 13694168        FixedQuaternion startRotation = Rotation;
 169        Vector3d displacement;
 170        Vector3d endPosition;
 171        FixedQuaternion targetRotation;
 172        Vector3d angularVelocity;
 173        Fixed64 angularDistance;
 174        ContinuousCollisionRotationPath3D rotationPath;
 13694175        if (IsKinematic)
 176        {
 6359177            _continuousCollisionAngularVelocityStepStart = Vector3d.Zero;
 6359178            Vector3d targetPosition = ProjectLinearEndpoint(
 6359179                startPosition,
 6359180                _positionTransform.WorldPosition);
 6359181            endPosition = targetPosition;
 6359182            if (ShouldUseContinuousCollision(out _))
 183            {
 6327184                displacement = ContinuousCollisionSweepRange.ValidateEndpoint(
 6327185                    startPosition,
 6327186                    targetPosition,
 6327187                    out _);
 188            }
 189            else
 190            {
 32191                displacement = targetPosition - startPosition;
 192            }
 193
 6358194            targetRotation = _rotationTransform.WorldRotation.Normalized;
 6358195            angularVelocity = FixedQuaternion.ToAngularVelocity(
 6358196                targetRotation,
 6358197                startRotation,
 6358198                Context.DeltaTime);
 6358199            angularDistance = ResolveKinematicAngularDistanceRadians(
 6358200                startRotation,
 6358201                targetRotation);
 6358202            rotationPath = ContinuousCollisionRotationPath3D.KinematicSlerp;
 203        }
 204        else
 205        {
 7335206            _continuousCollisionAngularVelocityStepStart = _angularVelocity;
 7335207            if (!_isSleeping)
 208            {
 6494209                if (CanTranslate)
 210                {
 6474211                    ApplyLinearForces();
 6474212                    UpdateLinearVelocity();
 213                }
 214
 6494215                if (CanRotate)
 216                {
 6299217                    ApplyAngularTorques();
 6299218                    UpdateAngularVelocity();
 219                }
 220            }
 221
 7335222            displacement = ProjectLinearMotion(_linearVelocity) * Context.DeltaTime;
 7335223            endPosition = startPosition + displacement;
 7335224            angularVelocity = _angularVelocity;
 7335225            targetRotation = IntegrateAngularRotation(
 7335226                startRotation,
 7335227                angularVelocity,
 7335228                Context.DeltaTime);
 7335229            angularDistance = angularVelocity.Magnitude * Context.DeltaTime;
 7335230            rotationPath = ContinuousCollisionRotationPath3D.IntegratedAngularVelocity;
 231        }
 232
 13693233        _continuousCollisionFrameToken = token;
 13693234        SetSingleContinuousCollisionSegment(new ContinuousCollisionMotionSegment3D(
 13693235            Fixed64.Zero,
 13693236            Fixed64.One,
 13693237            startPosition,
 13693238            endPosition,
 13693239            displacement,
 13693240            startRotation,
 13693241            angularVelocity,
 13693242            targetRotation,
 13693243            angularDistance,
 13693244            rotationPath));
 13693245    }
 246
 247    private void InvalidateContinuousCollisionTrajectory()
 248    {
 11766249        _continuousCollisionFrameToken = int.MinValue;
 11766250        _continuousCollisionAngularVelocityStepStart = Vector3d.Zero;
 11766251        _continuousCollisionTrajectory.FastClear();
 11766252    }
 253
 254    private void SetSingleContinuousCollisionSegment(
 255        ContinuousCollisionMotionSegment3D segment)
 256    {
 13693257        _continuousCollisionTrajectory.FastClear();
 13693258        _continuousCollisionTrajectory.Add(segment);
 13693259    }
 260
 261    private void AppendContinuousCollisionSegment(
 262        Vector3d positionAtElapsedTime,
 263        Vector3d velocity,
 264        Fixed64 elapsedTime)
 265    {
 151266        Fixed64 startFraction = FixedMath.Clamp01(elapsedTime / Context.DeltaTime);
 151267        FixedQuaternion startRotation = SampleContinuousCollisionRotation(startFraction);
 151268        AppendContinuousCollisionSegment(
 151269            positionAtElapsedTime,
 151270            startRotation,
 151271            velocity,
 151272            _angularVelocity,
 151273            elapsedTime);
 151274    }
 275
 276    private void AppendContinuousCollisionSegment(
 277        Vector3d positionAtElapsedTime,
 278        FixedQuaternion rotationAtElapsedTime,
 279        Vector3d linearVelocity,
 280        Vector3d angularVelocity,
 281        Fixed64 elapsedTime)
 282    {
 464283        Fixed64 deltaTime = Context.DeltaTime;
 464284        Fixed64 startFraction = FixedMath.Clamp01(elapsedTime / deltaTime);
 464285        Fixed64 remainingTime = FixedMath.Max(Fixed64.Zero, deltaTime - elapsedTime);
 464286        Vector3d displacement = ProjectLinearMotion(linearVelocity) * remainingTime;
 464287        FixedQuaternion targetRotation = IntegrateAngularRotation(
 464288            rotationAtElapsedTime,
 464289            angularVelocity,
 464290            remainingTime);
 464291        _continuousCollisionFrameToken = Context.LateSimulateToken;
 464292        RemoveSupersededContinuousCollisionSegments(
 464293            startFraction,
 464294            positionAtElapsedTime,
 464295            rotationAtElapsedTime);
 464296        if (startFraction >= Fixed64.One && _continuousCollisionTrajectory.Count > 0)
 3297            return;
 298
 461299        _continuousCollisionTrajectory.Add(new ContinuousCollisionMotionSegment3D(
 461300            startFraction,
 461301            Fixed64.One,
 461302            positionAtElapsedTime,
 461303            positionAtElapsedTime + displacement,
 461304            displacement,
 461305            rotationAtElapsedTime,
 461306            angularVelocity,
 461307            targetRotation,
 461308            angularVelocity.Magnitude * remainingTime,
 461309            ContinuousCollisionRotationPath3D.IntegratedAngularVelocity));
 461310    }
 311
 312    internal bool CanAppendContinuousCollisionSegment(Fixed64 elapsedTime)
 313    {
 517314        Fixed64 startFraction = FixedMath.Clamp01(elapsedTime / Context.DeltaTime);
 517315        int retainedCount = _continuousCollisionTrajectory.Count;
 572316        while (retainedCount > 0
 572317            && _continuousCollisionTrajectory[retainedCount - 1].StartFraction >= startFraction)
 318        {
 55319            retainedCount--;
 320        }
 321
 517322        return retainedCount <= Context.Settings.ContinuousCollisionMaxToiIterations;
 323    }
 324
 325    internal bool TryResolveContinuousCollisionMotionBound(
 326        Fixed64 lowerFraction,
 327        Fixed64 upperFraction,
 328        Fixed64 pivotRadius,
 329        out Fixed64 motionBound)
 330    {
 3955331        motionBound = Fixed64.Zero;
 15900332        for (int i = 0; i < _continuousCollisionTrajectory.Count; i++)
 333        {
 3996334            ContinuousCollisionMotionSegment3D segment = _continuousCollisionTrajectory[i];
 3996335            Fixed64 lower = FixedMath.Max(lowerFraction, segment.StartFraction);
 3996336            Fixed64 upper = FixedMath.Min(upperFraction, segment.EndFraction);
 3996337            if (upper <= lower)
 338                continue;
 339
 3959340            if (!segment.TryResolveMotionBound(lower, upper, pivotRadius, out Fixed64 segmentBound)
 3959341                || !Fixed64.TryAdd(motionBound, segmentBound, out motionBound))
 342            {
 1343                motionBound = default;
 1344                return false;
 345            }
 346        }
 347
 3954348        return true;
 349    }
 350
 351    internal FixedBoundVolume ResolveContinuousCollisionTrajectoryBounds(Fixed64 radius)
 352    {
 588353        Vector3d min = Position3d;
 588354        Vector3d max = Position3d;
 3506355        for (int i = 0; i < _continuousCollisionTrajectory.Count; i++)
 356        {
 1165357            ContinuousCollisionMotionSegment3D segment = _continuousCollisionTrajectory[i];
 1165358            min = Vector3d.Min(min, Vector3d.Min(segment.StartPosition, segment.EndPosition));
 1165359            max = Vector3d.Max(max, Vector3d.Max(segment.StartPosition, segment.EndPosition));
 360        }
 361
 588362        Vector3d extents = Vector3d.One * radius;
 588363        return new FixedBoundVolume(min - extents, max + extents);
 364    }
 365
 366    private void RemoveSupersededContinuousCollisionSegments(
 367        Fixed64 startFraction,
 368        Vector3d positionAtElapsedTime,
 369        FixedQuaternion rotationAtElapsedTime)
 370    {
 515371        while (_continuousCollisionTrajectory.Count > 0
 515372            && _continuousCollisionTrajectory[_continuousCollisionTrajectory.Count - 1]
 515373                .StartFraction >= startFraction)
 374        {
 51375            _continuousCollisionTrajectory.RemoveAt(
 51376                _continuousCollisionTrajectory.Count - 1);
 377        }
 378
 464379        if (_continuousCollisionTrajectory.Count == 0)
 34380            return;
 381
 430382        int predecessorIndex = _continuousCollisionTrajectory.Count - 1;
 430383        ContinuousCollisionMotionSegment3D predecessor =
 430384            _continuousCollisionTrajectory[predecessorIndex];
 430385        if (predecessor.EndFraction <= startFraction)
 21386            return;
 387
 409388        Fixed64 localFraction = (startFraction - predecessor.StartFraction)
 409389            / (predecessor.EndFraction - predecessor.StartFraction);
 409390        _continuousCollisionTrajectory[predecessorIndex] =
 409391            new ContinuousCollisionMotionSegment3D(
 409392                predecessor.StartFraction,
 409393                startFraction,
 409394                predecessor.StartPosition,
 409395                positionAtElapsedTime,
 409396                positionAtElapsedTime - predecessor.StartPosition,
 409397                predecessor.StartRotation,
 409398                predecessor.AngularVelocity,
 409399                rotationAtElapsedTime,
 409400                predecessor.AngularDistance * localFraction,
 409401                predecessor.RotationPath);
 409402    }
 403
 404    private ContinuousCollisionMotionSegment3D ResolveContinuousCollisionSegment(
 405        Fixed64 frameFraction) =>
 9305406        _continuousCollisionTrajectory[
 9305407            GetContinuousCollisionTrajectorySegmentIndex(frameFraction)];
 408
 409    internal int GetContinuousCollisionTrajectorySegmentIndex(
 410        Fixed64 frameFraction)
 411    {
 18770412        for (int i = _continuousCollisionTrajectory.Count - 1; i > 0; i--)
 413        {
 252414            ContinuousCollisionMotionSegment3D segment =
 252415                _continuousCollisionTrajectory[i];
 252416            if (frameFraction >= segment.StartFraction)
 197417                return i;
 418        }
 419
 9133420        return 0;
 421    }
 422
 423    internal void GetContinuousCollisionTrajectorySegmentRange(
 424        Fixed64 startFraction,
 425        out int startIndex,
 426        out int endExclusive)
 427    {
 169428        startIndex = startFraction <= Fixed64.Zero
 169429            ? 0
 169430            : GetContinuousCollisionTrajectorySegmentIndex(startFraction);
 169431        endExclusive = _continuousCollisionTrajectory.Count;
 169432    }
 433}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.ContinuousCollision.Dynamic.cs

#LineLine coverage
 1//=======================================================================
 2// SolidBody.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;
 12
 13namespace Gravitas;
 14
 15public partial class SolidBody
 16{
 17    internal bool ApplyContinuousCollisionHandoff(
 18        Vector3d positionAtImpact,
 19        Vector3d velocityDelta,
 20        Fixed64 remainingTime,
 21        LSCollider? ignoredCollider3D = null,
 22        LSCollider2D? ignoredCollider2D = null)
 23    {
 4224        if (!Vector3d.TryAdd(_linearVelocity, ProjectLinearMotion(velocityDelta), out Vector3d postLinearVelocity))
 125            return false;
 26
 4127        return ApplyContinuousCollisionHandoff(
 4128            positionAtImpact,
 4129            Rotation,
 4130            postLinearVelocity,
 4131            _angularVelocity,
 4132            remainingTime,
 4133            ignoredCollider3D,
 4134            ignoredCollider2D);
 35    }
 36
 37    internal bool ApplyContinuousCollisionHandoff(
 38        Vector3d positionAtImpact,
 39        FixedQuaternion rotationAtImpact,
 40        Vector3d postLinearVelocity,
 41        Vector3d postAngularVelocity,
 42        Fixed64 remainingTime,
 43        LSCollider? ignoredCollider3D = null,
 44        LSCollider2D? ignoredCollider2D = null)
 45    {
 9546        if (!CanApplyContinuousCollisionHandoff(
 9547                positionAtImpact,
 9548                rotationAtImpact,
 9549                remainingTime,
 9550                out Vector3d resolvedPosition))
 351            return false;
 52
 9253        _ = Context.Physics.TryReserveContinuousCollisionCandidateRefresh(this);
 54
 9255        ApplyContinuousCollisionHandoffReserved(
 9256            resolvedPosition,
 9257            rotationAtImpact,
 9258            postLinearVelocity,
 9259            postAngularVelocity,
 9260            remainingTime,
 9261            ignoredCollider3D,
 9262            ignoredCollider2D);
 9263        return true;
 64    }
 65
 66    internal void ApplyContinuousCollisionHandoffReserved(
 67        Vector3d resolvedPosition,
 68        FixedQuaternion rotationAtImpact,
 69        Vector3d postLinearVelocity,
 70        Vector3d postAngularVelocity,
 71        Fixed64 remainingTime,
 72        LSCollider? ignoredCollider3D = null,
 73        LSCollider2D? ignoredCollider2D = null)
 74    {
 23575        FixedQuaternion resolvedRotation = rotationAtImpact.Normalized;
 23576        Position3d = resolvedPosition;
 23577        Rotation = resolvedRotation;
 23578        Collider.PublishPreparedBodyPose();
 23579        UpdateInertiaTensorOrientation();
 23580        ApplyCollisionVelocityState(postLinearVelocity, postAngularVelocity);
 23581        bool hasRemainingMotion = remainingTime > Fixed64.Epsilon
 23582            && (_linearVelocity.MagnitudeSquared > Fixed64.Epsilon
 23583                || _angularVelocity.MagnitudeSquared > Fixed64.Epsilon);
 23584        AppendContinuousCollisionSegment(
 23585            resolvedPosition,
 23586            Rotation,
 23587            hasRemainingMotion ? _linearVelocity : Vector3d.Zero,
 23588            hasRemainingMotion ? _angularVelocity : Vector3d.Zero,
 23589            Context.DeltaTime - remainingTime);
 23590        Context.Physics.RefreshContinuousCollisionCandidate(this);
 23591        if (!hasRemainingMotion)
 92        {
 2493            DiscardContinuousCollisionHandoff();
 2494            return;
 95        }
 96
 21197        _continuousCollisionHandoffToken = Context.LateSimulateToken;
 21198        _continuousCollisionHandoffRemainingTime = remainingTime;
 21199        _continuousCollisionHandoffIgnoredCollider3D = ignoredCollider3D;
 211100        _continuousCollisionHandoffIgnoredCollider2D = ignoredCollider2D;
 211101        _continuousCollisionHandoffPending = true;
 211102        Context.Physics.QueueContinuousCollisionHandoff(this);
 211103    }
 104
 105    internal bool CanApplyContinuousCollisionHandoff(
 106        Vector3d positionAtImpact,
 107        FixedQuaternion rotationAtImpact,
 108        Fixed64 remainingTime,
 109        out Vector3d resolvedPosition)
 110    {
 250111        bool hasMobility = CanTranslate | CanRotate;
 250112        bool hasTrajectoryCapacity = CanAppendContinuousCollisionSegment(
 250113            Context.DeltaTime - remainingTime);
 250114        bool positionDeltaResolved = Vector3d.TrySubtract(
 250115            positionAtImpact,
 250116            Position3d,
 250117            out Vector3d positionDelta);
 250118        bool positionResolved = Vector3d.TryAdd(
 250119            Position3d,
 250120            ProjectLinearMotion(positionDelta),
 250121            out resolvedPosition);
 250122        if (!(hasMobility
 250123                & hasTrajectoryCapacity
 250124                & positionDeltaResolved
 250125                & positionResolved))
 126        {
 5127            return false;
 128        }
 129
 245130        return Collider.TryPrepareBodyPose(
 245131            resolvedPosition,
 245132            rotationAtImpact.Normalized);
 133    }
 134
 135    internal bool TryConsumeContinuousCollisionHandoff(bool updateSleepState, bool updateColliderState) =>
 13577136        TryConsumeContinuousCollisionHandoff(
 13577137            updateSleepState,
 13577138            updateColliderState,
 13577139            invokeMovementCallback: true,
 13577140            out _);
 141
 142    internal bool TryConsumeQueuedContinuousCollisionHandoff(
 143        bool updateSleepState,
 144        bool updateColliderState,
 145        out bool shouldNotifyMovement) =>
 88146        TryConsumeContinuousCollisionHandoff(
 88147            updateSleepState,
 88148            updateColliderState,
 88149            invokeMovementCallback: false,
 88150            out shouldNotifyMovement);
 151
 152    private bool TryConsumeContinuousCollisionHandoff(
 153        bool updateSleepState,
 154        bool updateColliderState,
 155        bool invokeMovementCallback,
 156        out bool shouldNotifyMovement)
 157    {
 13665158        shouldNotifyMovement = false;
 13665159        if (!_continuousCollisionHandoffPending || _continuousCollisionHandoffToken != Context.LateSimulateToken)
 13512160            return false;
 161
 153162        Fixed64 remainingTime = _continuousCollisionHandoffRemainingTime;
 153163        _continuousCollisionHandoffPending = false;
 153164        _continuousCollisionHandoffRemainingTime = Fixed64.Zero;
 153165        if ((!CanTranslate || _linearVelocity.MagnitudeSquared <= Fixed64.Epsilon)
 153166            && (!CanRotate || _angularVelocity.MagnitudeSquared <= Fixed64.Epsilon))
 167        {
 2168            _continuousCollisionHandoffIgnoredCollider3D = null;
 2169            _continuousCollisionHandoffIgnoredCollider2D = null;
 2170            if (updateColliderState)
 1171                Collider.Simulate();
 2172            return true;
 173        }
 174
 151175        Vector3d startPosition = Position3d;
 151176        Vector3d proposedPosition = startPosition + _linearVelocity * remainingTime;
 151177        FixedQuaternion startRotation = Rotation;
 151178        FixedQuaternion proposedRotation = IntegrateAngularRotation(
 151179            startRotation,
 151180            _angularVelocity,
 151181            remainingTime);
 151182        Fixed64 elapsedTime = FixedMath.Max(Fixed64.Zero, Context.DeltaTime - remainingTime);
 183        try
 184        {
 151185            if (!TryResolveRotationalContinuousCollision(
 151186                    startPosition,
 151187                    ref proposedPosition,
 151188                    startRotation,
 151189                    ref proposedRotation,
 151190                    remainingTime,
 151191                    elapsedTime,
 151192                    forceContinuous: true))
 193            {
 143194                TryResolveContinuousCollision(
 143195                    startPosition,
 143196                    ref proposedPosition,
 143197                    remainingTime,
 143198                    elapsedTime,
 143199                    forceContinuous: true);
 200            }
 151201        }
 202        finally
 203        {
 151204            _continuousCollisionHandoffIgnoredCollider3D = null;
 151205            _continuousCollisionHandoffIgnoredCollider2D = null;
 151206        }
 207
 151208        SetPosition2d(proposedPosition.ToVector2d());
 151209        HeightPos = proposedPosition.Y;
 151210        Rotation = proposedRotation;
 151211        UpdateInertiaTensorOrientation();
 212
 151213        CheckGroundForSimulation();
 151214        ApplyGroundedHeightOrReset();
 215
 151216        CheckChangedValues();
 217
 151218        if (updateColliderState)
 1219            Collider.Simulate();
 220        else
 150221            Collider.RebuildRuntimeShapeOnly(refreshMassProperties: false);
 222
 151223        if (updateSleepState)
 1224            UpdateSleepState();
 225
 151226        shouldNotifyMovement = PositionChangePending || RotationChangePending;
 151227        if (invokeMovementCallback && shouldNotifyMovement)
 68228            NotifyAuthoritativeMovement();
 229
 151230        return true;
 231    }
 232
 148233    internal void NotifyAuthoritativeMovement() => OnMoved?.Invoke();
 234
 235    internal void DiscardContinuousCollisionHandoff()
 236    {
 250237        _continuousCollisionHandoffPending = false;
 250238        _continuousCollisionHandoffRemainingTime = Fixed64.Zero;
 250239        _continuousCollisionHandoffIgnoredCollider3D = null;
 250240        _continuousCollisionHandoffIgnoredCollider2D = null;
 250241    }
 242
 243    private bool TryResolveContinuousCollision(Vector3d startPosition, ref Vector3d proposedPosition) =>
 6318244        TryResolveContinuousCollision(
 6318245            startPosition,
 6318246            ref proposedPosition,
 6318247            Context.DeltaTime,
 6318248            Fixed64.Zero,
 6318249            forceContinuous: false);
 250
 251    private bool TryResolveContinuousCollision(
 252        Vector3d startPosition,
 253        ref Vector3d proposedPosition,
 254        Fixed64 initialRemainingTime,
 255        Fixed64 initialElapsedTime,
 256        bool forceContinuous)
 257    {
 6461258        LastContinuousCollisionToiIterationCount = 0;
 6461259        LastContinuousCollisionToiIterationLimitReached = false;
 260
 6461261        ContinuousCollisionMode mode = ContinuousCollisionMode.Continuous;
 6461262        if (!forceContinuous && !ShouldUseContinuousCollision(out mode))
 6093263            return false;
 264
 368265        Vector3d requestedDisplacement = _linearVelocity * initialRemainingTime;
 368266        Vector3d displacement = ContinuousCollisionSweepRange.ValidateEndpoint(
 368267            startPosition,
 368268            proposedPosition,
 368269            requestedDisplacement,
 368270            out _);
 271
 367272        Fixed64 displacementMagnitudeSquared = displacement.MagnitudeSquared;
 367273        if (displacementMagnitudeSquared <= Fixed64.Epsilon)
 12274            return false;
 275
 355276        Fixed64 proxyRadius = ResolveContinuousCollisionProxyRadius();
 355277        if (proxyRadius <= Fixed64.Epsilon
 355278            || (mode == ContinuousCollisionMode.Auto
 355279                && ContinuousCollisionMath.IsWithinProxyRadius(displacement, displacementMagnitudeSquared, proxyRadius))
 280        {
 2281            return false;
 282        }
 283
 353284        bool resolved = false;
 353285        int conservativeMixedRefinementCount = 0;
 353286        Vector3d currentPosition = startPosition;
 353287        Fixed64 remainingTime = initialRemainingTime;
 353288        Fixed64 elapsedTime = initialElapsedTime;
 353289        int maxToiIterations = Context.Settings.ContinuousCollisionMaxToiIterations;
 353290        LSCollider? originalIgnoredCollider3D = _continuousCollisionHandoffIgnoredCollider3D;
 353291        LSCollider2D? originalIgnoredCollider2D = _continuousCollisionHandoffIgnoredCollider2D;
 292        try
 293        {
 431294            while (LastContinuousCollisionToiIterationCount < maxToiIterations)
 295            {
 431296                Vector3d requestedSegmentDisplacement = _linearVelocity * remainingTime;
 431297                Vector3d requestedSegmentEnd = currentPosition + requestedSegmentDisplacement;
 431298                Vector3d segmentDisplacement = ContinuousCollisionSweepRange.ValidateEndpoint(
 431299                    currentPosition,
 431300                    requestedSegmentEnd,
 431301                    requestedSegmentDisplacement,
 431302                    out Fixed64 segmentLength);
 431303                Vector3d segmentEnd = requestedSegmentEnd;
 304
 431305                Fixed64 elapsedFraction = elapsedTime / Context.DeltaTime;
 431306                if (!TryGetFirstContinuousCollisionHit(
 431307                        currentPosition,
 431308                        segmentEnd,
 431309                        proxyRadius,
 431310                        elapsedFraction,
 431311                        out Vector3d hitNormal,
 431312                        out Fixed64 hitDistance,
 431313                        out ContinuousCollisionTargetKind targetKind,
 431314                        out LSCollider? target3D,
 431315                        out LSCollider2D? target2D))
 316                {
 273317                    currentPosition = segmentEnd;
 273318                    break;
 319                }
 320
 158321                Fixed64 hitTime = FixedMath.Clamp01(hitDistance / segmentLength);
 158322                currentPosition += segmentDisplacement.Normalized * hitDistance;
 158323                Vector3d previousVelocity = _linearVelocity;
 158324                Fixed64 consumedTime = remainingTime * hitTime;
 158325                Fixed64 remainingAfterHit = remainingTime - consumedTime;
 158326                Fixed64 hitElapsedTime = elapsedTime + consumedTime;
 158327                if (targetKind
 158328                    == ContinuousCollisionTargetKind.UnresolvedMixed)
 329                {
 11330                    resolved = true;
 11331                    if (hitTime > Fixed64.Epsilon
 11332                        && conservativeMixedRefinementCount
 11333                            < ContinuousCollisionMath.RotationalIntervalMaxDepth)
 334                    {
 8335                        conservativeMixedRefinementCount++;
 8336                        remainingTime = remainingAfterHit;
 8337                        elapsedTime = hitElapsedTime;
 8338                        if (remainingTime > Fixed64.Epsilon)
 339                            continue;
 340                    }
 341
 3342                    LastContinuousCollisionToiIterationCount++;
 3343                    LastContinuousCollisionToiIterationLimitReached =
 3344                        remainingAfterHit > Fixed64.Epsilon;
 3345                    _ = Context.Physics
 3346                        .TryReserveContinuousCollisionCandidateRefresh(this);
 3347                    UpdateContinuousCollisionFrameTrajectory(
 3348                        currentPosition,
 3349                        Vector3d.Zero,
 3350                        hitElapsedTime);
 3351                    Context.Physics
 3352                        .RefreshContinuousCollisionCandidate(this);
 353
 3354                    if (LastContinuousCollisionToiIterationLimitReached)
 3355                        Context.Physics.ReportContinuousCollisionIterationLimit();
 356
 3357                    break;
 358                }
 359
 147360                if (!CanAppendContinuousCollisionSegment(hitElapsedTime))
 361                {
 3362                    ContinuousCollisionMotionSegment3D activeSegment =
 3363                        ResolveContinuousCollisionSegment(
 3364                            ResolveContinuousCollisionFrameFraction(hitElapsedTime));
 3365                    currentPosition = activeSegment.StartPosition;
 3366                    elapsedTime = activeSegment.StartFraction * Context.DeltaTime;
 3367                    RemoveClosingContinuousCollisionVelocity(hitNormal);
 3368                    UpdateContinuousCollisionFrameTrajectory(
 3369                        currentPosition,
 3370                        _linearVelocity,
 3371                        elapsedTime);
 3372                    Context.Physics.RefreshContinuousCollisionCandidate(this);
 3373                    LastContinuousCollisionToiIterationCount++;
 3374                    LastContinuousCollisionToiIterationLimitReached = true;
 3375                    Context.Physics.ReportContinuousCollisionIterationLimit();
 3376                    resolved = true;
 3377                    break;
 378                }
 379
 144380                bool appliedResponse = TryApplyContinuousCollisionDynamicResponse(
 144381                        hitNormal,
 144382                        targetKind,
 144383                        target3D,
 144384                        target2D,
 144385                        currentPosition,
 144386                        hitElapsedTime,
 144387                        remainingAfterHit);
 144388                if (!appliedResponse)
 389                {
 89390                    RemoveClosingContinuousCollisionVelocity(hitNormal);
 89391                    UpdateContinuousCollisionFrameTrajectory(currentPosition, _linearVelocity, hitElapsedTime);
 392                }
 55393                else if (targetKind == ContinuousCollisionTargetKind.Dynamic3D)
 394                {
 43395                    _continuousCollisionHandoffIgnoredCollider3D = target3D;
 43396                    _continuousCollisionHandoffIgnoredCollider2D = null;
 397                }
 398                else
 399                {
 12400                    _continuousCollisionHandoffIgnoredCollider3D = null;
 12401                    _continuousCollisionHandoffIgnoredCollider2D = target2D;
 402                }
 403
 144404                LastContinuousCollisionToiIterationCount++;
 144405                resolved = true;
 406
 144407                remainingTime = remainingAfterHit;
 144408                elapsedTime = hitElapsedTime;
 144409                if (remainingTime <= Fixed64.Epsilon || _linearVelocity.MagnitudeSquared <= Fixed64.Epsilon)
 410                    break;
 411
 76412                if (LastContinuousCollisionToiIterationCount >= maxToiIterations)
 413                {
 6414                    LastContinuousCollisionToiIterationLimitReached = true;
 6415                    break;
 416                }
 417
 70418                if (hitTime <= Fixed64.Epsilon && previousVelocity == _linearVelocity)
 419                    break;
 420            }
 421
 353422            proposedPosition = currentPosition;
 353423            return resolved;
 424        }
 425        finally
 426        {
 353427            _continuousCollisionHandoffIgnoredCollider3D = originalIgnoredCollider3D;
 353428            _continuousCollisionHandoffIgnoredCollider2D = originalIgnoredCollider2D;
 353429        }
 353430    }
 431
 432    private bool TryApplyContinuousCollisionDynamicResponse(
 433        Vector3d normalForSource,
 434        ContinuousCollisionTargetKind targetKind,
 435        LSCollider? target3D,
 436        LSCollider2D? target2D,
 437        Vector3d sourcePositionAtImpact,
 438        Fixed64 hitElapsedTime,
 439        Fixed64 remainingTime)
 440    {
 144441        if (targetKind == ContinuousCollisionTargetKind.Dynamic3D)
 442        {
 44443            SolidBody targetBody = target3D!.Body!;
 44444            Fixed64 frameFraction = ResolveContinuousCollisionFrameFraction(hitElapsedTime);
 44445            Vector3d targetPositionAtImpact = targetBody.SampleContinuousCollisionPosition(
 44446                frameFraction);
 44447            return TryApplyContinuousCollisionDynamicResponse(
 44448                targetBody,
 44449                normalForSource,
 44450                sourcePositionAtImpact,
 44451                targetPositionAtImpact,
 44452                hitElapsedTime,
 44453                remainingTime);
 454        }
 455
 100456        if (targetKind == ContinuousCollisionTargetKind.Dynamic2D)
 457        {
 14458            SolidBody2D targetBody = target2D!.Body!;
 14459            Fixed64 frameFraction = ResolveContinuousCollisionFrameFraction(hitElapsedTime);
 14460            Vector2d targetPositionAtImpact = targetBody.SampleContinuousCollisionPosition(
 14461                frameFraction);
 14462            return TryApplyContinuousCollisionMixed2DResponse(
 14463                targetBody,
 14464                normalForSource,
 14465                sourcePositionAtImpact,
 14466                targetPositionAtImpact,
 14467                hitElapsedTime,
 14468                remainingTime);
 469        }
 470
 86471        return false;
 472    }
 473
 474    internal bool TryApplyContinuousCollisionDynamicResponse(
 475        SolidBody target,
 476        Vector3d normalForSource,
 477        Vector3d sourcePositionAtImpact,
 478        Vector3d targetPositionAtImpact,
 479        Fixed64 hitElapsedTime,
 480        Fixed64 remainingTime)
 481    {
 51482        if (!ContinuousCollisionImpulsePolicy.TryResolveImpactNormal(normalForSource, out Vector3d normal))
 1483            return false;
 484
 50485        Fixed64 constrainedInverseMassA = GetConstrainedInverseMass(normal);
 50486        Fixed64 constrainedInverseMassB = target.GetConstrainedInverseMass(normal);
 50487        Fixed64 frameFraction = ResolveContinuousCollisionFrameFraction(hitElapsedTime);
 50488        Vector3d targetLinearVelocity = target.SampleContinuousCollisionLinearVelocity(frameFraction);
 50489        bool inverseMassResolved = Fixed64.TryAdd(
 50490            constrainedInverseMassA,
 50491            constrainedInverseMassB,
 50492            out Fixed64 inverseMass);
 50493        bool relativeVelocityResolved = Vector3d.TrySubtract(
 50494            _linearVelocity,
 50495            targetLinearVelocity,
 50496            out Vector3d relativeVelocity);
 50497        if (!(inverseMassResolved & relativeVelocityResolved))
 498        {
 1499            return false;
 500        }
 501
 49502        Fixed64 normalVelocity = Vector3d.Dot(relativeVelocity, normal);
 49503        if (normalVelocity >= -Fixed64.Epsilon)
 1504            return false;
 505
 48506        Fixed64 restitution = ResolveContinuousCollisionRestitution(target, -normalVelocity);
 48507        bool responseFactorResolved = Fixed64.TryAdd(
 48508            Fixed64.One,
 48509            restitution,
 48510            out Fixed64 responseFactor);
 48511        bool closingSpeedResolved = Fixed64.TrySubtract(
 48512            Fixed64.Zero,
 48513            normalVelocity,
 48514            out Fixed64 closingSpeed);
 48515        bool responseSpeedResolved = Fixed64.TryMultiplyDivide(
 48516            closingSpeed,
 48517            responseFactor,
 48518            Fixed64.One,
 48519            out Fixed64 responseSpeed);
 48520        if (!(responseFactorResolved & closingSpeedResolved & responseSpeedResolved))
 521        {
 1522            return false;
 523        }
 524
 47525        Vector3d sourceResponseNormal = ProjectLinearMotion(normal);
 47526        Vector3d targetResponseNormal = target.ProjectLinearMotion(-normal);
 47527        bool sourceDeltaResolved = ContinuousCollisionImpulsePolicy.TryResolveVelocityDelta(
 47528            sourceResponseNormal,
 47529            responseSpeed,
 47530            EffectiveInverseMass,
 47531            inverseMass,
 47532            out Vector3d sourceVelocityDelta);
 47533        bool targetDeltaResolved = ContinuousCollisionImpulsePolicy.TryResolveVelocityDelta(
 47534            targetResponseNormal,
 47535            responseSpeed,
 47536            target.EffectiveInverseMass,
 47537            inverseMass,
 47538            out Vector3d targetVelocityDelta);
 47539        if (!(sourceDeltaResolved & targetDeltaResolved))
 540        {
 1541            return false;
 542        }
 543
 46544        bool sourceVelocityResolved = Vector3d.TryAdd(
 46545            _linearVelocity,
 46546            sourceVelocityDelta,
 46547            out Vector3d sourcePostLinearVelocity);
 46548        bool sourceTrajectoryAvailable = CanAppendContinuousCollisionSegment(hitElapsedTime);
 46549        if (!(sourceVelocityResolved & sourceTrajectoryAvailable))
 550        {
 1551            return false;
 552        }
 553
 45554        Vector3d targetResolvedPosition = default;
 45555        Vector3d targetPostLinearVelocity = targetLinearVelocity;
 45556        FixedQuaternion targetRotationAtImpact = target.SampleContinuousCollisionRotation(frameFraction);
 45557        Vector3d targetAngularVelocity = target.SampleContinuousCollisionAngularVelocity(frameFraction);
 45558        bool targetStateAvailable = true;
 45559        if (target.CanTranslate)
 560        {
 42561            bool targetVelocityResolved = Vector3d.TryAdd(
 42562                targetLinearVelocity,
 42563                targetVelocityDelta,
 42564                out targetPostLinearVelocity);
 42565            bool targetTrajectoryAvailable = target.CanApplyContinuousCollisionHandoff(
 42566                targetPositionAtImpact,
 42567                targetRotationAtImpact,
 42568                remainingTime,
 42569                out targetResolvedPosition);
 42570            targetStateAvailable = targetVelocityResolved & targetTrajectoryAvailable;
 571        }
 572
 45573        if (!targetStateAvailable)
 574        {
 1575            return false;
 576        }
 577
 44578        _ = target.CanTranslate
 44579            ? Context.Physics.TryReserveContinuousCollisionCandidateRefresh(this, target)
 44580            : Context.Physics.TryReserveContinuousCollisionCandidateRefresh(this);
 581
 44582        ApplyCollisionVelocityState(sourcePostLinearVelocity, _angularVelocity);
 44583        UpdateContinuousCollisionFrameTrajectory(sourcePositionAtImpact, _linearVelocity, hitElapsedTime);
 44584        Context.Physics.RefreshContinuousCollisionCandidate(this);
 44585        if (target.CanTranslate)
 586        {
 41587            target.ApplyContinuousCollisionHandoffReserved(
 41588                targetResolvedPosition,
 41589                targetRotationAtImpact,
 41590                targetPostLinearVelocity,
 41591                targetAngularVelocity,
 41592                remainingTime,
 41593                ignoredCollider3D: Collider);
 594        }
 595
 44596        return true;
 597    }
 598
 599    internal bool TryApplyContinuousCollisionMixed2DResponse(
 600        SolidBody2D target,
 601        Vector3d normalForSource,
 602        Vector3d sourcePositionAtImpact,
 603        Vector2d targetPositionAtImpact,
 604        Fixed64 hitElapsedTime,
 605        Fixed64 remainingTime)
 606    {
 15607        bool normalResolved = ContinuousCollisionImpulsePolicy.TryResolveImpactNormal(
 15608            normalForSource,
 15609            out Vector3d normal);
 15610        Vector2d planarNormal = normal.ToVector2d();
 15611        bool targetParticipates =
 15612            planarNormal != Vector2d.Zero && target.CanTranslate;
 15613        Fixed64 constrainedInverseMassA = GetConstrainedInverseMass(normal);
 15614        Fixed64 constrainedInverseMassB = targetParticipates
 15615            ? target.GetConstrainedInverseMass(planarNormal)
 15616                * planarNormal.MagnitudeSquared
 15617            : Fixed64.Zero;
 15618        Fixed64 frameFraction = ResolveContinuousCollisionFrameFraction(hitElapsedTime);
 15619        Vector2d targetLinearVelocity = target.SampleContinuousCollisionLinearVelocity(frameFraction);
 15620        Vector3d targetVelocity = targetLinearVelocity
 15621            .ToVector3d(Fixed64.Zero);
 15622        bool inverseMassResolved = Fixed64.TryAdd(
 15623            constrainedInverseMassA,
 15624            constrainedInverseMassB,
 15625            out Fixed64 inverseMass);
 15626        bool relativeVelocityResolved = Vector3d.TrySubtract(
 15627            _linearVelocity,
 15628            targetVelocity,
 15629            out Vector3d relativeVelocity);
 15630        Fixed64 normalVelocity = Vector3d.Dot(relativeVelocity, normal);
 15631        Fixed64 restitution = ResolveContinuousCollisionRestitution(target, -normalVelocity);
 15632        bool responseFactorResolved = Fixed64.TryAdd(
 15633            Fixed64.One,
 15634            restitution,
 15635            out Fixed64 responseFactor);
 15636        bool closingSpeedResolved = Fixed64.TrySubtract(
 15637            Fixed64.Zero,
 15638            normalVelocity,
 15639            out Fixed64 closingSpeed);
 15640        bool responseSpeedResolved = Fixed64.TryMultiplyDivide(
 15641            closingSpeed,
 15642            responseFactor,
 15643            Fixed64.One,
 15644            out Fixed64 responseSpeed);
 15645        Vector3d sourceResponseNormal = ProjectLinearMotion(normal);
 15646        Vector2d targetResponseNormal = targetParticipates
 15647            ? target.ProjectLinearMotion(-planarNormal)
 15648            : Vector2d.Zero;
 15649        bool sourceDeltaResolved = ContinuousCollisionImpulsePolicy.TryResolveVelocityDelta(
 15650            sourceResponseNormal,
 15651            responseSpeed,
 15652            EffectiveInverseMass,
 15653            inverseMass,
 15654            out Vector3d sourceVelocityDelta);
 15655        Vector2d targetVelocityDelta = Vector2d.Zero;
 15656        bool targetDeltaResolved = !targetParticipates
 15657            || ContinuousCollisionImpulsePolicy.TryResolveVelocityDelta(
 15658                targetResponseNormal,
 15659                responseSpeed,
 15660                target.EffectiveInverseMass,
 15661                inverseMass,
 15662                out targetVelocityDelta);
 15663        bool sourceVelocityResolved = Vector3d.TryAdd(
 15664            _linearVelocity,
 15665            sourceVelocityDelta,
 15666            out Vector3d sourcePostLinearVelocity);
 15667        bool sourceTrajectoryAvailable = CanAppendContinuousCollisionSegment(hitElapsedTime);
 15668        Vector2d targetResolvedPosition = default;
 15669        Vector2d targetPostLinearVelocity = targetLinearVelocity;
 15670        Fixed64 targetRotationAtImpact = target.SampleContinuousCollisionRotation(frameFraction);
 15671        Fixed64 targetAngularVelocity = target.SampleContinuousCollisionAngularVelocity(frameFraction);
 15672        bool targetStateAvailable = true;
 15673        if (targetParticipates)
 674        {
 10675            bool targetVelocityResolved = Vector2d.TryAdd(
 10676                targetLinearVelocity,
 10677                targetVelocityDelta,
 10678                out targetPostLinearVelocity);
 10679            bool targetTrajectoryAvailable = target.CanApplyContinuousCollisionHandoffState(
 10680                targetPositionAtImpact,
 10681                targetRotationAtImpact,
 10682                remainingTime,
 10683                out targetResolvedPosition);
 10684            targetStateAvailable = targetVelocityResolved & targetTrajectoryAvailable;
 685        }
 686
 15687        bool responseAdmissible = normalResolved
 15688            & inverseMassResolved
 15689            & relativeVelocityResolved
 15690            & normalVelocity < -Fixed64.Epsilon
 15691            & responseFactorResolved
 15692            & closingSpeedResolved
 15693            & responseSpeedResolved
 15694            & sourceDeltaResolved
 15695            & targetDeltaResolved
 15696            & sourceVelocityResolved
 15697            & sourceTrajectoryAvailable
 15698            & targetStateAvailable;
 15699        if (!responseAdmissible)
 700        {
 3701            return false;
 702        }
 703
 12704        _ = Context.Physics.TryReserveContinuousCollisionCandidateRefresh(this);
 12705        if (targetParticipates)
 10706            _ = Context.Physics2D.TryReserveContinuousCollisionCandidateRefresh(target);
 707
 12708        ApplyCollisionVelocityState(sourcePostLinearVelocity, _angularVelocity);
 12709        UpdateContinuousCollisionFrameTrajectory(sourcePositionAtImpact, _linearVelocity, hitElapsedTime);
 12710        Context.Physics.RefreshContinuousCollisionCandidate(this);
 12711        if (targetParticipates)
 712        {
 10713            target.ApplyContinuousCollisionHandoffStateReserved(
 10714                targetResolvedPosition,
 10715                targetRotationAtImpact,
 10716                targetPostLinearVelocity,
 10717                targetAngularVelocity,
 10718                remainingTime,
 10719                ignoredCollider3D: Collider);
 720        }
 721
 12722        return true;
 723    }
 724
 725    private void UpdateContinuousCollisionFrameTrajectory(
 726        Vector3d positionAtElapsedTime,
 727        Vector3d velocity,
 728        Fixed64 elapsedTime)
 729    {
 151730        AppendContinuousCollisionSegment(
 151731            positionAtElapsedTime,
 151732            velocity,
 151733            elapsedTime);
 151734    }
 735
 736    private Fixed64 ResolveContinuousCollisionFrameFraction(Fixed64 hitElapsedTime)
 737    {
 126738        return FixedMath.Clamp01(hitElapsedTime / Context.DeltaTime);
 739    }
 740
 741    private Fixed64 ResolveContinuousCollisionRestitution(SolidBody target, Fixed64 closingSpeed)
 742    {
 58743        if (closingSpeed <= Context.Settings.RestitutionVelocityThreshold)
 2744            return Fixed64.Zero;
 745
 56746        return PhysicsMaterial.CombineRestitution(Collider.Material, target.Collider.Material);
 747    }
 748
 749    private Fixed64 ResolveContinuousCollisionRestitution(SolidBody2D target, Fixed64 closingSpeed)
 750    {
 21751        if (closingSpeed <= Context.Settings.RestitutionVelocityThreshold)
 5752            return Fixed64.Zero;
 753
 16754        return PhysicsMaterial.CombineRestitution(Collider.Material, target.Collider.Material);
 755    }
 756
 757    private void RemoveClosingContinuousCollisionVelocity(Vector3d normal)
 758    {
 130759        Fixed64 closingSpeed = Vector3d.Dot(_linearVelocity, normal);
 130760        if (closingSpeed >= Fixed64.Zero)
 39761            return;
 762
 91763        Vector3d lastVelocity = _linearVelocity;
 91764        _linearVelocity -= normal * closingSpeed;
 91765        RefreshLinearMotionState(lastVelocity);
 91766        Context.Diagnostics.EmitLinearVelocityDelta(this, lastVelocity, _linearVelocity);
 91767    }
 768
 769}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.ContinuousCollision.Helpers.cs

#LineLine coverage
 1//=======================================================================
 2// SolidBody.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 System;
 12using System.Runtime.CompilerServices;
 13
 14namespace Gravitas;
 15
 16public partial class SolidBody
 17{
 18    private bool IsEligibleDynamicContinuousCollisionTarget(SolidBody target) =>
 684819        (IsKinematic || !target.ShouldOwnContinuousCollisionMovingPair(
 684820            HasContinuousCollisionRotationalMotion))
 684821        && !ContinuousCollisionCandidateOrdering.IsIgnoredTarget(
 684822            target.Collider,
 684823            _continuousCollisionHandoffIgnoredCollider3D)
 684824        && ContinuousCollisionTargetPolicy.AllowsIndexed3DTarget(
 684825            ReferenceEquals(target, this),
 684826            target.Active,
 684827            target.IsDynamic,
 684828            target.IsKinematic,
 684829            target.IsKinematic && target.HasContinuousCollisionMotion,
 684830            target.Collider.IsTrigger,
 684831            Context.Physics.RequireCollisionPair(Collider, target.Collider));
 32
 33    private bool IsEligibleDynamicMixed2DTarget(SolidBody2D target) =>
 10834        (IsKinematic || !target.ShouldOwnContinuousCollisionMovingPair(
 10835            HasContinuousCollisionRotationalMotion))
 10836        && !ContinuousCollisionCandidateOrdering.IsIgnoredTarget(
 10837            target.Collider,
 10838            _continuousCollisionHandoffIgnoredCollider2D)
 10839        && ContinuousCollisionTargetPolicy.AllowsMixedIndexedTarget(
 10840            target.Active,
 10841            target.IsDynamic,
 10842            target.IsKinematic,
 10843            target.IsKinematic && target.HasContinuousCollisionMotion,
 10844            target.Collider.IsTrigger,
 10845            Context.MixedCollisions.RequireCollisionPair(Collider, target.Collider));
 46
 47    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 48    private bool ShouldUseContinuousCollision(out ContinuousCollisionMode mode)
 49    {
 4269150        mode = ResolveContinuousCollisionMode();
 4269151        return mode == ContinuousCollisionMode.Continuous || mode == ContinuousCollisionMode.Auto;
 52    }
 53
 54    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 55    private ContinuousCollisionMode ResolveContinuousCollisionMode()
 56    {
 4269157        ContinuousCollisionMode mode = _continuousCollisionMode;
 4269158        if (mode != ContinuousCollisionMode.Inherit)
 2590259            return mode;
 60
 1678961        SolidBody? parentBody = Collider.TopParent3D?.Body;
 1678962        if (parentBody != null && parentBody._continuousCollisionMode != ContinuousCollisionMode.Inherit)
 663            return parentBody._continuousCollisionMode;
 64
 1678365        mode = Context.Settings.DefaultContinuousCollisionMode;
 1678366        return mode == ContinuousCollisionMode.Inherit
 1678367            ? ContinuousCollisionMode.Discrete
 1678368            : mode;
 69    }
 70
 71    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 72    internal Fixed64 ResolveContinuousCollisionProxyRadius()
 73    {
 3170774        bool offsetResolved =
 3170775            Collider.TryGetCurrentScaledOffset(out Vector3d centerOffset);
 3170776        bool distanceResolved =
 3170777            centerOffset.TryGetMagnitudeCeiling(
 3170778                out Fixed64 offsetDistance);
 3170779        bool radiusResolved = Fixed64.TryAdd(
 3170780            offsetDistance,
 3170781            Collider.CanonicalCenteredProxyRadius,
 3170782            out Fixed64 radius);
 3170783        return offsetResolved & distanceResolved & radiusResolved
 3170784            ? radius
 3170785            : Fixed64.MaxValue;
 86    }
 87
 88    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 89    private static bool IsClosingContinuousCollisionHit(Vector3d displacement, Vector3d normal) =>
 632790        Vector3d.Dot(displacement, normal) < -Fixed64.Epsilon;
 91
 92    private bool IsValidContinuousCollisionTarget(LSCollider hitCollider)
 93    {
 640194        SolidBody? hitBody = hitCollider.Body;
 640195        if (hitBody?.IsKinematic == true)
 96        {
 1697            hitBody.EnsureContinuousCollisionFramePrepared(Context.LateSimulateToken);
 1698            if (hitBody.HasContinuousCollisionMotion)
 699                return false;
 100        }
 101
 6395102        return ContinuousCollisionTargetPolicy.AllowsStaticOrKinematic3DTarget(
 6395103            ReferenceEquals(hitCollider, Collider),
 6395104            ContinuousCollisionCandidateOrdering.IsIgnoredTarget(hitCollider, _continuousCollisionHandoffIgnoredCollider
 6395105            hitCollider.IsTrigger,
 6395106            Context.Physics.RequireCollisionPair(Collider, hitCollider),
 6395107            hitCollider.IsStatic,
 6395108            hitBody != null && hitBody.IsKinematic);
 109    }
 110
 111    private bool IsValidMixedContinuousCollisionHit(LSCollider2D hitCollider)
 112    {
 7113        SolidBody2D? hitBody = hitCollider.Body;
 7114        if (hitBody?.IsKinematic == true)
 115        {
 3116            hitBody.EnsureContinuousCollisionFramePrepared(Context.LateSimulateToken);
 3117            if (hitBody.HasContinuousCollisionMotion)
 3118                return false;
 119        }
 120
 4121        return ContinuousCollisionTargetPolicy.AllowsMixedStaticOrKinematicTarget(
 4122            ContinuousCollisionCandidateOrdering.IsIgnoredTarget(hitCollider, _continuousCollisionHandoffIgnoredCollider
 4123            hitCollider.IsTrigger,
 4124            Context.MixedCollisions.RequireCollisionPair(Collider, hitCollider),
 4125            hitCollider.IsStatic,
 4126            hitBody != null && hitBody.IsKinematic);
 127    }
 128
 129}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.ContinuousCollision.Hits.cs

#LineLine coverage
 1//=======================================================================
 2// SolidBody.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 partial class SolidBody
 19{
 20    private bool TryGetFirstContinuousCollisionHit(
 21        Vector3d startPosition,
 22        Vector3d proposedPosition,
 23        Fixed64 proxyRadius,
 24        Fixed64 elapsedFrameFraction,
 25        out Vector3d normal,
 26        out Fixed64 distance,
 27        out ContinuousCollisionTargetKind targetKind,
 28        out LSCollider? target3D,
 29        out LSCollider2D? target2D)
 30    {
 43131        Vector3d originalPosition = Position3d;
 43132        bool originalPositionMutated = _positionMutated;
 33        try
 34        {
 43135            Position3d = startPosition;
 43136            Collider.RebuildRuntimeShapeOnly(refreshMassProperties: false);
 37
 43138            int hitCount = QueryStaticContinuousCollisionHits(
 43139                startPosition,
 43140                proposedPosition,
 43141                proxyRadius,
 43142                out bool staticHitsAreShapeExact);
 43143            int mixedHitCount = Context.Settings.RuntimeMode.RunsMixedContacts()
 43144                ? Context.QueryMixed.SweepSphereAgainstStatic2DAll(
 43145                    startPosition,
 43146                    proposedPosition,
 43147                    proxyRadius,
 43148                    PhysicsLayerMask.All,
 43149                    _continuousMixedCollisionHits,
 43150                    Collider,
 43151                    includeTriggers: false,
 43152                    cacheTargetPartitions: true)
 43153                : 0;
 54
 43155            bool found3D = TryGetFirstValidContinuousCollisionHit(
 43156                startPosition,
 43157                proposedPosition,
 43158                hitCount,
 43159                staticHitsAreShapeExact,
 43160                out Physics3DHit hit3D);
 43161            ContinuousCollisionTargetKind hit3DKind = found3D
 43162                ? ContinuousCollisionTargetKind.Static3D
 43163                : ContinuousCollisionTargetKind.None;
 43164            bool foundDynamic3D = TryGetFirstDynamicContinuousCollisionHit(
 43165                startPosition,
 43166                proposedPosition,
 43167                proxyRadius,
 43168                elapsedFrameFraction,
 43169                out Physics3DHit dynamicHit3D,
 43170                out Fixed64 dynamicClosingSpeed3D);
 43171            if (ContinuousCollisionCandidateOrdering.ShouldReplaceHit(dynamicHit3D, dynamicClosingSpeed3D, foundDynamic3
 72            {
 4773                hit3D = dynamicHit3D;
 4774                found3D = true;
 4775                hit3DKind = ContinuousCollisionTargetKind.Dynamic3D;
 76            }
 77
 43178            bool foundMixed = TryGetFirstValidMixedContinuousCollisionHit(startPosition, proposedPosition, mixedHitCount
 43179            ContinuousCollisionTargetKind hitMixedKind = foundMixed
 43180                ? ContinuousCollisionTargetKind.Static2D
 43181                : ContinuousCollisionTargetKind.None;
 43182            Fixed64 hitMixedDistance = foundMixed
 43183                ? hitMixed.Distance
 43184                : Fixed64.Zero;
 43185            bool foundDynamicMixed = TryGetFirstDynamicMixedContinuousCollisionHit(
 43186                startPosition,
 43187                proposedPosition,
 43188                proxyRadius,
 43189                elapsedFrameFraction,
 43190                out DynamicMixedIntervalHit dynamicMixed);
 43191            if (DynamicMixedIntervalHit.ShouldReplaceStatic(
 43192                    dynamicMixed,
 43193                    foundDynamicMixed,
 43194                    hitMixed,
 43195                    foundMixed))
 96            {
 2697                hitMixed = dynamicMixed.ExactHit;
 2698                hitMixedDistance = dynamicMixed.SafeDistance;
 2699                foundMixed = true;
 26100                hitMixedKind = dynamicMixed.Status
 26101                    == ContinuousCollisionMath.IntervalSearchStatus.ExactHit
 26102                    ? ContinuousCollisionTargetKind.Dynamic2D
 26103                    : ContinuousCollisionTargetKind.UnresolvedMixed;
 104            }
 105
 431106            if (DynamicMixedIntervalHit.ShouldSelect3D(
 431107                    found3D,
 431108                    hit3D.Distance,
 431109                    foundMixed,
 431110                    hitMixedDistance))
 111            {
 131112                normal = hit3D.Normal;
 131113                distance = hit3D.Distance;
 131114                targetKind = hit3DKind;
 131115                target3D = hit3D.Collider;
 131116                target2D = null;
 131117                return true;
 118            }
 119
 300120            if (foundMixed)
 121            {
 27122                normal = hitMixedKind == ContinuousCollisionTargetKind.UnresolvedMixed
 27123                    ? Vector3d.Zero
 27124                    : hitMixed.NormalFor3DSource;
 27125                distance = hitMixedDistance;
 27126                targetKind = hitMixedKind;
 27127                target3D = null;
 27128                target2D = hitMixedKind == ContinuousCollisionTargetKind.UnresolvedMixed
 27129                    ? null
 27130                    : hitMixed.Collider2D;
 27131                return true;
 132            }
 273133        }
 134        finally
 135        {
 431136            Position3d = originalPosition;
 431137            Collider.RebuildRuntimeShapeOnly(refreshMassProperties: false);
 431138            _positionMutated = originalPositionMutated;
 431139        }
 140
 273141        normal = Vector3d.Zero;
 273142        distance = Fixed64.Zero;
 273143        targetKind = ContinuousCollisionTargetKind.None;
 273144        target3D = null;
 273145        target2D = null;
 273146        return false;
 158147    }
 148
 149    private bool TryGetFirstValidContinuousCollisionHit(
 150        Vector3d startPosition,
 151        Vector3d proposedPosition,
 152        int hitCount,
 153        bool hitsAreShapeExact,
 154        out Physics3DHit hit)
 155    {
 6633156        Vector3d displacement = proposedPosition - startPosition;
 6633157        Vector3d direction = displacement.Normalized;
 6633158        bool found = false;
 6633159        Physics3DHit best = default;
 25926160        for (int i = 0; i < hitCount; i++)
 161        {
 6330162            Physics3DHit candidate = _continuousCollisionHits[i];
 6330163            if (!IsValidContinuousCollisionTarget(candidate.Collider!))
 164                continue;
 165
 166            Physics3DHit refined;
 6325167            if (hitsAreShapeExact)
 9168                refined = ApplyShapeExactContinuousContactSlop(candidate);
 6316169            else if (TryRefineShapeExactContinuousCollisionHit(candidate, displacement, direction, out Physics3DHit exac
 1170                refined = exactHit;
 6315171            else if (exactSupported)
 172                continue;
 173            else
 6314174                refined = candidate;
 175
 6324176            if (!IsClosingContinuousCollisionHit(displacement, refined.Normal))
 177                continue;
 178
 6293179            if (found && !ContinuousCollisionHitComesBefore(refined, best))
 180                continue;
 181
 6261182            best = refined;
 6261183            found = true;
 184        }
 185
 6633186        hit = best;
 6633187        return found;
 188    }
 189
 190    private int QueryStaticContinuousCollisionHits(
 191        Vector3d startPosition,
 192        Vector3d proposedPosition,
 193        Fixed64 proxyRadius,
 194        out bool hitsAreShapeExact)
 195    {
 6633196        Vector3d displacement = proposedPosition - startPosition;
 6633197        if (Collider is not LSSphereCollider && IsExactConvexSourceSupported(Collider))
 198        {
 103199            hitsAreShapeExact = true;
 103200            return Context.Query3D.SweepExactSourceAgainstStaticAll(
 103201                Collider,
 103202                displacement,
 103203                PhysicsLayerMask.All,
 103204                _continuousCollisionHits,
 103205                Collider,
 103206                includeTriggers: false);
 207        }
 208
 6530209        hitsAreShapeExact = false;
 6530210        return Context.Query3D.SweepSphereAgainstStaticAll(
 6530211            startPosition,
 6530212            proposedPosition,
 6530213            proxyRadius,
 6530214            PhysicsLayerMask.All,
 6530215            _continuousCollisionHits,
 6530216            Collider,
 6530217            includeTriggers: false);
 218    }
 219
 220    private bool TryGetFirstValidMixedContinuousCollisionHit(
 221        Vector3d startPosition,
 222        Vector3d proposedPosition,
 223        int hitCount,
 224        out PhysicsMixedHit hit)
 225    {
 6633226        Vector3d displacement = proposedPosition - startPosition;
 13274227        for (int i = 0; i < hitCount; i++)
 228        {
 7229            PhysicsMixedHit candidate = _continuousMixedCollisionHits[i];
 7230            if (!IsValidMixedContinuousCollisionHit(candidate.Collider2D!)
 7231                || !IsClosingContinuousCollisionHit(displacement, candidate.NormalFor3DSource))
 232                continue;
 233
 3234            hit = candidate;
 3235            return true;
 236        }
 237
 6630238        hit = default;
 6630239        return false;
 240    }
 241
 242    private bool TryRefineShapeExactContinuousCollisionHit(
 243        Physics3DHit candidate,
 244        Vector3d displacement,
 245        Vector3d direction,
 246        out Physics3DHit refined,
 247        out bool exactSupported)
 248    {
 6316249        refined = default;
 6316250        exactSupported = false;
 251
 6316252        if (Collider is LSSphereCollider)
 6313253            return false;
 254
 3255        if (candidate.Collider is not LSSphereCollider targetSphere)
 1256            return false;
 257
 2258        return TryRefineContinuousCollisionAgainstTargetSphere(targetSphere, displacement, direction, out refined, out e
 259    }
 260
 261    private bool TryRefineContinuousCollisionAgainstTargetSphere(
 262        LSSphereCollider targetSphere,
 263        Vector3d displacement,
 264        Vector3d direction,
 265        out Physics3DHit refined,
 266        out bool exactSupported)
 267    {
 36268        exactSupported = true;
 36269        refined = default;
 270
 36271        Vector3d reverseStart = targetSphere.Center;
 36272        Vector3d reverseEnd = targetSphere.Center - displacement;
 36273        _shapeExactContinuousSweepWorker.Prepare(reverseStart, reverseEnd, targetSphere.ScaledRadius);
 36274        if (!_shapeExactContinuousSweepWorker.TrySweep(Collider, out Vector3d reverseCenterAtImpact, out Fixed64 distanc
 32275            return false;
 276
 4277        _ = ContinuousCollisionContactPolicy.TryResolveSweptSphereContact(
 4278            Collider,
 4279            reverseCenterAtImpact,
 4280            -direction,
 4281            out _,
 4282            out Vector3d sourceSurfaceNormal);
 283
 4284        Vector3d normal = -sourceSurfaceNormal;
 4285        refined = new Physics3DHit(
 4286            targetSphere,
 4287            new ContactAnchor(
 4288                targetSphere.Center,
 4289                normal * targetSphere.ScaledRadius),
 4290            normal,
 4291            distance,
 4292            direction);
 4293        return true;
 294    }
 295
 296    private static Physics3DHit ApplyShapeExactContinuousContactSlop(Physics3DHit hit)
 297    {
 12298        Fixed64 distance = hit.Distance > ShapeExactContinuousContactSlop
 12299            ? hit.Distance - ShapeExactContinuousContactSlop
 12300            : Fixed64.Zero;
 12301        return new Physics3DHit(hit.Collider, hit.Anchor, hit.Normal, distance, hit.Direction);
 302    }
 303
 304    private void PrepareExactConvexSourceSweep(Vector3d displacement)
 305    {
 4306        switch (Collider)
 307        {
 308            case LSMeshCollider mesh:
 1309                _shapeExactContinuousConvexSweepWorker.PrepareConvexMeshSource(mesh, displacement);
 1310                return;
 311            case LSCompoundCollider compound:
 1312                _shapeExactContinuousConvexSweepWorker.PrepareCompoundSource(compound, displacement);
 1313                return;
 314            default:
 2315                _shapeExactContinuousConvexSweepWorker.PreparePrimitiveSource(Collider, displacement);
 2316                return;
 317        }
 318    }
 319
 320    private static bool IsExactConvexSourceSupported(LSCollider collider)
 321    {
 132322        return collider switch
 132323        {
 2324            LSCapsuleCollider => true,
 94325            LSCuboidCollider => true,
 2326            LSCylinderCollider => true,
 2327            LSConeCollider => true,
 6328            LSMeshCollider { Mode: MeshColliderMode.Convex } => true,
 5329            LSCompoundCollider => true,
 21330            _ => false
 132331        };
 332    }
 333
 334    private static bool ContinuousCollisionHitComesBefore(Physics3DHit left, Physics3DHit right)
 335    {
 32336        int distanceCompare = left.Distance.CompareTo(right.Distance);
 32337        if (distanceCompare != 0)
 31338            return distanceCompare < 0;
 339
 1340        return left.Collider!.Id < right.Collider!.Id;
 341    }
 342
 343    private bool TryGetFirstDynamicContinuousCollisionHit(
 344        Vector3d startPosition,
 345        Vector3d proposedPosition,
 346        Fixed64 proxyRadius,
 347        Fixed64 elapsedFrameFraction,
 348        out Physics3DHit hit,
 349        out Fixed64 closingSpeed)
 350    {
 431351        hit = default;
 431352        closingSpeed = Fixed64.Zero;
 353
 431354        Vector3d sourceDisplacement = proposedPosition - startPosition;
 431355        Fixed64 sourceLength = sourceDisplacement.Magnitude;
 431356        bool found = false;
 431357        Physics3DHit best = default;
 431358        Fixed64 bestClosingSpeed = Fixed64.Zero;
 431359        int token = Context.LateSimulateToken;
 431360        SwiftList<int> candidateIds = Context.Physics.QueryContinuousCollisionCandidates(
 431361            DynamicCcdCandidateIndex.CreateSweptSphereBounds(startPosition, sourceDisplacement, proxyRadius));
 2126362        for (int candidateIndex = 0; candidateIndex < candidateIds.Count; candidateIndex++)
 363        {
 632364            int dynamicId = candidateIds[candidateIndex];
 632365            SolidBody target = Context.Physics.GetContinuousCollisionCandidate(dynamicId);
 632366            if (!IsEligibleDynamicContinuousCollisionTarget(target))
 367            {
 368                continue;
 369            }
 370
 95371            target.EnsureContinuousCollisionFramePrepared(token);
 95372            if (!TryGetDynamicRelativeContinuousCollisionHit(
 95373                    target,
 95374                    startPosition,
 95375                    sourceDisplacement,
 95376                    proxyRadius,
 95377                    sourceLength,
 95378                    elapsedFrameFraction,
 95379                    out Physics3DHit candidate,
 95380                    out Fixed64 candidateClosingSpeed))
 381                continue;
 382
 57383            if (!ContinuousCollisionCandidateOrdering.ShouldReplaceHit(candidate, candidateClosingSpeed, true, found, be
 384                continue;
 385
 50386            best = candidate;
 50387            bestClosingSpeed = candidateClosingSpeed;
 50388            found = true;
 389        }
 390
 431391        hit = best;
 431392        closingSpeed = bestClosingSpeed;
 431393        return found;
 394    }
 395
 396    internal bool TryGetDynamicRelativeContinuousCollisionHit(
 397        SolidBody target,
 398        Vector3d sourceStart,
 399        Vector3d sourceDisplacement,
 400        Fixed64 sourceRadius,
 401        Fixed64 sourceLength,
 402        Fixed64 queryStartFraction,
 403        out Physics3DHit hit,
 404        out Fixed64 closingSpeed)
 405    {
 111406        hit = default;
 111407        closingSpeed = Fixed64.Zero;
 111408        Vector3d sourceEnd = sourceStart + sourceDisplacement;
 111409        Vector3d sourceDirection = sourceDisplacement.Normalized;
 111410        Fixed64 targetRadius = target.ResolveContinuousCollisionProxyRadius();
 111411        bool exactSupported = Collider is LSSphereCollider
 111412            || target.Collider is LSSphereCollider
 111413            || IsExactConvexSourceSupported(Collider);
 111414        bool found = false;
 111415        Physics3DHit best = default;
 111416        Fixed64 bestClosingSpeed = Fixed64.Zero;
 417
 111418        Vector3d originalSourcePosition = Position3d;
 111419        FixedQuaternion originalSourceRotation = Rotation;
 111420        bool originalSourcePositionMutated = _positionMutated;
 111421        bool originalSourceRotationMutated = _rotationMutated;
 111422        Vector3d originalTargetPosition = target.Position3d;
 111423        FixedQuaternion originalTargetRotation = target.Rotation;
 111424        bool originalTargetPositionMutated = target._positionMutated;
 111425        bool originalTargetRotationMutated = target._rotationMutated;
 426
 427        try
 428        {
 111429            target.GetContinuousCollisionTrajectorySegmentRange(
 111430                queryStartFraction,
 111431                out int segmentStartIndex,
 111432                out int segmentEndExclusive);
 111433            for (int segmentIndex = segmentStartIndex;
 158434                segmentIndex < segmentEndExclusive;
 47435                segmentIndex++)
 436            {
 118437                ContinuousCollisionMotionSegment3D segment =
 118438                    target.GetContinuousCollisionTrajectorySegment(segmentIndex);
 118439                ContinuousCollisionMath.ClipTranslationalTrajectoryInterval(
 118440                    queryStartFraction,
 118441                    segment.StartFraction,
 118442                    segment.EndFraction,
 118443                    out Fixed64 overlapStart,
 118444                    out Fixed64 overlapEnd,
 118445                    out Fixed64 sourceStartTime,
 118446                    out Fixed64 sourceEndTime);
 447
 118448                Vector3d sourceSegmentStart = Vector3d.Lerp(
 118449                    sourceStart,
 118450                    sourceEnd,
 118451                    sourceStartTime);
 118452                Vector3d sourceSegmentEnd = Vector3d.Lerp(
 118453                    sourceStart,
 118454                    sourceEnd,
 118455                    sourceEndTime);
 118456                Vector3d sourceSegmentDisplacement =
 118457                    ContinuousCollisionSweepRange.ValidateEndpoint(
 118458                        sourceSegmentStart,
 118459                        sourceSegmentEnd,
 118460                        out Fixed64 sourceSegmentLength);
 118461                Vector3d targetStart = segment.SamplePosition(overlapStart);
 118462                Vector3d targetEnd = segment.SamplePosition(overlapEnd);
 118463                Vector3d targetDisplacement =
 118464                    ContinuousCollisionSweepRange.ValidateEndpoint(
 118465                        targetStart,
 118466                        targetEnd,
 118467                        out _);
 118468                LSSphereCollider? radialSourceSphere = Collider as LSSphereCollider;
 118469                LSSphereCollider? radialTargetSphere = target.Collider as LSSphereCollider;
 118470                bool radialPair = radialSourceSphere is not null
 118471                    && radialTargetSphere is not null;
 118472                Fixed64 radialEntryDistance = default;
 118473                Vector3d relativeDisplacement = default;
 118474                Fixed64 relativeLength = default;
 118475                Vector3d normal = default;
 118476                if (!radialPair
 118477                    && !ContinuousCollisionMath.TryGetRelativeSphereOverlapDistanceInterval(
 118478                            sourceSegmentStart,
 118479                            sourceSegmentDisplacement,
 118480                            sourceRadius,
 118481                            targetStart,
 118482                            targetDisplacement,
 118483                            targetRadius,
 118484                            out radialEntryDistance,
 118485                            out _,
 118486                            out relativeDisplacement,
 118487                            out relativeLength,
 118488                            out normal,
 118489                            out _))
 490                {
 491                    continue;
 492                }
 493
 118494                Position3d = sourceSegmentStart;
 118495                target.Position3d = targetStart;
 118496                target.Rotation = target.SampleContinuousCollisionRotation(
 118497                    overlapStart);
 118498                Collider.RebuildRuntimeShapeOnly(refreshMassProperties: false);
 118499                target.Collider.RebuildRuntimeShapeOnly(refreshMassProperties: false);
 500
 118501                Physics3DHit relativeHit = default;
 118502                bool foundExact = false;
 118503                if (radialPair)
 504                {
 72505                    foundExact = ContinuousCollisionMath
 72506                        .TryGetRelativeSphereOverlapDistanceInterval(
 72507                            radialSourceSphere!.Center,
 72508                            sourceSegmentDisplacement,
 72509                            radialSourceSphere.ScaledRadius,
 72510                            radialTargetSphere!.Center,
 72511                            targetDisplacement,
 72512                            radialTargetSphere.ScaledRadius,
 72513                            out radialEntryDistance,
 72514                            out _,
 72515                            out relativeDisplacement,
 72516                            out relativeLength,
 72517                            out normal,
 72518                            out _);
 519                }
 520
 118521                Vector3d relativeDirection = relativeDisplacement.Normalized;
 118522                if (radialPair && foundExact)
 523                {
 61524                    relativeHit = new Physics3DHit(
 61525                        radialTargetSphere!,
 61526                        new ContactAnchor(
 61527                            radialTargetSphere!.Center,
 61528                            normal * radialTargetSphere.ScaledRadius),
 61529                        normal,
 61530                        radialEntryDistance,
 61531                        relativeDirection);
 532                }
 57533                else if (!radialPair && Collider is LSSphereCollider sourceSphere)
 534                {
 6535                    foundExact = TrySweepRelativeSourceSphere(
 6536                        sourceSphere,
 6537                        target.Collider,
 6538                        relativeDisplacement,
 6539                        relativeDirection,
 6540                        out relativeHit);
 541                }
 51542                else if (!radialPair && target.Collider is LSSphereCollider targetSphere)
 543                {
 34544                    foundExact = TryRefineContinuousCollisionAgainstTargetSphere(
 34545                        targetSphere,
 34546                        relativeDisplacement,
 34547                        relativeDirection,
 34548                        out relativeHit,
 34549                        out _);
 550                }
 17551                else if (IsExactConvexSourceSupported(Collider))
 552                {
 4553                    PrepareExactConvexSourceSweep(relativeDisplacement);
 4554                    foundExact = _shapeExactContinuousConvexSweepWorker
 4555                        .TrySweepPreparedSource(
 4556                            target.Collider,
 4557                            out Physics3DHit convexHit);
 4558                    if (foundExact)
 3559                        relativeHit = ApplyShapeExactContinuousContactSlop(convexHit);
 560                }
 561
 118562                if (exactSupported && !foundExact)
 563                    continue;
 564
 74565                Fixed64 contactDistance = radialEntryDistance;
 74566                if (foundExact)
 567                {
 72568                    contactDistance = relativeHit.Distance;
 72569                    normal = relativeHit.Normal;
 570                }
 571
 74572                Fixed64 successorStart = segmentIndex + 1
 74573                    < target.ContinuousCollisionTrajectoryCount
 74574                    ? target.GetContinuousCollisionTrajectorySegment(segmentIndex + 1)
 74575                        .StartFraction
 74576                    : Fixed64.Zero;
 74577                if (ContinuousCollisionMath.IsSupersededTranslationalBoundaryHit(
 74578                        contactDistance >= relativeLength,
 74579                        overlapEnd,
 74580                        segmentIndex,
 74581                        target.ContinuousCollisionTrajectoryCount,
 74582                        successorStart))
 583                {
 584                    continue;
 585                }
 586
 73587                Fixed64 localClosingSpeed =
 73588                    -Vector3d.Dot(relativeDisplacement, normal);
 73589                if (!ContinuousCollisionMath.TryNormalizeTranslationalClosingSpeed(
 73590                        localClosingSpeed,
 73591                        sourceEndTime - sourceStartTime,
 73592                        out Fixed64 candidateClosingSpeed))
 593                    continue;
 594
 71595                Vector3d sourceCenter = new FixedSegment(
 71596                    sourceSegmentStart,
 71597                    sourceSegmentEnd).GetPointAtDistance(
 71598                        contactDistance,
 71599                        relativeLength);
 71600                Vector3d targetCenter = new FixedSegment(
 71601                    targetStart,
 71602                    targetEnd).GetPointAtDistance(
 71603                        contactDistance,
 71604                        relativeLength);
 605                ContactAnchor anchor;
 71606                if (foundExact
 71607                    && relativeHit.Anchor.TryGetOffsetFrom(
 71608                        target.Collider.Center,
 71609                        out Vector3d targetOffset))
 610                {
 69611                    anchor = new ContactAnchor(targetCenter, targetOffset);
 612                }
 613                else
 614                {
 2615                    anchor = ContactAnchor.FromWorldPoint(
 2616                        ContinuousCollisionMath.ResolveContactPointOnTarget(
 2617                            sourceCenter,
 2618                            targetCenter,
 2619                            normal,
 2620                            targetRadius));
 621                }
 71622                if (!Fixed64.TryMultiplyDivide(
 71623                        sourceSegmentLength,
 71624                        contactDistance,
 71625                        relativeLength,
 71626                        out Fixed64 localSourceDistance)
 71627                    || !Fixed64.TryMultiplyAdd(
 71628                        sourceLength,
 71629                        sourceStartTime,
 71630                        localSourceDistance,
 71631                        out Fixed64 sourceDistance))
 632                {
 633                    continue;
 634                }
 71635                var candidate = new Physics3DHit(
 71636                    target.Collider,
 71637                    anchor,
 71638                    normal,
 71639                    sourceDistance,
 71640                    sourceDirection);
 71641                best = candidate;
 71642                bestClosingSpeed = candidateClosingSpeed;
 71643                found = true;
 71644                break;
 645            }
 40646        }
 647        finally
 648        {
 111649            Position3d = originalSourcePosition;
 111650            Rotation = originalSourceRotation;
 111651            target.Position3d = originalTargetPosition;
 111652            target.Rotation = originalTargetRotation;
 111653            target.Collider.RebuildRuntimeShapeOnly(refreshMassProperties: false);
 111654            Collider.RebuildRuntimeShapeOnly(refreshMassProperties: false);
 111655            target._positionMutated = originalTargetPositionMutated;
 111656            target._rotationMutated = originalTargetRotationMutated;
 111657            _positionMutated = originalSourcePositionMutated;
 111658            _rotationMutated = originalSourceRotationMutated;
 111659        }
 660
 111661        hit = best;
 111662        closingSpeed = bestClosingSpeed;
 111663        return found;
 664    }
 665
 666    private bool TrySweepRelativeSourceSphere(
 667        LSSphereCollider sourceSphere,
 668        LSCollider target,
 669        Vector3d relativeDisplacement,
 670        Vector3d relativeDirection,
 671        out Physics3DHit hit)
 672    {
 6673        hit = default;
 6674        _shapeExactContinuousSweepWorker.Prepare(
 6675            sourceSphere.Center,
 6676            sourceSphere.Center + relativeDisplacement,
 6677            sourceSphere.ScaledRadius);
 6678        if (!_shapeExactContinuousSweepWorker.TrySweep(target, out Vector3d sphereCenterAtImpact, out Fixed64 distance))
 1679            return false;
 680
 5681        _ = ContinuousCollisionContactPolicy.TryResolveSweptSphereContact(
 5682            target,
 5683            sphereCenterAtImpact,
 5684            relativeDirection,
 5685            out ContactAnchor anchor,
 5686            out Vector3d normal);
 687
 5688        hit = new Physics3DHit(target, anchor, normal, distance, relativeDirection);
 5689        return true;
 690    }
 691
 692    private bool TryGetFirstDynamicMixedContinuousCollisionHit(
 693        Vector3d startPosition,
 694        Vector3d proposedPosition,
 695        Fixed64 proxyRadius,
 696        Fixed64 elapsedFrameFraction,
 697        out DynamicMixedIntervalHit hit)
 698    {
 431699        hit = default;
 700
 431701        if (!Context.Settings.RuntimeMode.RunsMixedContacts())
 324702            return false;
 703
 107704        Vector3d sourceDisplacement = proposedPosition - startPosition;
 107705        Fixed64 sourceLength = sourceDisplacement.Magnitude;
 107706        Vector3d sourceDirection = sourceDisplacement.Normalized;
 107707        bool found = false;
 107708        DynamicMixedIntervalHit best = default;
 107709        int token = Context.LateSimulateToken;
 107710        SwiftList<int> candidateIds = Context.Physics2D.QueryMixedContinuousCollisionCandidates(
 107711            DynamicCcdCandidateIndex.CreateSweptSphereBounds(startPosition, sourceDisplacement, proxyRadius));
 408712        for (int candidateIndex = 0; candidateIndex < candidateIds.Count; candidateIndex++)
 713        {
 97714            int dynamicId = candidateIds[candidateIndex];
 97715            SolidBody2D target = Context.Physics2D.GetContinuousCollisionCandidate(dynamicId);
 97716            if (!IsEligibleDynamicMixed2DTarget(target))
 717            {
 718                continue;
 719            }
 720
 29721            target.EnsureContinuousCollisionFramePrepared(token);
 29722            Fixed64 targetRadius =
 29723                target.ResolveMixedContinuousCollisionProxyRadius();
 29724            ContinuousCollisionMath.IntervalSearchStatus status =
 29725                TryGetDynamicMixed2DContinuousCollisionHit(
 29726                    target,
 29727                    startPosition,
 29728                    sourceDisplacement,
 29729                    proxyRadius,
 29730                    targetRadius,
 29731                    sourceLength,
 29732                    elapsedFrameFraction,
 29733                    out DynamicMixedIntervalHit candidate);
 29734            if (status
 29735                == ContinuousCollisionMath.IntervalSearchStatus.CertifiedNoHit)
 736            {
 737                continue;
 738            }
 739
 27740            best = DynamicMixedIntervalHit.Select(
 27741                candidate,
 27742                best,
 27743                ref found);
 744        }
 745
 107746        hit = best;
 107747        return found;
 748    }
 749
 750    private ContinuousCollisionMath.IntervalSearchStatus
 751        TryGetDynamicMixed2DContinuousCollisionHit(
 752        SolidBody2D target,
 753        Vector3d sourceStart,
 754        Vector3d sourceDisplacement,
 755        Fixed64 sourceRadius,
 756        Fixed64 targetRadius,
 757        Fixed64 sourceLength,
 758        Fixed64 queryStartFraction,
 759        out DynamicMixedIntervalHit hit)
 760    {
 49761        hit = default;
 49762        Vector3d sourceEnd = sourceStart + sourceDisplacement;
 49763        Vector3d sourceDirection = sourceDisplacement.Normalized;
 49764        target.GetContinuousCollisionTrajectorySegmentRange(
 49765            queryStartFraction,
 49766            out int segmentStartIndex,
 49767            out int segmentEndExclusive);
 49768        for (int segmentIndex = segmentStartIndex;
 59769            segmentIndex < segmentEndExclusive;
 10770            segmentIndex++)
 771        {
 52772            ContinuousCollisionMotionSegment2D segment =
 52773                target.GetContinuousCollisionTrajectorySegment(segmentIndex);
 52774            ContinuousCollisionMath.ClipTranslationalTrajectoryInterval(
 52775                queryStartFraction,
 52776                segment.StartFraction,
 52777                segment.EndFraction,
 52778                out Fixed64 overlapStart,
 52779                out Fixed64 overlapEnd,
 52780                out Fixed64 sourceStartTime,
 52781                out Fixed64 sourceEndTime);
 782
 52783            Vector3d sourceSegmentStart = Vector3d.Lerp(
 52784                sourceStart,
 52785                sourceEnd,
 52786                sourceStartTime);
 52787            Vector3d sourceSegmentEnd = Vector3d.Lerp(
 52788                sourceStart,
 52789                sourceEnd,
 52790                sourceEndTime);
 52791            Vector3d sourceSegmentDisplacement =
 52792                ContinuousCollisionSweepRange.ValidateEndpoint(
 52793                    sourceSegmentStart,
 52794                    sourceSegmentEnd,
 52795                    out Fixed64 sourceSegmentLength);
 52796            Vector2d targetStart2D = segment.SamplePosition(overlapStart);
 52797            Vector2d targetEnd2D = segment.SamplePosition(overlapEnd);
 52798            Vector2d targetDisplacement2D =
 52799                ContinuousCollisionSweepRange.ValidateEndpoint(
 52800                    targetStart2D,
 52801                    targetEnd2D,
 52802                    out Fixed64 targetSegmentLength);
 52803            Vector3d targetStart = new(
 52804                targetStart2D.X,
 52805                target.Collider.MixedSlabCenterY,
 52806                targetStart2D.Y);
 52807            Vector3d targetEnd = new(
 52808                targetEnd2D.X,
 52809                target.Collider.MixedSlabCenterY,
 52810                targetEnd2D.Y);
 52811            Vector3d targetDisplacement = new(
 52812                targetDisplacement2D.X,
 52813                Fixed64.Zero,
 52814                targetDisplacement2D.Y);
 52815            if (!ContinuousCollisionMath.TryGetRelativeSphereOverlapDistanceInterval(
 52816                    sourceSegmentStart,
 52817                    sourceSegmentDisplacement,
 52818                    sourceRadius,
 52819                    targetStart,
 52820                    targetDisplacement,
 52821                    targetRadius,
 52822                    out Fixed64 entryDistance,
 52823                    out Fixed64 exitDistance,
 52824                    out Vector3d relativeDisplacement,
 52825                    out Fixed64 relativeLength,
 52826                    out _,
 52827                    out _))
 828            {
 829                continue;
 830            }
 831
 49832            Fixed64 successorStart = segmentIndex + 1
 49833                < target.ContinuousCollisionTrajectoryCount
 49834                ? target.GetContinuousCollisionTrajectorySegment(segmentIndex + 1)
 49835                    .StartFraction
 49836                : Fixed64.Zero;
 49837            if (ContinuousCollisionMath.IsSupersededTranslationalBoundaryHit(
 49838                    entryDistance >= relativeLength,
 49839                    overlapEnd,
 49840                    segmentIndex,
 49841                    target.ContinuousCollisionTrajectoryCount,
 49842                    successorStart))
 843            {
 844                continue;
 845            }
 846
 49847            FixedQuaternion sourceStartRotation =
 49848                SampleContinuousCollisionRotation(overlapStart);
 49849            FixedQuaternion sourceEndRotation =
 49850                SampleContinuousCollisionRotation(overlapEnd);
 49851            Fixed64 angularDistance =
 49852                ResolveKinematicAngularDistanceRadians(
 49853                    sourceStartRotation,
 49854                    sourceEndRotation);
 49855            Fixed64 intervalElapsedTime =
 49856                overlapStart * Context.DeltaTime;
 49857            Fixed64 intervalDuration =
 49858                (overlapEnd - overlapStart) * Context.DeltaTime;
 49859            bool foundExactContact =
 49860                TryGetExactSphereCircleTranslationalContact(
 49861                    target,
 49862                    segment,
 49863                    new FixedSegment2d(targetStart2D, targetEnd2D),
 49864                    targetDisplacement2D,
 49865                    targetSegmentLength,
 49866                    new FixedSegment(sourceSegmentStart, sourceSegmentEnd),
 49867                    sourceSegmentDisplacement,
 49868                    sourceSegmentLength,
 49869                    sourceStartRotation,
 49870                    angularDistance,
 49871                    out bool requiresRotationalFallback,
 49872                    out Fixed64 exactRelativeDistance,
 49873                    out Fixed64 sourceContactDistance,
 49874                    out Fixed64 exactLocalClosingSpeed,
 49875                    out MixedContact exactContact);
 49876            if (foundExactContact)
 877            {
 28878                if (ContinuousCollisionMath.IsSupersededTranslationalBoundaryHit(
 28879                        exactRelativeDistance >= relativeLength,
 28880                        overlapEnd,
 28881                        segmentIndex,
 28882                        target.ContinuousCollisionTrajectoryCount,
 28883                        successorStart))
 884                {
 885                    continue;
 886                }
 887
 26888                if (!ContinuousCollisionMath.TryNormalizeTranslationalClosingSpeed(
 26889                        exactLocalClosingSpeed,
 26890                        sourceEndTime - sourceStartTime,
 26891                        out Fixed64 exactClosingSpeed))
 892                {
 893                    continue;
 894                }
 895
 26896                if (!Fixed64.TryMultiplyAdd(
 26897                        sourceLength,
 26898                        sourceStartTime,
 26899                        sourceContactDistance,
 26900                        out Fixed64 exactSourceDistance))
 901                {
 902                    continue;
 903                }
 904
 26905                var analyticHit = new PhysicsMixedHit(
 26906                    null,
 26907                    target.Collider,
 26908                    exactContact.Anchor3D,
 26909                    exactContact.Anchor2D,
 26910                    exactContact.Normal3DTo2D,
 26911                    PhysicsQueryReducerKind.Exact,
 26912                    exactSourceDistance,
 26913                    sourceDirection);
 26914                hit = new DynamicMixedIntervalHit(
 26915                    ContinuousCollisionMath.IntervalSearchStatus.ExactHit,
 26916                    analyticHit,
 26917                    analyticHit.Distance,
 26918                    exactClosingSpeed,
 26919                    target.Collider.Id);
 26920                return ContinuousCollisionMath.IntervalSearchStatus.ExactHit;
 921            }
 21922            if (!requiresRotationalFallback)
 923                continue;
 924
 18925            Fixed64 entryTime = entryDistance / relativeLength;
 18926            Fixed64 exitTime = exitDistance / relativeLength;
 927
 18928            bool searchFound =
 18929                TryFindEarliestMixedRotationalContinuousCollisionAgainstTarget(
 18930                    target.Collider,
 18931                    sourceSegmentStart,
 18932                    sourceSegmentDisplacement,
 18933                    sourceStartRotation,
 18934                    sourceEndRotation,
 18935                    angularDistance,
 18936                    sourceRadius,
 18937                    intervalElapsedTime,
 18938                    intervalDuration,
 18939                    IsKinematic,
 18940                    entryTime,
 18941                    exitTime,
 18942                    out Fixed64 safeTime,
 18943                    out MixedContact contact,
 18944                    out bool hasContact,
 18945                    out Fixed64 contactTime);
 18946            if (!searchFound)
 947            {
 948                continue;
 949            }
 950
 16951            bool closingResolved = Vector3d.TryDot(
 16952                relativeDisplacement,
 16953                contact.Normal3DTo2D,
 16954                out Fixed64 contactClosingSpeed);
 16955            bool hasOnlyTranslationalMotion =
 16956                angularDistance <= Fixed64.Epsilon
 16957                & segment.AngularDistance <= Fixed64.Epsilon;
 16958            bool useContactClosingSpeed = hasContact & hasOnlyTranslationalMotion;
 16959            if (useContactClosingSpeed
 16960                & (!closingResolved
 16961                    | contactClosingSpeed <= Fixed64.Epsilon))
 962            {
 963                continue;
 964            }
 16965            Fixed64 candidateClosingSpeed =
 16966                (useContactClosingSpeed
 16967                    ? contactClosingSpeed
 16968                    : relativeLength)
 16969                / (sourceEndTime - sourceStartTime);
 970
 16971            Fixed64 safeSourceTime = FixedMath.Lerp(
 16972                sourceStartTime,
 16973                sourceEndTime,
 16974                safeTime);
 16975            bool exactHit = hasContact & contactTime == safeTime;
 16976            PhysicsMixedHit exact = exactHit
 16977                ? new PhysicsMixedHit(
 16978                    null,
 16979                    target.Collider,
 16980                    contact.Anchor3D,
 16981                    contact.Anchor2D,
 16982                    contact.Normal3DTo2D,
 16983                    PhysicsQueryReducerKind.ConservativeFallback,
 16984                    sourceLength * safeSourceTime,
 16985                    sourceDirection)
 16986                : default;
 16987            ContinuousCollisionMath.IntervalSearchStatus status = exactHit
 16988                ? ContinuousCollisionMath.IntervalSearchStatus.ExactHit
 16989                : ContinuousCollisionMath.IntervalSearchStatus.Unresolved;
 16990            hit = new DynamicMixedIntervalHit(
 16991                status,
 16992                exact,
 16993                sourceLength * safeSourceTime,
 16994                candidateClosingSpeed,
 16995                target.Collider.Id);
 16996            return status;
 997        }
 998
 7999        return ContinuousCollisionMath.IntervalSearchStatus.CertifiedNoHit;
 1000    }
 1001
 1002}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.ContinuousCollision.Kinematic.cs

#LineLine coverage
 1//=======================================================================
 2// SolidBody.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 partial class SolidBody
 17{
 18    private bool TryResolveKinematicContinuousCollision(Vector3d startPosition, ref Vector3d proposedPosition)
 19    {
 622120        if (!ShouldUseContinuousCollision(out ContinuousCollisionMode mode))
 1321            return false;
 22
 620823        Vector3d displacement = ContinuousCollisionSweepRange.ValidateEndpoint(
 620824            startPosition,
 620825            proposedPosition,
 620826            out Fixed64 sourceLength);
 620827        Fixed64 displacementMagnitudeSquared = displacement.MagnitudeSquared;
 620828        if (displacementMagnitudeSquared <= Fixed64.Epsilon)
 329            return false;
 30
 620531        Fixed64 proxyRadius = ResolveContinuousCollisionProxyRadius();
 620532        if (proxyRadius <= Fixed64.Epsilon
 620533            || (mode == ContinuousCollisionMode.Auto
 620534                && ContinuousCollisionMath.IsWithinProxyRadius(displacement, displacementMagnitudeSquared, proxyRadius))
 35        {
 336            return false;
 37        }
 38
 620239        bool foundStatic = TryGetFirstKinematicStaticContinuousCollisionHit(
 620240            startPosition,
 620241            proposedPosition,
 620242            proxyRadius,
 620243            out Fixed64 staticHitDistance);
 620244        Fixed64 dynamicPushLimit = foundStatic ? staticHitDistance : sourceLength;
 620245        bool pushedDynamic = TryApplyKinematicDynamicContinuousCollisionPushes(
 620246            startPosition,
 620247            proposedPosition,
 620248            proxyRadius,
 620249            dynamicPushLimit,
 620250            sourceLength,
 620251            out bool hasUnresolvedMixedLimit,
 620252            out Fixed64 unresolvedMixedDistance);
 620253        staticHitDistance = FixedMath.Min(
 620254            dynamicPushLimit,
 620255            unresolvedMixedDistance);
 620256        foundStatic |= hasUnresolvedMixedLimit;
 620257        if (!foundStatic && !pushedDynamic)
 1358            return false;
 59
 618960        LastContinuousCollisionToiIterationCount++;
 618961        if (foundStatic)
 617862            proposedPosition = startPosition + displacement.Normalized * staticHitDistance;
 63
 618964        return true;
 65    }
 66
 67    private bool TryGetFirstKinematicStaticContinuousCollisionHit(
 68        Vector3d startPosition,
 69        Vector3d proposedPosition,
 70        Fixed64 proxyRadius,
 71        out Fixed64 distance)
 72    {
 620273        Vector3d originalPosition = Position3d;
 620274        bool originalPositionMutated = _positionMutated;
 75        try
 76        {
 620277            Position3d = startPosition;
 620278            Collider.RebuildRuntimeShapeOnly(refreshMassProperties: false);
 79
 620280            int hitCount = QueryStaticContinuousCollisionHits(
 620281                startPosition,
 620282                proposedPosition,
 620283                proxyRadius,
 620284                out bool staticHitsAreShapeExact);
 620285            int mixedHitCount = Context.Settings.RuntimeMode.RunsMixedContacts()
 620286                ? Context.QueryMixed.SweepSphereAgainstStatic2DAll(
 620287                    startPosition,
 620288                    proposedPosition,
 620289                    proxyRadius,
 620290                    PhysicsLayerMask.All,
 620291                    _continuousMixedCollisionHits,
 620292                    Collider,
 620293                    includeTriggers: false,
 620294                    cacheTargetPartitions: true)
 620295                : 0;
 96
 620297            bool found3D = TryGetFirstValidContinuousCollisionHit(
 620298                startPosition,
 620299                proposedPosition,
 6202100                hitCount,
 6202101                staticHitsAreShapeExact,
 6202102                out Physics3DHit hit3D);
 6202103            bool foundMixed = TryGetFirstValidMixedContinuousCollisionHit(startPosition, proposedPosition, mixedHitCount
 6202104            if (found3D
 6202105                && (!foundMixed
 6202106                    || !ContinuousCollisionCandidateOrdering.Is2DHitFirst(
 6202107                        hitMixed.Distance,
 6202108                        hit3D.Distance)))
 109            {
 6176110                distance = hit3D.Distance;
 6176111                return true;
 112            }
 113
 26114            if (foundMixed)
 115            {
 1116                distance = hitMixed.Distance;
 1117                return true;
 118            }
 25119        }
 120        finally
 121        {
 6202122            Position3d = originalPosition;
 6202123            Collider.RebuildRuntimeShapeOnly(refreshMassProperties: false);
 6202124            _positionMutated = originalPositionMutated;
 6202125        }
 126
 25127        distance = Fixed64.Zero;
 25128        return false;
 6177129    }
 130
 131    private bool TryApplyKinematicDynamicContinuousCollisionPushes(
 132        Vector3d startPosition,
 133        Vector3d proposedPosition,
 134        Fixed64 proxyRadius,
 135        Fixed64 maxDistance,
 136        Fixed64 sourceLength,
 137        out bool hasUnresolvedMixedLimit,
 138        out Fixed64 unresolvedMixedDistance)
 139    {
 6202140        hasUnresolvedMixedLimit = false;
 6202141        unresolvedMixedDistance = maxDistance;
 6202142        _continuousCollisionHits.FastClear();
 6202143        _continuousMixedCollisionHits.FastClear();
 6202144        GatherKinematicDynamic3DContinuousCollisionHits(
 6202145            startPosition,
 6202146            proposedPosition,
 6202147            proxyRadius,
 6202148            maxDistance,
 6202149            sourceLength);
 6202150        GatherKinematicDynamicMixed2DContinuousCollisionHits(
 6202151            startPosition,
 6202152            proposedPosition,
 6202153            proxyRadius,
 6202154            maxDistance,
 6202155            sourceLength,
 6202156            ref hasUnresolvedMixedLimit,
 6202157            ref unresolvedMixedDistance);
 158
 6202159        Physics3DHitSorter.SortByDistance(_continuousCollisionHits);
 6202160        PhysicsMixedHitSorter.SortByDistance(_continuousMixedCollisionHits);
 6202161        Vector3d sourceDisplacement = proposedPosition - startPosition;
 6202162        int hit3DIndex = 0;
 6202163        int hit2DIndex = 0;
 6202164        bool pushed = false;
 6218165        while (hit3DIndex < _continuousCollisionHits.Count
 6218166            || hit2DIndex < _continuousMixedCollisionHits.Count)
 167        {
 16168            bool take2D = hit3DIndex >= _continuousCollisionHits.Count
 16169                || (hit2DIndex < _continuousMixedCollisionHits.Count
 16170                    && _continuousMixedCollisionHits[hit2DIndex].Distance
 16171                        <= _continuousCollisionHits[hit3DIndex].Distance);
 16172            if (take2D)
 173            {
 6174                PhysicsMixedHit hit = _continuousMixedCollisionHits[hit2DIndex++];
 6175                pushed |= ApplyKinematicContinuousCollisionHandoff(
 6176                    hit.Body2D!,
 6177                    sourceDisplacement,
 6178                    hit.NormalFor3DSource,
 6179                    hit.Distance,
 6180                    sourceLength);
 181
 6182                continue;
 183            }
 184
 10185            Physics3DHit hit3D = _continuousCollisionHits[hit3DIndex++];
 10186            pushed |= ApplyKinematicContinuousCollisionHandoff(
 10187                hit3D.Body!,
 10188                sourceDisplacement,
 10189                hit3D.Normal,
 10190                hit3D.Distance,
 10191                sourceLength);
 192        }
 193
 6202194        return pushed;
 195    }
 196
 197    private void GatherKinematicDynamic3DContinuousCollisionHits(
 198        Vector3d startPosition,
 199        Vector3d proposedPosition,
 200        Fixed64 proxyRadius,
 201        Fixed64 maxDistance,
 202        Fixed64 sourceLength)
 203    {
 6202204        Vector3d sourceDisplacement = proposedPosition - startPosition;
 6202205        int token = Context.LateSimulateToken;
 6202206        SwiftList<int> candidateIds = Context.Physics.QueryContinuousCollisionCandidates(
 6202207            DynamicCcdCandidateIndex.CreateSweptSphereBounds(startPosition, sourceDisplacement, proxyRadius));
 24836208        for (int candidateIndex = 0; candidateIndex < candidateIds.Count; candidateIndex++)
 209        {
 6216210            int dynamicId = candidateIds[candidateIndex];
 6216211            SolidBody target = Context.Physics.GetContinuousCollisionCandidate(dynamicId);
 6216212            if (!IsEligibleDynamicContinuousCollisionTarget(target))
 213            {
 214                continue;
 215            }
 216
 13217            target.EnsureContinuousCollisionFramePrepared(token);
 13218            if (!TryGetDynamicRelativeContinuousCollisionHit(
 13219                    target,
 13220                    startPosition,
 13221                    sourceDisplacement,
 13222                    proxyRadius,
 13223                    sourceLength,
 13224                    Fixed64.Zero,
 13225                    out Physics3DHit hit,
 13226                    out _))
 227                continue;
 228
 11229            if (hit.Distance > maxDistance)
 230                continue;
 231
 10232            _continuousCollisionHits.Add(hit);
 233        }
 6202234    }
 235
 236    private void GatherKinematicDynamicMixed2DContinuousCollisionHits(
 237        Vector3d startPosition,
 238        Vector3d proposedPosition,
 239        Fixed64 proxyRadius,
 240        Fixed64 maxDistance,
 241        Fixed64 sourceLength,
 242        ref bool hasUnresolvedMixedLimit,
 243        ref Fixed64 unresolvedMixedDistance)
 244    {
 6202245        if (!Context.Settings.RuntimeMode.RunsMixedContacts())
 6189246            return;
 247
 13248        Vector3d sourceDisplacement = proposedPosition - startPosition;
 13249        int token = Context.LateSimulateToken;
 13250        SwiftList<int> candidateIds = Context.Physics2D.QueryMixedContinuousCollisionCandidates(
 13251            DynamicCcdCandidateIndex.CreateSweptSphereBounds(startPosition, sourceDisplacement, proxyRadius));
 48252        for (int candidateIndex = 0; candidateIndex < candidateIds.Count; candidateIndex++)
 253        {
 11254            int dynamicId = candidateIds[candidateIndex];
 11255            SolidBody2D target = Context.Physics2D.GetContinuousCollisionCandidate(dynamicId);
 11256            if (!IsEligibleDynamicMixed2DTarget(target))
 257            {
 258                continue;
 259            }
 260
 10261            target.EnsureContinuousCollisionFramePrepared(token);
 10262            Fixed64 targetRadius =
 10263                target.ResolveMixedContinuousCollisionProxyRadius();
 10264            ContinuousCollisionMath.IntervalSearchStatus status =
 10265                TryGetDynamicMixed2DContinuousCollisionHit(
 10266                    target,
 10267                    startPosition,
 10268                    sourceDisplacement,
 10269                    proxyRadius,
 10270                    targetRadius,
 10271                    sourceLength,
 10272                    Fixed64.Zero,
 10273                    out DynamicMixedIntervalHit candidate);
 10274            if (status
 10275                    == ContinuousCollisionMath.IntervalSearchStatus.CertifiedNoHit
 10276                || candidate.SafeDistance > maxDistance)
 277            {
 278                continue;
 279            }
 280
 7281            if (status
 7282                == ContinuousCollisionMath.IntervalSearchStatus.Unresolved)
 283            {
 1284                hasUnresolvedMixedLimit = true;
 1285                unresolvedMixedDistance = FixedMath.Min(
 1286                    unresolvedMixedDistance,
 1287                    candidate.SafeDistance);
 1288                continue;
 289            }
 290
 6291            _continuousMixedCollisionHits.Add(candidate.ExactHit);
 292        }
 13293    }
 294
 295    internal bool ApplyKinematicContinuousCollisionHandoff(
 296        SolidBody target,
 297        Vector3d sourceDisplacement,
 298        Vector3d normalForSource,
 299        Fixed64 hitDistance,
 300        Fixed64 sourceLength)
 301    {
 11302        _ = ContinuousCollisionImpulsePolicy.TryResolveSourceNormal(normalForSource, sourceDisplacement, out Vector3d no
 303
 11304        Fixed64 deltaTime = Context.DeltaTime;
 11305        Fixed64 constrainedInverseMass = target.GetConstrainedInverseMass(normal);
 11306        if (constrainedInverseMass <= Fixed64.Zero)
 1307            return false;
 308
 10309        Fixed64 hitTime = FixedMath.Clamp01(hitDistance / sourceLength);
 10310        Vector3d sourceVelocity = SampleContinuousCollisionLinearVelocity(hitTime);
 10311        Vector3d targetVelocity = target.SampleContinuousCollisionLinearVelocity(hitTime);
 10312        bool relativeVelocityResolved = Vector3d.TrySubtract(
 10313            sourceVelocity,
 10314            targetVelocity,
 10315            out Vector3d relativeVelocity);
 10316        Fixed64 normalVelocity = Vector3d.Dot(relativeVelocity, normal);
 10317        Fixed64 restitution = ResolveContinuousCollisionRestitution(target, -normalVelocity);
 10318        bool responseFactorResolved = Fixed64.TryAdd(
 10319            Fixed64.One,
 10320            restitution,
 10321            out Fixed64 responseFactor);
 10322        bool closingSpeedResolved = Fixed64.TrySubtract(
 10323            Fixed64.Zero,
 10324            normalVelocity,
 10325            out Fixed64 closingSpeed);
 10326        bool responseSpeedResolved = Fixed64.TryMultiplyDivide(
 10327            closingSpeed,
 10328            responseFactor,
 10329            Fixed64.One,
 10330            out Fixed64 responseSpeed);
 10331        Vector3d targetResponseNormal = target.ProjectLinearMotion(-normal);
 10332        bool targetDeltaResolved = ContinuousCollisionImpulsePolicy.TryResolveVelocityDelta(
 10333            targetResponseNormal,
 10334            responseSpeed,
 10335            target.EffectiveInverseMass,
 10336            constrainedInverseMass,
 10337            out Vector3d targetVelocityDelta);
 10338        bool targetVelocityResolved = Vector3d.TryAdd(
 10339            targetVelocity,
 10340            target.ProjectLinearMotion(targetVelocityDelta),
 10341            out Vector3d targetPostVelocity);
 10342        if (!(relativeVelocityResolved
 10343            & normalVelocity < -Fixed64.Epsilon
 10344            & responseFactorResolved
 10345            & closingSpeedResolved
 10346            & responseSpeedResolved
 10347            & targetDeltaResolved
 10348            & targetVelocityResolved))
 349        {
 1350            return false;
 351        }
 352
 9353        return target.ApplyContinuousCollisionHandoff(
 9354            target.SampleContinuousCollisionPosition(hitTime),
 9355            target.SampleContinuousCollisionRotation(hitTime),
 9356            targetPostVelocity,
 9357            target.SampleContinuousCollisionAngularVelocity(hitTime),
 9358            deltaTime * (Fixed64.One - hitTime),
 9359            ignoredCollider3D: Collider);
 360    }
 361
 362    internal bool ApplyKinematicContinuousCollisionHandoff(
 363        SolidBody2D target,
 364        Vector3d sourceDisplacement,
 365        Vector3d normalForSource,
 366        Fixed64 hitDistance,
 367        Fixed64 sourceLength)
 368    {
 8369        if (!ContinuousCollisionImpulsePolicy.TryResolveImpactNormal(
 8370                normalForSource,
 8371                out Vector3d normal))
 372        {
 1373            return false;
 374        }
 375
 7376        Fixed64 deltaTime = Context.DeltaTime;
 7377        Fixed64 inverseMass = target.EffectiveInverseMass;
 378
 7379        Fixed64 hitTime = FixedMath.Clamp01(hitDistance / sourceLength);
 7380        Vector3d sourceVelocity = SampleContinuousCollisionLinearVelocity(hitTime);
 7381        Vector2d targetLinearVelocity = target.SampleContinuousCollisionLinearVelocity(hitTime);
 7382        Vector3d targetVelocity = targetLinearVelocity.ToVector3d(Fixed64.Zero);
 7383        bool relativeVelocityResolved = Vector3d.TrySubtract(
 7384            sourceVelocity,
 7385            targetVelocity,
 7386            out Vector3d relativeVelocity);
 387
 7388        Vector2d planarNormal = normal.ToVector2d();
 7389        Fixed64 constrainedInverseMass = target.GetConstrainedInverseMass(planarNormal) * planarNormal.MagnitudeSquared;
 7390        if (constrainedInverseMass <= Fixed64.Zero)
 1391            return false;
 392
 6393        Fixed64 normalVelocity = Vector3d.Dot(relativeVelocity, normal);
 6394        Fixed64 restitution = ResolveContinuousCollisionRestitution(target, -normalVelocity);
 6395        bool responseFactorResolved = Fixed64.TryAdd(
 6396            Fixed64.One,
 6397            restitution,
 6398            out Fixed64 responseFactor);
 6399        bool closingSpeedResolved = Fixed64.TrySubtract(
 6400            Fixed64.Zero,
 6401            normalVelocity,
 6402            out Fixed64 closingSpeed);
 6403        bool responseSpeedResolved = Fixed64.TryMultiplyDivide(
 6404            closingSpeed,
 6405            responseFactor,
 6406            Fixed64.One,
 6407            out Fixed64 responseSpeed);
 6408        Vector2d targetResponseNormal = target.ProjectLinearMotion(-planarNormal);
 6409        bool targetDeltaResolved = ContinuousCollisionImpulsePolicy.TryResolveVelocityDelta(
 6410            targetResponseNormal,
 6411            responseSpeed,
 6412            inverseMass,
 6413            constrainedInverseMass,
 6414            out Vector2d targetVelocityDelta);
 6415        bool targetVelocityResolved = Vector2d.TryAdd(
 6416            targetLinearVelocity,
 6417            target.ProjectLinearMotion(targetVelocityDelta),
 6418            out Vector2d targetPostVelocity);
 6419        if (!(relativeVelocityResolved
 6420            & normalVelocity < -Fixed64.Epsilon
 6421            & responseFactorResolved
 6422            & closingSpeedResolved
 6423            & responseSpeedResolved
 6424            & targetDeltaResolved
 6425            & targetVelocityResolved))
 426        {
 1427            return false;
 428        }
 429
 5430        return target.ApplyContinuousCollisionHandoffState(
 5431            target.SampleContinuousCollisionPosition(hitTime),
 5432            target.SampleContinuousCollisionRotation(hitTime),
 5433            targetPostVelocity,
 5434            target.SampleContinuousCollisionAngularVelocity(hitTime),
 5435            deltaTime * (Fixed64.One - hitTime),
 5436            ignoredCollider3D: Collider);
 437    }
 438
 439}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.ContinuousCollision.Rotational.cs

#LineLine coverage
 1//=======================================================================
 2// SolidBody.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 FixedMathSharp.Geometry;
 10using Gravitas.Colliders;
 11using Gravitas.CollisionHandling;
 12using System;
 13
 14namespace Gravitas;
 15
 16public partial class SolidBody
 17{
 18    private bool TryResolveRotationalContinuousCollision(
 19        Vector3d startPosition,
 20        ref Vector3d proposedPosition,
 21        FixedQuaternion startRotation,
 22        ref FixedQuaternion proposedRotation) =>
 1091823        TryResolveRotationalContinuousCollision(
 1091824            startPosition,
 1091825            ref proposedPosition,
 1091826            startRotation,
 1091827            ref proposedRotation,
 1091828            Context.DeltaTime,
 1091829            Fixed64.Zero,
 1091830            forceContinuous: false);
 31
 32    private bool TryResolveRotationalContinuousCollision(
 33        Vector3d startPosition,
 34        ref Vector3d proposedPosition,
 35        FixedQuaternion startRotation,
 36        ref FixedQuaternion proposedRotation,
 37        Fixed64 initialRemainingTime,
 38        Fixed64 initialElapsedTime,
 39        bool forceContinuous)
 40    {
 1106941        ContinuousCollisionMode mode = ContinuousCollisionMode.Continuous;
 1106942        if (!forceContinuous && !ShouldUseContinuousCollision(out mode))
 1063743            return false;
 44
 43245        Fixed64 pivotRadius = ResolveContinuousCollisionProxyRadius();
 43246        Vector3d initialDisplacement = proposedPosition - startPosition;
 43247        Fixed64 angularDistance = _angularVelocity.Magnitude * initialRemainingTime;
 43248        bool targetRequiresRotationalSampling = angularDistance <= Fixed64.Epsilon
 43249            && HasNearbyRotationalContinuousCollisionTarget(
 43250                startPosition,
 43251                initialDisplacement,
 43252                pivotRadius);
 43253        if (angularDistance <= Fixed64.Epsilon && !targetRequiresRotationalSampling)
 36654            return false;
 55
 6656        Fixed64 angularArcLength = angularDistance * pivotRadius;
 6657        if (pivotRadius <= Fixed64.Epsilon
 6658            || (!targetRequiresRotationalSampling
 6659                && angularArcLength <= Fixed64.Epsilon)
 6660            || (!targetRequiresRotationalSampling
 6661                && mode == ContinuousCollisionMode.Auto
 6662                && angularArcLength <= pivotRadius
 6663                && ContinuousCollisionMath.IsWithinProxyRadius(
 6664                    initialDisplacement,
 6665                    initialDisplacement.MagnitudeSquared,
 6666                    pivotRadius)))
 67        {
 668            return false;
 69        }
 70
 6071        Vector3d originalPosition = Position3d;
 6072        FixedQuaternion originalRotation = Rotation;
 6073        bool originalPositionMutated = _positionMutated;
 6074        bool originalRotationMutated = _rotationMutated;
 6075        LSCollider2D? originalIgnoredMixedTarget = _continuousCollisionHandoffIgnoredCollider2D;
 76        try
 77        {
 6078            Vector3d currentPosition = startPosition;
 6079            FixedQuaternion currentRotation = startRotation;
 6080            Fixed64 remainingTime = initialRemainingTime;
 6081            Fixed64 elapsedTime = initialElapsedTime;
 6082            Vector3d motionSegmentStartPosition = startPosition;
 6083            FixedQuaternion motionSegmentStartRotation = startRotation;
 6084            Fixed64 motionSegmentStartElapsedTime = initialElapsedTime;
 6085            Vector3d motionSegmentLinearVelocity = ProjectLinearMotion(_linearVelocity);
 6086            Vector3d motionSegmentAngularVelocity = _angularVelocity;
 6087            LSCollider? ignoredTarget = _continuousCollisionHandoffIgnoredCollider3D;
 6088            int maxIterations = Context.Settings.ContinuousCollisionMaxToiIterations;
 6089            int conservativeRefinementCount = 0;
 6090            int arbiterIterationLimit = maxIterations
 6091                + ContinuousCollisionMath.RotationalIntervalMaxDepth;
 16092            for (int iteration = 0; iteration < arbiterIterationLimit; iteration++)
 93            {
 8094                Fixed64 segmentEndElapsedTime = elapsedTime + remainingTime;
 8095                Fixed64 segmentElapsedTime = segmentEndElapsedTime - motionSegmentStartElapsedTime;
 8096                Vector3d segmentEnd = motionSegmentStartPosition
 8097                    + motionSegmentLinearVelocity * segmentElapsedTime;
 8098                Vector3d displacement = segmentEnd - currentPosition;
 8099                FixedQuaternion targetRotation = IntegrateAngularRotation(
 80100                    motionSegmentStartRotation,
 80101                    motionSegmentAngularVelocity,
 80102                    segmentElapsedTime);
 80103                angularDistance = motionSegmentAngularVelocity.Magnitude * remainingTime;
 80104                GatherRotationalContinuousCollisionCandidates(
 80105                    currentPosition,
 80106                    segmentEnd,
 80107                    displacement,
 80108                    pivotRadius);
 80109                bool foundSameDimension = TryFindEarliestRotationalContinuousCollision(
 80110                        currentPosition,
 80111                        displacement,
 80112                        currentRotation,
 80113                        targetRotation,
 80114                        angularDistance,
 80115                        pivotRadius,
 80116                        elapsedTime,
 80117                        remainingTime,
 80118                        isKinematic: false,
 80119                        ignoredTarget,
 80120                        out Fixed64 safeTime,
 80121                        out ManifoldContact contact,
 80122                        out bool hasContact,
 80123                        out Fixed64 contactTime,
 80124                        out LSCollider? target);
 80125                bool foundMixed = TryFindEarliestMixedRotationalContinuousCollision(
 80126                    currentPosition,
 80127                    displacement,
 80128                    currentRotation,
 80129                    targetRotation,
 80130                    angularDistance,
 80131                    pivotRadius,
 80132                    elapsedTime,
 80133                    remainingTime,
 80134                    sourceIsKinematic: false,
 80135                    out RotationalMixed2DHit mixedHit);
 136                // Both searches initialize their hit structures, so evaluating
 137                // the tie policy eagerly avoids encoding an artificial
 138                // short-circuit state in this deterministic arbitration rule.
 80139                bool useMixed = foundMixed
 80140                    & (!foundSameDimension | mixedHit.SafeTime <= safeTime);
 80141                if (!foundSameDimension && !foundMixed)
 142                {
 20143                    currentPosition = segmentEnd;
 20144                    currentRotation = targetRotation;
 20145                    break;
 146                }
 147
 60148                Fixed64 conservativeTime = useMixed ? mixedHit.SafeTime : safeTime;
 60149                Fixed64 witnessedTime = useMixed ? mixedHit.ContactTime : contactTime;
 60150                bool responseWitnessIsEarliest = witnessedTime == conservativeTime;
 60151                Fixed64 witnessedConsumedTime = remainingTime * witnessedTime;
 60152                bool appliedResponse = responseWitnessIsEarliest && (useMixed
 60153                    ? mixedHit.HasContact
 60154                        && TryApplyMixedRotationalContinuousCollisionResponse(
 60155                            mixedHit.Target,
 60156                            mixedHit.Contact,
 60157                            mixedHit.ContactTime,
 60158                            currentPosition,
 60159                            displacement,
 60160                            currentRotation,
 60161                            targetRotation,
 60162                            elapsedTime,
 60163                            remainingTime,
 60164                            sourceIsKinematic: false)
 60165                    : hasContact
 60166                        && target!.Body is SolidBody targetBody
 60167                        && IsMovingRotationalContinuousCollisionTarget(targetBody)
 60168                        && TryApplyRotationalContinuousCollisionResponse(
 60169                            targetBody,
 60170                            contact,
 60171                            contactTime,
 60172                            currentPosition,
 60173                            displacement,
 60174                            currentRotation,
 60175                            elapsedTime,
 60176                            remainingTime,
 60177                            sourceIsKinematic: false));
 178                // Stable trajectories and candidate ownership make a retained
 179                // witness rediscoverable or terminally clamped within this arbiter.
 60180                bool deferUnresolvedWitness = !responseWitnessIsEarliest
 60181                    && witnessedTime > conservativeTime
 60182                    && conservativeTime > Fixed64.Epsilon
 60183                    && conservativeRefinementCount
 60184                        < ContinuousCollisionMath.RotationalIntervalMaxDepth;
 60185                Fixed64 consumedTime = appliedResponse
 60186                    ? witnessedConsumedTime
 60187                    : remainingTime * conservativeTime;
 60188                Fixed64 impactElapsedTime = elapsedTime + consumedTime;
 60189                Fixed64 impactSegmentElapsedTime = impactElapsedTime
 60190                    - motionSegmentStartElapsedTime;
 60191                Vector3d impactPosition = motionSegmentStartPosition
 60192                    + motionSegmentLinearVelocity * impactSegmentElapsedTime;
 60193                FixedQuaternion impactRotation = IntegrateAngularRotation(
 60194                    motionSegmentStartRotation,
 60195                    motionSegmentAngularVelocity,
 60196                    impactSegmentElapsedTime);
 60197                if (appliedResponse)
 198                {
 11199                    elapsedTime = impactElapsedTime;
 11200                    remainingTime -= consumedTime;
 11201                    currentPosition = impactPosition;
 11202                    currentRotation = impactRotation;
 11203                    motionSegmentStartPosition = currentPosition;
 11204                    motionSegmentStartRotation = currentRotation;
 11205                    motionSegmentStartElapsedTime = elapsedTime;
 11206                    motionSegmentLinearVelocity = ProjectLinearMotion(_linearVelocity);
 11207                    motionSegmentAngularVelocity = _angularVelocity;
 11208                    LastContinuousCollisionToiIterationCount++;
 11209                    if (useMixed)
 210                    {
 2211                        _continuousCollisionHandoffIgnoredCollider2D = mixedHit.Target;
 2212                        ignoredTarget = null;
 213                    }
 214                    else
 215                    {
 9216                        ignoredTarget = target;
 9217                        _continuousCollisionHandoffIgnoredCollider2D = null;
 218                    }
 11219                    Fixed64 remainingMotionSquared = FixedMath.Max(
 11220                        _linearVelocity.MagnitudeSquared,
 11221                        _angularVelocity.MagnitudeSquared);
 11222                    if (remainingTime <= Fixed64.Epsilon
 11223                        || remainingMotionSquared <= Fixed64.Epsilon)
 224                    {
 225                        break;
 226                    }
 227
 11228                    if (LastContinuousCollisionToiIterationCount >= maxIterations)
 229                    {
 2230                        LastContinuousCollisionToiIterationLimitReached = true;
 2231                        break;
 232                    }
 233
 234                    continue;
 235                }
 236
 49237                currentPosition = impactPosition;
 49238                currentRotation = impactRotation;
 49239                if (deferUnresolvedWitness)
 240                {
 11241                    conservativeRefinementCount++;
 11242                    elapsedTime = impactElapsedTime;
 11243                    remainingTime -= consumedTime;
 11244                    continue;
 245                }
 246
 38247                LastContinuousCollisionToiIterationCount++;
 38248                StopRotationalContinuousCollision(
 38249                    useMixed
 38250                        ? -mixedHit.Contact.Normal3DTo2D
 38251                        : ResolveSourceContactNormal(target!, contact));
 38252                if (CanAppendContinuousCollisionSegment(impactElapsedTime))
 253                {
 37254                    _ = Context.Physics.TryReserveContinuousCollisionCandidateRefresh(this);
 37255                    AppendContinuousCollisionSegment(
 37256                        currentPosition,
 37257                        currentRotation,
 37258                        Vector3d.Zero,
 37259                        Vector3d.Zero,
 37260                        impactElapsedTime);
 37261                    Context.Physics.RefreshContinuousCollisionCandidate(this);
 262                }
 263                else
 264                {
 1265                    LastContinuousCollisionToiIterationLimitReached = true;
 266                }
 1267                break;
 268            }
 269
 60270            proposedPosition = currentPosition;
 60271            proposedRotation = currentRotation;
 60272            return true;
 273        }
 274        finally
 275        {
 60276            Position3d = originalPosition;
 60277            Rotation = originalRotation;
 60278            Collider.RebuildRuntimeShapeOnly(refreshMassProperties: false);
 60279            _positionMutated = originalPositionMutated;
 60280            _rotationMutated = originalRotationMutated;
 60281            _continuousCollisionHandoffIgnoredCollider2D = originalIgnoredMixedTarget;
 60282        }
 60283    }
 284
 285    private bool TryResolveKinematicRotationalContinuousCollision(
 286        Vector3d startPosition,
 287        ref Vector3d proposedPosition,
 288        FixedQuaternion startRotation,
 289        ref FixedQuaternion proposedRotation)
 290    {
 6331291        if (!ShouldUseContinuousCollision(out ContinuousCollisionMode mode))
 13292            return false;
 293
 6318294        Fixed64 pivotRadius = ResolveContinuousCollisionProxyRadius();
 6318295        Vector3d displacement = proposedPosition - startPosition;
 6318296        Fixed64 angularDistance = ResolveKinematicAngularDistanceRadians(startRotation, proposedRotation);
 6318297        bool targetRequiresRotationalSampling = angularDistance <= Fixed64.Epsilon
 6318298            && HasNearbyRotationalContinuousCollisionTarget(
 6318299                startPosition,
 6318300                displacement,
 6318301                pivotRadius);
 6318302        if (angularDistance <= Fixed64.Epsilon && !targetRequiresRotationalSampling)
 303        {
 6205304            return false;
 305        }
 306
 113307        Fixed64 angularArcLength = angularDistance * pivotRadius;
 113308        if (pivotRadius <= Fixed64.Epsilon
 113309            || (!targetRequiresRotationalSampling
 113310                && angularArcLength <= Fixed64.Epsilon)
 113311            || (!targetRequiresRotationalSampling
 113312                && mode == ContinuousCollisionMode.Auto
 113313                && angularArcLength <= pivotRadius
 113314                && ContinuousCollisionMath.IsWithinProxyRadius(
 113315                    displacement,
 113316                    displacement.MagnitudeSquared,
 113317                    pivotRadius)))
 318        {
 3319            return false;
 320        }
 321
 110322        FixedQuaternion targetRotation = proposedRotation;
 110323        Vector3d originalPosition = Position3d;
 110324        FixedQuaternion originalRotation = Rotation;
 110325        bool originalPositionMutated = _positionMutated;
 110326        bool originalRotationMutated = _rotationMutated;
 110327        LSCollider2D? originalIgnoredMixedTarget = _continuousCollisionHandoffIgnoredCollider2D;
 328        try
 329        {
 110330            Vector3d currentPosition = startPosition;
 110331            FixedQuaternion currentRotation = startRotation;
 110332            Fixed64 elapsedTime = Fixed64.Zero;
 110333            Fixed64 remainingTime = Context.DeltaTime;
 110334            LSCollider? ignoredTarget = null;
 110335            int maxIterations = Context.Settings.ContinuousCollisionMaxToiIterations;
 110336            int conservativeRefinementCount = 0;
 110337            int arbiterIterationLimit = maxIterations
 110338                + ContinuousCollisionMath.RotationalIntervalMaxDepth;
 578339            for (int iteration = 0; iteration < arbiterIterationLimit; iteration++)
 340            {
 289341                Vector3d segmentDisplacement = (proposedPosition - currentPosition);
 289342                FixedQuaternion segmentTargetRotation = targetRotation;
 289343                angularDistance = ResolveKinematicAngularDistanceRadians(
 289344                    currentRotation,
 289345                    segmentTargetRotation);
 289346                GatherRotationalContinuousCollisionCandidates(
 289347                    currentPosition,
 289348                    proposedPosition,
 289349                    segmentDisplacement,
 289350                    pivotRadius);
 289351                bool foundSameDimension = TryFindEarliestRotationalContinuousCollision(
 289352                        currentPosition,
 289353                        segmentDisplacement,
 289354                        currentRotation,
 289355                        segmentTargetRotation,
 289356                        angularDistance,
 289357                        pivotRadius,
 289358                        elapsedTime,
 289359                        remainingTime,
 289360                        isKinematic: true,
 289361                        ignoredTarget,
 289362                        out Fixed64 safeTime,
 289363                        out ManifoldContact contact,
 289364                        out bool hasContact,
 289365                        out Fixed64 contactTime,
 289366                        out LSCollider? target);
 289367                bool foundMixed = TryFindEarliestMixedRotationalContinuousCollision(
 289368                    currentPosition,
 289369                    segmentDisplacement,
 289370                    currentRotation,
 289371                    segmentTargetRotation,
 289372                    angularDistance,
 289373                    pivotRadius,
 289374                    elapsedTime,
 289375                    remainingTime,
 289376                    sourceIsKinematic: true,
 289377                    out RotationalMixed2DHit mixedHit);
 289378                bool useMixed = foundMixed
 289379                    && (!foundSameDimension || mixedHit.SafeTime <= safeTime);
 289380                if (!foundSameDimension && !foundMixed)
 381                {
 85382                    currentPosition = proposedPosition;
 85383                    currentRotation = targetRotation;
 85384                    break;
 385                }
 386
 204387                Fixed64 conservativeTime = useMixed ? mixedHit.SafeTime : safeTime;
 204388                Fixed64 witnessedTime = useMixed ? mixedHit.ContactTime : contactTime;
 204389                bool responseWitnessIsEarliest = witnessedTime == conservativeTime;
 204390                bool appliedResponse = responseWitnessIsEarliest && (useMixed
 204391                    ? mixedHit.HasContact
 204392                        && TryApplyMixedRotationalContinuousCollisionResponse(
 204393                            mixedHit.Target,
 204394                            mixedHit.Contact,
 204395                            mixedHit.ContactTime,
 204396                            currentPosition,
 204397                            segmentDisplacement,
 204398                            currentRotation,
 204399                            segmentTargetRotation,
 204400                            elapsedTime,
 204401                            remainingTime,
 204402                            sourceIsKinematic: true)
 204403                    : hasContact
 204404                        && target!.Body is SolidBody targetBody
 204405                        && !targetBody.IsKinematic
 204406                        && IsMovingRotationalContinuousCollisionTarget(targetBody)
 204407                        && TryApplyRotationalContinuousCollisionResponse(
 204408                            targetBody,
 204409                            contact,
 204410                            contactTime,
 204411                            currentPosition,
 204412                            segmentDisplacement,
 204413                            currentRotation,
 204414                            elapsedTime,
 204415                            remainingTime,
 204416                            sourceIsKinematic: true));
 204417                bool deferUnresolvedWitness = !responseWitnessIsEarliest
 204418                    && witnessedTime > conservativeTime
 204419                    && conservativeTime > Fixed64.Epsilon
 204420                    && conservativeRefinementCount
 204421                        < ContinuousCollisionMath.RotationalIntervalMaxDepth;
 204422                Fixed64 eventTime = appliedResponse ? witnessedTime : conservativeTime;
 204423                if (appliedResponse)
 424                {
 84425                    Fixed64 consumedTime = remainingTime * eventTime;
 84426                    elapsedTime += consumedTime;
 84427                    remainingTime -= consumedTime;
 84428                    currentPosition += segmentDisplacement * eventTime;
 84429                    currentRotation = FixedQuaternion.Slerp(
 84430                        currentRotation,
 84431                        segmentTargetRotation,
 84432                        eventTime).Normalized;
 84433                    LastContinuousCollisionToiIterationCount++;
 84434                    if (useMixed)
 435                    {
 44436                        _continuousCollisionHandoffIgnoredCollider2D = mixedHit.Target;
 44437                        ignoredTarget = null;
 438                    }
 439                    else
 440                    {
 40441                        ignoredTarget = target;
 40442                        _continuousCollisionHandoffIgnoredCollider2D = null;
 443                    }
 84444                    if (remainingTime <= Fixed64.Epsilon)
 445                        break;
 84446                    if (LastContinuousCollisionToiIterationCount >= maxIterations)
 447                    {
 3448                        LastContinuousCollisionToiIterationLimitReached = true;
 3449                        currentPosition = proposedPosition;
 3450                        currentRotation = targetRotation;
 3451                        break;
 452                    }
 453
 454                    continue;
 455                }
 456
 120457                currentPosition += segmentDisplacement * eventTime;
 120458                currentRotation = FixedQuaternion.Slerp(
 120459                    currentRotation,
 120460                    segmentTargetRotation,
 120461                    eventTime).Normalized;
 120462                Fixed64 impactElapsedTime = elapsedTime + remainingTime * eventTime;
 120463                if (deferUnresolvedWitness)
 464                {
 98465                    conservativeRefinementCount++;
 98466                    Fixed64 consumedTime = remainingTime * eventTime;
 98467                    elapsedTime = impactElapsedTime;
 98468                    remainingTime -= consumedTime;
 98469                    continue;
 470                }
 471
 22472                LastContinuousCollisionToiIterationCount++;
 473                // Kinematic sources never receive handoff trajectory segments.
 474                // Their prepared authored segment is therefore the only retained
 475                // segment before this terminal clamp, so the validated positive
 476                // TOI budget always admits this single replacement tail.
 22477                _ = Context.Physics.TryReserveContinuousCollisionCandidateRefresh(this);
 22478                AppendContinuousCollisionSegment(
 22479                    currentPosition,
 22480                    currentRotation,
 22481                    Vector3d.Zero,
 22482                    Vector3d.Zero,
 22483                    impactElapsedTime);
 22484                Context.Physics.RefreshContinuousCollisionCandidate(this);
 22485                proposedPosition = currentPosition;
 22486                proposedRotation = currentRotation;
 22487                return true;
 488            }
 489
 88490            proposedPosition = currentPosition;
 88491            proposedRotation = currentRotation;
 88492            return true;
 493        }
 494        finally
 495        {
 110496            Position3d = originalPosition;
 110497            Rotation = originalRotation;
 110498            Collider.RebuildRuntimeShapeOnly(refreshMassProperties: false);
 110499            _positionMutated = originalPositionMutated;
 110500            _rotationMutated = originalRotationMutated;
 110501            _continuousCollisionHandoffIgnoredCollider2D = originalIgnoredMixedTarget;
 110502        }
 110503    }
 504
 505    private bool TryFindEarliestRotationalContinuousCollisionAgainstTarget(
 506        LSCollider target,
 507        Vector3d startPosition,
 508        Vector3d displacement,
 509        FixedQuaternion startRotation,
 510        FixedQuaternion targetRotation,
 511        Fixed64 angularDistance,
 512        Fixed64 pivotRadius,
 513        Fixed64 elapsedTime,
 514        Fixed64 remainingTime,
 515        bool isKinematic,
 516        out Fixed64 safeTime,
 517        out ManifoldContact contact,
 518        out bool hasContact,
 519        out Fixed64 contactTime)
 520    {
 166521        safeTime = Fixed64.Zero;
 166522        contact = default;
 166523        hasContact = false;
 166524        contactTime = Fixed64.Zero;
 166525        SolidBody? targetBody = target.Body;
 166526        bool samplesTargetMotion = targetBody != null
 166527            && IsMovingRotationalContinuousCollisionTarget(targetBody);
 166528        Vector3d originalTargetPosition = targetBody?.Position3d ?? Vector3d.Zero;
 166529        FixedQuaternion originalTargetRotation = targetBody?.Rotation ?? FixedQuaternion.Identity;
 166530        bool originalTargetPositionMutated = targetBody?._positionMutated ?? false;
 166531        bool originalTargetRotationMutated = targetBody?._rotationMutated ?? false;
 166532        if (samplesTargetMotion)
 106533            targetBody!.EnsureContinuousCollisionFramePrepared(Context.LateSimulateToken);
 534
 535        try
 536        {
 166537        Span<ContinuousCollisionMath.RotationalInterval> intervals =
 166538            stackalloc ContinuousCollisionMath.RotationalInterval[
 166539                ContinuousCollisionMath.RotationalIntervalMaxDepth + 2];
 166540        int intervalCount = 1;
 166541        int processedNodeCount = 0;
 166542        bool hasKnownContact = false;
 166543        Fixed64 knownContactTime = Fixed64.One;
 166544        ManifoldContact knownContact = default;
 166545        intervals[0] = new ContinuousCollisionMath.RotationalInterval(
 166546            Fixed64.Zero,
 166547            Fixed64.One,
 166548            depth: 0);
 549
 3143550        while (intervalCount > 0)
 551        {
 3136552            ContinuousCollisionMath.RotationalInterval interval = intervals[--intervalCount];
 3136553            Fixed64 midpoint = (interval.LowerTime + interval.UpperTime) * Fixed64.Half;
 3136554            Fixed64 intervalSpan = interval.UpperTime - interval.LowerTime;
 3136555            if (isKinematic)
 556            {
 1905557                SampleKinematicRotationalContinuousPose(
 1905558                    startPosition,
 1905559                    displacement,
 1905560                    startRotation,
 1905561                    targetRotation,
 1905562                    midpoint);
 563            }
 564            else
 565            {
 1231566                SampleDynamicRotationalContinuousPose(
 1231567                    startPosition,
 1231568                    displacement,
 1231569                    startRotation,
 1231570                    remainingTime,
 1231571                    midpoint);
 572            }
 573
 3136574            Fixed64 midpointFrameFraction = ResolveRotationalFrameFraction(
 3136575                elapsedTime,
 3136576                remainingTime,
 3136577                midpoint);
 3136578            if (samplesTargetMotion)
 579            {
 1944580                targetBody!.Position3d = targetBody.SampleContinuousCollisionPosition(midpointFrameFraction);
 1944581                targetBody.Rotation = targetBody.SampleContinuousCollisionRotation(midpointFrameFraction);
 1944582                targetBody.Collider.RebuildRuntimeShapeOnly(refreshMassProperties: false);
 583            }
 584
 3136585            processedNodeCount++;
 3136586            bool hasMotionBound = ContinuousCollisionMath.TryResolveRotationalIntervalMotionBound(
 3136587                displacement,
 3136588                angularDistance,
 3136589                pivotRadius,
 3136590                intervalSpan,
 3136591                out Fixed64 motionBound);
 3136592            if (hasMotionBound && samplesTargetMotion)
 593            {
 1944594                Fixed64 lowerFrameFraction = ResolveRotationalFrameFraction(
 1944595                    elapsedTime,
 1944596                    remainingTime,
 1944597                    interval.LowerTime);
 1944598                Fixed64 upperFrameFraction = ResolveRotationalFrameFraction(
 1944599                    elapsedTime,
 1944600                    remainingTime,
 1944601                    interval.UpperTime);
 1944602                Fixed64 targetRadius = targetBody!.ResolveContinuousCollisionProxyRadius();
 1944603                bool targetBoundResolved = targetBody.TryResolveContinuousCollisionMotionBound(
 1944604                    lowerFrameFraction,
 1944605                    upperFrameFraction,
 1944606                    targetRadius,
 1944607                    out Fixed64 targetMotionBound);
 1944608                bool combinedBoundResolved = Fixed64.TryAdd(
 1944609                    motionBound,
 1944610                    targetMotionBound,
 1944611                    out motionBound);
 1944612                hasMotionBound = targetBoundResolved & combinedBoundResolved;
 613            }
 3136614            bool sampleHasContact = TrySampleRotationalContinuousCollision(
 3136615                target,
 3136616                out ManifoldContact sampleContact);
 3136617            if (sampleHasContact
 3136618                && (!hasKnownContact || midpoint < knownContactTime))
 619            {
 1098620                hasKnownContact = true;
 1098621                knownContactTime = midpoint;
 1098622                knownContact = sampleContact;
 623            }
 624
 3136625            if (!sampleHasContact
 3136626                && hasMotionBound
 3136627                && IsRotationalIntervalSeparated(target, motionBound))
 628            {
 629                continue;
 630            }
 631
 2258632            if (ContinuousCollisionMath.TryResolveRotationalSearchLimit(
 2258633                    interval,
 2258634                    processedNodeCount,
 2258635                    hasKnownContact,
 2258636                    knownContactTime,
 2258637                    out safeTime,
 2258638                    out contactTime,
 2258639                    out hasContact))
 640            {
 159641                contact = knownContact;
 159642                return true;
 643            }
 644
 2099645            int childDepth = interval.Depth + 1;
 2099646            if (sampleHasContact)
 647            {
 648                // A real midpoint contact is already an upper bound. Only the
 649                // earlier half can contain an earlier first contact.
 1052650                intervals[intervalCount++] = new ContinuousCollisionMath.RotationalInterval(
 1052651                    interval.LowerTime,
 1052652                    midpoint,
 1052653                    childDepth);
 1052654                continue;
 655            }
 656
 1047657            intervals[intervalCount++] = new ContinuousCollisionMath.RotationalInterval(
 1047658                midpoint,
 1047659                interval.UpperTime,
 1047660                childDepth);
 1047661            intervals[intervalCount++] = new ContinuousCollisionMath.RotationalInterval(
 1047662                interval.LowerTime,
 1047663                midpoint,
 1047664                childDepth);
 665        }
 666
 7667        return false;
 668        }
 669        finally
 670        {
 166671            if (samplesTargetMotion)
 672            {
 106673                targetBody!.Position3d = originalTargetPosition;
 106674                targetBody.Rotation = originalTargetRotation;
 106675                targetBody.Collider.RebuildRuntimeShapeOnly(refreshMassProperties: false);
 106676                targetBody._positionMutated = originalTargetPositionMutated;
 106677                targetBody._rotationMutated = originalTargetRotationMutated;
 678            }
 166679        }
 166680    }
 681
 682    private bool IsRotationalIntervalSeparated(LSCollider target, Fixed64 motionBound)
 683    {
 2038684        if (TryGetSpherePairSeparationGap(target, out Fixed64 closestPointGap))
 2001685            return closestPointGap > motionBound;
 686
 37687        return ContinuousCollisionMath.AreBoundsSeparatedByMoreThan(
 37688            Collider.Bounds,
 37689            target.Bounds,
 37690            motionBound);
 691    }
 692
 693    private bool TryGetSpherePairSeparationGap(LSCollider target, out Fixed64 separationGap)
 694    {
 2038695        if (Collider is LSSphereCollider sourceSphere)
 57696            return TryGetSphereSeparationGap(sourceSphere, target, out separationGap);
 697
 1981698        if (target is LSSphereCollider targetSphere)
 1944699            return TryGetSphereSeparationGap(targetSphere, Collider, out separationGap);
 700
 37701        separationGap = default;
 37702        return false;
 703    }
 704
 705    internal static bool TryGetSphereSeparationGap(
 706        LSSphereCollider sphere,
 707        LSCollider other,
 708        out Fixed64 separationGap)
 709    {
 710        Vector3d separation;
 711        Fixed64 otherRadius;
 2011712        if (other is LSSphereCollider otherSphere)
 713        {
 5714            if (!Vector3d.TrySubtract(
 5715                    sphere.Center,
 5716                    otherSphere.Center,
 5717                    out separation))
 718            {
 1719                separationGap = default;
 1720                return false;
 721            }
 4722            otherRadius = otherSphere.ScaledRadius;
 723        }
 2006724        else if (other is LSCuboidCollider cuboid)
 725        {
 2005726            var sphereCenter = new FixedPointAnchor(
 2005727                sphere.Center,
 2005728                FixedQuaternion.Identity,
 2005729                Vector3d.Zero);
 2005730            if (!sphereCenter.TryGetOffsetFrom(
 2005731                    cuboid.OrientedBox.GetClosestPointAnchor(sphere.Center),
 2005732                    out separation))
 733            {
 1734                separationGap = default;
 1735                return false;
 736            }
 2004737            otherRadius = Fixed64.Zero;
 738        }
 739        else
 740        {
 1741            separationGap = default;
 1742            return false;
 743        }
 744
 2008745        if (!Vector3d.TryGetMagnitude(separation, out Fixed64 distance)
 2008746            || !Fixed64.TryAdd(sphere.ScaledRadius, otherRadius, out Fixed64 combinedRadius))
 747        {
 1748            separationGap = default;
 1749            return false;
 750        }
 751
 2007752        Fixed64 rawGap = distance - combinedRadius;
 2007753        if (!Fixed64.TryAdd(distance, combinedRadius, out Fixed64 characteristicScale)
 2007754            || !Fixed64.TryAdd(characteristicScale, other.ScaledRadius, out characteristicScale)
 2007755            || !ContinuousCollisionMath.TrySubtractClosestFeatureUncertainty(
 2007756                rawGap,
 2007757                characteristicScale,
 2007758                out separationGap))
 759        {
 4760            separationGap = default;
 4761            return false;
 762        }
 763
 2003764        return separationGap > Fixed64.Zero;
 765    }
 766
 767    private void SampleDynamicRotationalContinuousPose(
 768        Vector3d startPosition,
 769        Vector3d displacement,
 770        FixedQuaternion startRotation,
 771        Fixed64 remainingTime,
 772        Fixed64 sampleTime)
 773    {
 1231774        Position3d = startPosition + displacement * sampleTime;
 1231775        Rotation = IntegrateAngularRotation(startRotation, remainingTime * sampleTime);
 1231776        Collider.RebuildRuntimeShapeOnly(refreshMassProperties: false);
 1231777    }
 778
 779    private void SampleKinematicRotationalContinuousPose(
 780        Vector3d startPosition,
 781        Vector3d displacement,
 782        FixedQuaternion startRotation,
 783        FixedQuaternion targetRotation,
 784        Fixed64 sampleTime)
 785    {
 1905786        Position3d = startPosition + displacement * sampleTime;
 1905787        Rotation = FixedQuaternion.Slerp(startRotation, targetRotation, sampleTime).Normalized;
 1905788        Collider.RebuildRuntimeShapeOnly(refreshMassProperties: false);
 1905789    }
 790
 791    private static Fixed64 ResolveKinematicAngularDistanceRadians(
 792        FixedQuaternion startRotation,
 793        FixedQuaternion proposedRotation)
 794    {
 13019795        Fixed64 angleDegrees = FixedQuaternion.Angle(startRotation, proposedRotation);
 13019796        return FixedMath.DegToRad(angleDegrees.Abs());
 797    }
 798
 799    private bool TrySampleRotationalContinuousCollision(
 800        LSCollider target,
 801        out ManifoldContact contact)
 802    {
 3136803        contact = default;
 3136804        OrderRotationalContinuousCollisionPair(
 3136805            target,
 3136806            out LSCollider colliderA,
 3136807            out LSCollider colliderB,
 3136808            out _);
 3136809        CollisionType collisionType = ColliderSettings.GetCollisionType(colliderA.Shape, colliderB.Shape);
 3136810        _rotationalContinuousCollisionManifold.BeginUpdate(Context.FrameCount);
 3136811        var workItem = new CollisionWorkItem(
 3136812            Context,
 3136813            colliderA,
 3136814            colliderB,
 3136815            collisionType,
 3136816            _rotationalContinuousCollisionManifold);
 3136817        if (!CollisionDetection.DoCollisionCheck(workItem)
 3136818            || !_rotationalContinuousCollisionManifold.HasContact)
 819        {
 2038820            return false;
 821        }
 822
 1098823        contact = _rotationalContinuousCollisionManifold.PrimaryContact;
 1098824        return true;
 825    }
 826
 827    private Vector3d ResolveSourceContactNormal(
 828        LSCollider target,
 829        ManifoldContact contact)
 830    {
 36831        OrderRotationalContinuousCollisionPair(
 36832            target,
 36833            out _,
 36834            out _,
 36835            out bool sourceIsA);
 36836        return sourceIsA ? contact.Normal : -contact.Normal;
 837    }
 838
 839    private bool IsMovingRotationalContinuousCollisionTarget(SolidBody target)
 840    {
 857841        if (target == this || !target.Active)
 371842            return false;
 843
 486844        if (target.IsKinematic)
 74845            target.EnsureContinuousCollisionFramePrepared(Context.LateSimulateToken);
 846
 486847        return (IsKinematic || !target.ShouldOwnContinuousCollisionMovingPair(
 486848                HasContinuousCollisionRotationalMotion))
 486849            && !target.Collider.IsTrigger
 486850            && (target.IsKinematic
 486851                ? target.HasContinuousCollisionMotion
 486852                : target.IsDynamic)
 486853            && target.Collider != _continuousCollisionHandoffIgnoredCollider3D;
 854    }
 855
 856    private Fixed64 ResolveRotationalFrameFraction(
 857        Fixed64 elapsedTime,
 858        Fixed64 remainingTime,
 859        Fixed64 localFraction) =>
 14124860        FixedMath.Clamp01(
 14124861            (elapsedTime + remainingTime * localFraction) / Context.DeltaTime);
 862
 863    private void OrderRotationalContinuousCollisionPair(
 864        LSCollider target,
 865        out LSCollider colliderA,
 866        out LSCollider colliderB,
 867        out bool sourceIsA)
 868    {
 3231869        if (Collider.Priority >= target.Priority)
 870        {
 3101871            colliderA = Collider;
 3101872            colliderB = target;
 3101873            sourceIsA = true;
 3101874            return;
 875        }
 876
 130877        colliderA = target;
 130878        colliderB = Collider;
 130879        sourceIsA = false;
 130880    }
 881
 882    private FixedQuaternion IntegrateAngularRotation(FixedQuaternion startRotation, Fixed64 deltaTime)
 12149883        => IntegrateAngularRotation(startRotation, _angularVelocity, deltaTime);
 884
 885    private static FixedQuaternion IntegrateAngularRotation(
 886        FixedQuaternion startRotation,
 887        Vector3d angularVelocity,
 888        Fixed64 deltaTime)
 889    {
 20693890        FixedQuaternion angularVelocityQuaternion = new(
 20693891            angularVelocity.X,
 20693892            angularVelocity.Y,
 20693893            angularVelocity.Z,
 20693894            Fixed64.Zero);
 20693895        FixedQuaternion spin = angularVelocityQuaternion * startRotation * Fixed64.Half * deltaTime;
 20693896        return (startRotation + spin).Normalized;
 897    }
 898
 899}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.ContinuousCollision.Rotational.Mixed.cs

#LineLine coverage
 1//=======================================================================
 2// SolidBody.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 partial class SolidBody
 21{
 507622    private readonly SwiftList<LSCollider2D> _rotationalMixedStatic2DCandidates =
 507623        new(DefaultBodyHitBufferCapacity);
 507624    private readonly SwiftList<int> _rotationalMixedMoving2DCandidateIds =
 507625        new(DefaultBodyHitBufferCapacity);
 26
 27    internal void SetMixedContinuousCollisionSamplePose(
 28        Vector3d position,
 29        FixedQuaternion rotation)
 30    {
 235331        _position2dUnmarked = position.ToVector2d();
 235332        _heightPosUnmarked = position.Y;
 235333        _rotation = rotation;
 235334        Collider.RebuildRuntimeShapeOnly(refreshMassProperties: false);
 235335    }
 36
 37    private readonly struct RotationalMixed2DHit
 38    {
 39        public RotationalMixed2DHit(
 40            Fixed64 safeTime,
 41            MixedContact contact,
 42            bool hasContact,
 43            Fixed64 contactTime,
 44            LSCollider2D target)
 45        {
 11646            SafeTime = safeTime;
 11647            Contact = contact;
 11648            HasContact = hasContact;
 11649            ContactTime = contactTime;
 11650            Target = target;
 11651        }
 52
 53        public Fixed64 SafeTime { get; }
 54
 55        public MixedContact Contact { get; }
 56
 57        public bool HasContact { get; }
 58
 59        public Fixed64 ContactTime { get; }
 60
 61        public LSCollider2D Target { get; }
 62    }
 63
 64    private bool TryFindEarliestMixedRotationalContinuousCollision(
 65        Vector3d startPosition,
 66        Vector3d displacement,
 67        FixedQuaternion startRotation,
 68        FixedQuaternion targetRotation,
 69        Fixed64 angularDistance,
 70        Fixed64 pivotRadius,
 71        Fixed64 elapsedTime,
 72        Fixed64 remainingTime,
 73        bool sourceIsKinematic,
 74        out RotationalMixed2DHit hit)
 75    {
 36976        hit = default;
 36977        if (!Context.Settings.RuntimeMode.RunsMixedContacts())
 20778            return false;
 79
 16280        GatherMixedRotationalContinuousCollisionCandidates(
 16281            startPosition,
 16282            displacement,
 16283            pivotRadius);
 84
 16285        bool found = false;
 16286        Fixed64 earliestTime = Fixed64.One;
 16287        int earliestId = int.MaxValue;
 16288        MixedContact earliestContact = default;
 16289        bool earliestHasContact = false;
 16290        Fixed64 earliestContactTime = Fixed64.Zero;
 16291        LSCollider2D? earliestTarget = null;
 92
 34493        for (int i = 0; i < _rotationalMixedStatic2DCandidates.Count; i++)
 94        {
 1095            LSCollider2D target = _rotationalMixedStatic2DCandidates[i];
 1096            ConsiderMixedRotationalContinuousCollisionCandidate(
 1097                target,
 1098                startPosition,
 1099                displacement,
 10100                startRotation,
 10101                targetRotation,
 10102                angularDistance,
 10103                pivotRadius,
 10104                elapsedTime,
 10105                remainingTime,
 10106                sourceIsKinematic,
 10107                ref found,
 10108                ref earliestTime,
 10109                ref earliestId,
 10110                ref earliestContact,
 10111                ref earliestHasContact,
 10112                ref earliestContactTime,
 10113                ref earliestTarget);
 114        }
 115
 640116        for (int i = 0; i < _rotationalMixedMoving2DCandidateIds.Count; i++)
 117        {
 158118            SolidBody2D targetBody = Context.Physics2D.GetContinuousCollisionCandidate(
 158119                _rotationalMixedMoving2DCandidateIds[i]);
 158120            if (!IsMovingMixedRotationalContinuousCollisionTarget(targetBody))
 121            {
 122                continue;
 123            }
 124
 110125            ConsiderMixedRotationalContinuousCollisionCandidate(
 110126                targetBody.Collider,
 110127                startPosition,
 110128                displacement,
 110129                startRotation,
 110130                targetRotation,
 110131                angularDistance,
 110132                pivotRadius,
 110133                elapsedTime,
 110134                remainingTime,
 110135                sourceIsKinematic,
 110136                ref found,
 110137                ref earliestTime,
 110138                ref earliestId,
 110139                ref earliestContact,
 110140                ref earliestHasContact,
 110141                ref earliestContactTime,
 110142                ref earliestTarget);
 143        }
 144
 162145        if (!found)
 46146            return false;
 147
 116148        hit = new RotationalMixed2DHit(
 116149            earliestTime,
 116150            earliestContact,
 116151            earliestHasContact,
 116152            earliestContactTime,
 116153            earliestTarget!);
 116154        return true;
 155    }
 156
 157    private void GatherMixedRotationalContinuousCollisionCandidates(
 158        Vector3d startPosition,
 159        Vector3d displacement,
 160        Fixed64 pivotRadius)
 161    {
 162162        _rotationalMixedStatic2DCandidates.FastClear();
 162163        _rotationalMixedMoving2DCandidateIds.FastClear();
 162164        if (pivotRadius == Fixed64.MaxValue)
 165        {
 4166            int count = Context.Physics2D.ColliderCount;
 4167            _rotationalMixedStatic2DCandidates.EnsureCapacity(count);
 4168            _rotationalMixedMoving2DCandidateIds.EnsureCapacity(count);
 16169            for (int i = 0; i < count; i++)
 170            {
 4171                LSCollider2D target = Context.Physics2D.GetColliderByServiceIndex(i);
 4172                if (target.Body is SolidBody2D targetBody
 4173                    && IsMovingMixedRotationalContinuousCollisionTarget(targetBody))
 174                {
 1175                    _rotationalMixedMoving2DCandidateIds.Add(targetBody.DynamicId);
 1176                    continue;
 177                }
 178
 3179                _rotationalMixedStatic2DCandidates.Add(target);
 180            }
 181
 4182            return;
 183        }
 184
 158185        FixedBoundVolume bounds = DynamicCcdCandidateIndex.CreateSweptSphereBounds(
 158186            startPosition,
 158187            displacement,
 158188            pivotRadius);
 158189        Context.MixedCollisions.Collect2DCandidatesInMixedBounds(
 158190            bounds.Min,
 158191            bounds.Max,
 158192            PhysicsLayerMask.All,
 158193            _rotationalMixedStatic2DCandidates,
 158194            staticStyleOnly: true,
 158195            cachePartitionRefresh: true);
 196
 158197        SwiftList<int> candidateIds =
 158198            Context.Physics2D.QueryMixedContinuousCollisionCandidates(bounds);
 158199        _rotationalMixedMoving2DCandidateIds.EnsureCapacity(candidateIds.Count);
 630200        for (int i = 0; i < candidateIds.Count; i++)
 157201            _rotationalMixedMoving2DCandidateIds.Add(candidateIds[i]);
 158202    }
 203
 204    private void ConsiderMixedRotationalContinuousCollisionCandidate(
 205        LSCollider2D target,
 206        Vector3d startPosition,
 207        Vector3d displacement,
 208        FixedQuaternion startRotation,
 209        FixedQuaternion targetRotation,
 210        Fixed64 angularDistance,
 211        Fixed64 pivotRadius,
 212        Fixed64 elapsedTime,
 213        Fixed64 remainingTime,
 214        bool sourceIsKinematic,
 215        ref bool found,
 216        ref Fixed64 earliestTime,
 217        ref int earliestId,
 218        ref MixedContact earliestContact,
 219        ref bool earliestHasContact,
 220        ref Fixed64 earliestContactTime,
 221        ref LSCollider2D? earliestTarget)
 222    {
 120223        if (!IsValidMixedRotationalContinuousCollisionTarget(target)
 120224            || !TryFindEarliestMixedRotationalContinuousCollisionAgainstTarget(
 120225                target,
 120226                startPosition,
 120227                displacement,
 120228                startRotation,
 120229                targetRotation,
 120230                angularDistance,
 120231                pivotRadius,
 120232                elapsedTime,
 120233                remainingTime,
 120234                sourceIsKinematic,
 120235                Fixed64.Zero,
 120236                Fixed64.One,
 120237                out Fixed64 candidateTime,
 120238                out MixedContact candidateContact,
 120239                out bool candidateHasContact,
 120240                out Fixed64 candidateContactTime)
 120241            || !ContinuousCollisionMath.ShouldReplaceContinuousCollisionHit(
 120242                candidateTime,
 120243                target.Id,
 120244                found,
 120245                earliestTime,
 120246                earliestId))
 247        {
 4248            return;
 249        }
 250
 116251        found = true;
 116252        earliestTime = candidateTime;
 116253        earliestId = target.Id;
 116254        earliestContact = candidateContact;
 116255        earliestHasContact = candidateHasContact;
 116256        earliestContactTime = candidateContactTime;
 116257        earliestTarget = target;
 116258    }
 259
 260    private bool TryFindEarliestMixedRotationalContinuousCollisionAgainstTarget(
 261        LSCollider2D target,
 262        Vector3d startPosition,
 263        Vector3d displacement,
 264        FixedQuaternion startRotation,
 265        FixedQuaternion targetRotation,
 266        Fixed64 angularDistance,
 267        Fixed64 pivotRadius,
 268        Fixed64 elapsedTime,
 269        Fixed64 remainingTime,
 270        bool sourceIsKinematic,
 271        Fixed64 searchStart,
 272        Fixed64 searchEnd,
 273        out Fixed64 safeTime,
 274        out MixedContact contact,
 275        out bool hasContact,
 276        out Fixed64 contactTime)
 277    {
 136278        safeTime = Fixed64.Zero;
 136279        contact = default;
 136280        hasContact = false;
 136281        contactTime = Fixed64.Zero;
 136282        SolidBody2D? targetBody = target.Body;
 136283        bool samplesTargetMotion = targetBody != null
 136284            && IsMovingMixedRotationalContinuousCollisionTarget(targetBody);
 136285        Vector2d originalTargetPosition = targetBody?.Position ?? Vector2d.Zero;
 136286        Fixed64 originalTargetRotation = targetBody?.Rotation ?? Fixed64.Zero;
 136287        if (samplesTargetMotion)
 126288            targetBody!.EnsureContinuousCollisionFramePrepared(Context.LateSimulateToken);
 289
 290        try
 291        {
 136292            Span<ContinuousCollisionMath.RotationalInterval> intervals =
 136293                stackalloc ContinuousCollisionMath.RotationalInterval[
 136294                    ContinuousCollisionMath.RotationalIntervalMaxDepth + 2];
 136295            int intervalCount = 1;
 136296            int processedNodeCount = 0;
 136297            bool hasKnownContact = false;
 136298            Fixed64 knownContactTime = Fixed64.One;
 136299            MixedContact knownContact = default;
 136300            intervals[0] = new ContinuousCollisionMath.RotationalInterval(
 136301                searchStart,
 136302                searchEnd,
 136303                depth: 0);
 304
 2469305            while (intervalCount > 0)
 306            {
 2465307                ContinuousCollisionMath.RotationalInterval interval = intervals[--intervalCount];
 2465308                Fixed64 midpoint =
 2465309                    (interval.LowerTime + interval.UpperTime) * Fixed64.Half;
 2465310                Fixed64 intervalSpan = interval.UpperTime - interval.LowerTime;
 2465311                SampleMixedRotationalContinuousPairPose(
 2465312                    startPosition,
 2465313                    displacement,
 2465314                    startRotation,
 2465315                    targetRotation,
 2465316                    midpoint,
 2465317                    elapsedTime,
 2465318                    remainingTime,
 2465319                    sourceIsKinematic,
 2465320                    targetBody,
 2465321                    samplesTargetMotion);
 2465322                processedNodeCount++;
 323
 2465324                bool hasMotionBound =
 2465325                    ContinuousCollisionMath.TryResolveRotationalIntervalMotionBound(
 2465326                        displacement,
 2465327                        angularDistance,
 2465328                        pivotRadius,
 2465329                        intervalSpan,
 2465330                        out Fixed64 motionBound);
 2465331                if (hasMotionBound && samplesTargetMotion)
 332                {
 2329333                    Fixed64 lowerFraction = ResolveRotationalFrameFraction(
 2329334                        elapsedTime,
 2329335                        remainingTime,
 2329336                        interval.LowerTime);
 2329337                    Fixed64 upperFraction = ResolveRotationalFrameFraction(
 2329338                        elapsedTime,
 2329339                        remainingTime,
 2329340                        interval.UpperTime);
 2329341                    Fixed64 targetRadius = targetBody!.ResolveContinuousCollisionProxyRadius();
 2329342                    bool targetBoundResolved = targetBody.TryResolveContinuousCollisionMotionBound(
 2329343                        lowerFraction,
 2329344                        upperFraction,
 2329345                        targetRadius,
 2329346                        out Fixed64 targetMotionBound);
 2329347                    bool combinedBoundResolved = Fixed64.TryAdd(
 2329348                        motionBound,
 2329349                        targetMotionBound,
 2329350                        out motionBound);
 2329351                    hasMotionBound = targetBoundResolved & combinedBoundResolved;
 352                }
 353
 2465354                bool sampleHasContact = CollisionDetectionMixed.TryCollide(
 2465355                    Collider,
 2465356                    target,
 2465357                    out MixedContact sampleContact);
 2465358                if (sampleHasContact
 2465359                    && (!hasKnownContact || midpoint < knownContactTime))
 360                {
 840361                    hasKnownContact = true;
 840362                    knownContactTime = midpoint;
 840363                    knownContact = sampleContact;
 364                }
 365
 2465366                bool hasShapeGap = CollisionDetectionMixed.TryGetRotationalSeparationGap(
 2465367                    Collider,
 2465368                    target,
 2465369                    out Fixed64 shapeGap,
 2465370                    out bool shapeGapSupported);
 2465371                Fixed64 characteristicScale = target is LSCircleCollider2D circle
 2465372                    ? FixedMath.Max(pivotRadius, circle.ScaledRadius)
 2465373                    : pivotRadius;
 2465374                bool shapeGapCertifiesSeparation = shapeGapSupported
 2465375                    && hasShapeGap
 2465376                    && ContinuousCollisionMath.TrySubtractClosestFeatureUncertainty(
 2465377                        shapeGap,
 2465378                        characteristicScale,
 2465379                        out Fixed64 conservativeShapeGap)
 2465380                    && conservativeShapeGap > motionBound;
 2465381                bool fallbackBoundsCertifySeparation = !shapeGapSupported
 2465382                    && ContinuousCollisionMath.AreBoundsSeparatedByMoreThan(
 2465383                        Collider.Bounds,
 2465384                        target.MixedBounds3D,
 2465385                        motionBound);
 2465386                if (!sampleHasContact
 2465387                    && hasMotionBound
 2465388                    && (shapeGapCertifiesSeparation || fallbackBoundsCertifySeparation))
 389                {
 390                    continue;
 391                }
 392
 1876393                if (ContinuousCollisionMath.TryResolveRotationalSearchLimit(
 1876394                        interval,
 1876395                        processedNodeCount,
 1876396                        hasKnownContact,
 1876397                        knownContactTime,
 1876398                        out safeTime,
 1876399                        out contactTime,
 1876400                        out hasContact))
 401                {
 132402                    contact = knownContact;
 132403                    return true;
 404                }
 405
 1744406                int childDepth = interval.Depth + 1;
 1744407                if (sampleHasContact)
 408                {
 837409                    intervals[intervalCount++] =
 837410                        new ContinuousCollisionMath.RotationalInterval(
 837411                            interval.LowerTime,
 837412                            midpoint,
 837413                            childDepth);
 837414                    continue;
 415                }
 416
 907417                intervals[intervalCount++] =
 907418                    new ContinuousCollisionMath.RotationalInterval(
 907419                        midpoint,
 907420                        interval.UpperTime,
 907421                        childDepth);
 907422                intervals[intervalCount++] =
 907423                    new ContinuousCollisionMath.RotationalInterval(
 907424                        interval.LowerTime,
 907425                        midpoint,
 907426                        childDepth);
 427            }
 428
 4429            return false;
 430        }
 431        finally
 432        {
 136433            if (targetBody != null)
 132434                targetBody.SetMixedContinuousCollisionSamplePose(
 132435                    originalTargetPosition,
 132436                    originalTargetRotation);
 136437        }
 136438    }
 439
 440    private void SampleMixedRotationalContinuousPairPose(
 441        Vector3d startPosition,
 442        Vector3d displacement,
 443        FixedQuaternion startRotation,
 444        FixedQuaternion targetRotation,
 445        Fixed64 sampleTime,
 446        Fixed64 elapsedTime,
 447        Fixed64 remainingTime,
 448        bool sourceIsKinematic,
 449        SolidBody2D? targetBody,
 450        bool samplesTargetMotion)
 451    {
 2465452        Position3d = startPosition + displacement * sampleTime;
 2465453        Rotation = sourceIsKinematic
 2465454            ? FixedQuaternion.Slerp(startRotation, targetRotation, sampleTime).Normalized
 2465455            : IntegrateAngularRotation(
 2465456                startRotation,
 2465457                _angularVelocity,
 2465458                remainingTime * sampleTime);
 2465459        Collider.RebuildRuntimeShapeOnly(refreshMassProperties: false);
 460
 2465461        if (!samplesTargetMotion)
 136462            return;
 463
 2329464        Fixed64 frameFraction = ResolveRotationalFrameFraction(
 2329465            elapsedTime,
 2329466            remainingTime,
 2329467            sampleTime);
 2329468        SolidBody2D movingTarget = targetBody!;
 2329469        movingTarget.SetMixedContinuousCollisionSamplePose(
 2329470            movingTarget.SampleContinuousCollisionPosition(frameFraction),
 2329471            movingTarget.SampleContinuousCollisionRotation(frameFraction));
 2329472    }
 473
 474    private bool TryGetExactSphereCircleTranslationalContact(
 475        SolidBody2D target,
 476        ContinuousCollisionMotionSegment2D targetSegment,
 477        FixedSegment2d targetPath,
 478        Vector2d targetDisplacement,
 479        Fixed64 targetTravelDistance,
 480        FixedSegment sourcePath,
 481        Vector3d sourceDisplacement,
 482        Fixed64 sourceTravelDistance,
 483        FixedQuaternion sourceStartRotation,
 484        Fixed64 sourceAngularDistance,
 485        out bool requiresRotationalFallback,
 486        out Fixed64 relativeContactDistance,
 487        out Fixed64 sourceContactDistance,
 488        out Fixed64 localClosingSpeed,
 489        out MixedContact contact)
 490    {
 49491        requiresRotationalFallback = true;
 49492        relativeContactDistance = default;
 49493        sourceContactDistance = default;
 49494        localClosingSpeed = default;
 49495        contact = default;
 49496        if (Collider is not LSSphereCollider sphere
 49497            || target.Collider is not LSCircleCollider2D circle
 49498            || (targetSegment.AngularDistance != Fixed64.Zero
 49499                && circle.ScaledLocalOffset != Vector2d.Zero)
 49500            || (sourceAngularDistance != Fixed64.Zero
 49501                && sphere.ScaledOffset != Vector3d.Zero))
 502        {
 18503            return false;
 504        }
 505
 31506        Vector3d originalSourcePosition = Position3d;
 31507        FixedQuaternion originalSourceRotation = Rotation;
 31508        Vector2d originalTargetPosition = target.Position;
 31509        Fixed64 originalTargetRotation = target.Rotation;
 510        try
 511        {
 31512            SetMixedContinuousCollisionSamplePose(
 31513                sourcePath.Start,
 31514                sourceStartRotation);
 31515            target.SetMixedContinuousCollisionSamplePose(
 31516                targetPath.Start,
 31517                targetSegment.StartRotation);
 31518            Vector3d sphereStart = sphere.Center;
 31519            Vector3d cylinderStart = new(
 31520                circle.Center.X,
 31521                circle.MixedSlabCenterY,
 31522                circle.Center.Y);
 31523            Vector3d cylinderDisplacement =
 31524                targetDisplacement.ToVector3d(Fixed64.Zero);
 31525            Vector3d relativeDisplacement =
 31526                ContinuousCollisionSweepRange.ValidateRelativeDisplacement(
 31527                    sourceDisplacement,
 31528                    cylinderDisplacement,
 31529                    out Fixed64 totalDistance);
 31530            if (!WideFiniteAxisIntersection
 31531                    .TryGetSphericallyExpandedFiniteCylinderDirectionFirstDistanceFromHalfAxisLength(
 31532                        sphereStart,
 31533                        relativeDisplacement,
 31534                        cylinderStart,
 31535                        Vector3d.Up,
 31536                        circle.MixedHalfThickness,
 31537                        circle.ScaledRadius,
 31538                        sphere.ScaledRadius,
 31539                        totalDistance,
 31540                        out Fixed64 contactDistance))
 541            {
 1542                requiresRotationalFallback = false;
 1543                return false;
 544            }
 545
 30546            relativeContactDistance = contactDistance;
 30547            _ = Fixed64.TryMultiplyDivide(
 30548                sourceTravelDistance,
 30549                contactDistance,
 30550                totalDistance,
 30551                out sourceContactDistance);
 30552            Fixed64 candidateDistance = contactDistance;
 64553            for (int attempt = 0; attempt < 2; attempt++)
 554            {
 32555                _ = Fixed64.TryMultiplyDivide(
 32556                    sourceTravelDistance,
 32557                    candidateDistance,
 32558                    totalDistance,
 32559                    out Fixed64 sampleSourceDistance);
 32560                _ = Fixed64.TryMultiplyDivide(
 32561                    targetTravelDistance,
 32562                    candidateDistance,
 32563                    totalDistance,
 32564                    out Fixed64 targetContactDistance);
 32565                SetMixedContinuousCollisionSamplePose(
 32566                    sourcePath.GetPointAtDistance(
 32567                        sampleSourceDistance,
 32568                        sourceTravelDistance),
 32569                    sourceStartRotation);
 32570                target.SetMixedContinuousCollisionSamplePose(
 32571                    targetPath.GetPointAtDistance(
 32572                        targetContactDistance,
 32573                        targetTravelDistance),
 32574                    targetSegment.StartRotation);
 32575                if (CollisionDetectionMixed.TryCollide(
 32576                    Collider,
 32577                    target.Collider,
 32578                    out contact))
 579                {
 30580                    bool closingResolved = Vector3d.TryDot(
 30581                            relativeDisplacement,
 30582                            contact.Normal3DTo2D,
 30583                            out localClosingSpeed);
 30584                    if (closingResolved
 30585                        && localClosingSpeed > Fixed64.Epsilon)
 586                    {
 28587                        return true;
 588                    }
 589
 2590                    if (closingResolved)
 2591                        requiresRotationalFallback = false;
 592
 2593                    break;
 594                }
 595
 2596                if (candidateDistance >= totalDistance)
 597                    break;
 598
 2599                candidateDistance = FixedMath.Min(
 2600                    totalDistance,
 2601                    candidateDistance + Fixed64.MinIncrement);
 602            }
 603
 2604            relativeContactDistance = default;
 2605            sourceContactDistance = default;
 2606            localClosingSpeed = default;
 2607            contact = default;
 2608            return false;
 609        }
 610        finally
 611        {
 31612            SetMixedContinuousCollisionSamplePose(
 31613                originalSourcePosition,
 31614                originalSourceRotation);
 31615            target.SetMixedContinuousCollisionSamplePose(
 31616                originalTargetPosition,
 31617                originalTargetRotation);
 31618        }
 31619    }
 620
 621    private bool IsValidMixedRotationalContinuousCollisionTarget(LSCollider2D target)
 622    {
 410623        return target.IsActive
 410624            && !target.IsTrigger
 410625            && !ContinuousCollisionCandidateOrdering.IsIgnoredTarget(
 410626                target,
 410627                _continuousCollisionHandoffIgnoredCollider2D)
 410628            && Context.MixedCollisions.RequireCollisionPair(Collider, target);
 629    }
 630
 631    private bool IsMovingMixedRotationalContinuousCollisionTarget(SolidBody2D target)
 632    {
 291633        if (target.IsKinematic)
 27634            target.EnsureContinuousCollisionFramePrepared(Context.LateSimulateToken);
 635
 291636        return (IsKinematic || !target.ShouldOwnContinuousCollisionMovingPair(
 291637                HasContinuousCollisionRotationalMotion))
 291638            && IsValidMixedRotationalContinuousCollisionTarget(target.Collider)
 291639            && (target.IsKinematic
 291640                ? target.HasContinuousCollisionMotion
 291641                : target.IsDynamic);
 642    }
 643
 644    internal bool TryApplyMixedRotationalContinuousCollisionResponse(
 645        LSCollider2D targetCollider,
 646        MixedContact contact,
 647        Fixed64 localContactTime,
 648        Vector3d sourceSegmentStart,
 649        Vector3d sourceDisplacement,
 650        FixedQuaternion sourceSegmentStartRotation,
 651        FixedQuaternion sourceSegmentTargetRotation,
 652        Fixed64 elapsedTime,
 653        Fixed64 remainingTime,
 654        bool sourceIsKinematic)
 655    {
 54656        Fixed64 frameFraction = ResolveRotationalFrameFraction(
 54657            elapsedTime,
 54658            remainingTime,
 54659            localContactTime);
 54660        Fixed64 consumedTime = remainingTime * localContactTime;
 54661        Fixed64 impactElapsedTime = elapsedTime + consumedTime;
 54662        Fixed64 remainingAfterImpact = remainingTime - consumedTime;
 54663        Vector3d sourcePosition = sourceSegmentStart
 54664            + sourceDisplacement * localContactTime;
 54665        FixedQuaternion sourceRotation = sourceIsKinematic
 54666            ? FixedQuaternion.Slerp(
 54667                sourceSegmentStartRotation,
 54668                sourceSegmentTargetRotation,
 54669                localContactTime).Normalized
 54670            : IntegrateAngularRotation(
 54671                sourceSegmentStartRotation,
 54672                _angularVelocity,
 54673                consumedTime);
 54674        Vector3d sourceLinearVelocity = sourceIsKinematic
 54675            ? SampleContinuousCollisionLinearVelocity(frameFraction)
 54676            : _linearVelocity;
 54677        Vector3d sourceAngularVelocity = sourceIsKinematic
 54678            ? SampleContinuousCollisionAngularVelocity(frameFraction)
 54679            : _angularVelocity;
 680
 54681        SolidBody2D? target = targetCollider.Body;
 54682        Vector2d targetPosition = target?.SampleContinuousCollisionPosition(frameFraction)
 54683            ?? targetCollider.Center;
 54684        Fixed64 targetRotation = target?.SampleContinuousCollisionRotation(frameFraction)
 54685            ?? Fixed64.Zero;
 54686        Vector2d targetLinearVelocity = target?.SampleContinuousCollisionLinearVelocity(frameFraction)
 54687            ?? Vector2d.Zero;
 54688        Fixed64 targetAngularVelocity = target?.SampleContinuousCollisionAngularVelocity(frameFraction)
 54689            ?? Fixed64.Zero;
 54690        Vector2d targetLocalCenterOfMassOffset =
 54691            target?.LocalCenterOfMassOffset ?? Vector2d.Zero;
 54692        SolidBody? sourceResponseBody = HasSolverMobility ? this : null;
 54693        SolidBody2D? targetResponseBody = target?.HasSolverMobility == true ? target : null;
 54694        Fixed64 restitution = PhysicsMaterial.CombineRestitution(
 54695            Collider.Material,
 54696            targetCollider.Material);
 54697        var sourceCenter = new ContactAnchor(
 54698            sourcePosition,
 54699            sourceRotation,
 54700            LocalCenterOfMassOffset);
 54701        bool sourceContactArmResolved = contact.Anchor3D.TryGetOffsetFrom(
 54702            sourceCenter,
 54703            out Vector3d sourceContactArm);
 54704        bool targetContactArmResolved =
 54705            contact.TryGetPlanarOffset2DFrom(
 54706                targetPosition,
 54707                targetRotation,
 54708                targetLocalCenterOfMassOffset,
 54709                out Vector2d targetContactArm);
 54710        ContactNormalVelocityDeltaResultMixed response = default;
 54711        bool responseResolved = sourceContactArmResolved
 54712            && targetContactArmResolved
 54713            && ContactNormalImpulseMixed.CanUseCompactResponse(
 54714                sourceResponseBody,
 54715                sourceLinearVelocity,
 54716                sourceAngularVelocity,
 54717                sourceContactArm,
 54718                targetResponseBody,
 54719                targetLinearVelocity,
 54720                targetAngularVelocity,
 54721                targetContactArm,
 54722                contact.Normal3DTo2D)
 54723            && ContactNormalImpulseMixed.TryCalculateVelocityDeltas(
 54724                sourceResponseBody,
 54725                sourceLinearVelocity,
 54726                sourceAngularVelocity,
 54727                sourceContactArm,
 54728                targetResponseBody,
 54729                targetLinearVelocity,
 54730                targetAngularVelocity,
 54731                targetContactArm,
 54732                contact.Normal3DTo2D,
 54733                restitution,
 54734                Context.Settings.RestitutionVelocityThreshold,
 54735                out response);
 54736        if (!responseResolved)
 737        {
 47738            ExactLever3D exactSource =
 47739                contact.Anchor3D.GetLeverFrom(sourceCenter);
 47740            ExactLever3D exactTarget =
 47741                contact.GetPlanarXZLeverFrom(
 47742                    targetPosition,
 47743                    targetRotation,
 47744                    targetLocalCenterOfMassOffset);
 47745            responseResolved =
 47746                ContactNormalImpulseMixed.TryCalculateVelocityDeltasExact(
 47747                    sourceResponseBody,
 47748                    sourceLinearVelocity,
 47749                    sourceAngularVelocity,
 47750                    exactSource,
 47751                    targetResponseBody,
 47752                    targetLinearVelocity,
 47753                    targetAngularVelocity,
 47754                    exactTarget,
 47755                    contact.Normal3DTo2D,
 47756                    restitution,
 47757                    Context.Settings.RestitutionVelocityThreshold,
 47758                    out response);
 759        }
 54760        if (!responseResolved || !response.IsClosing)
 2761            return false;
 52762        if (response.HasRepresentableNormalVelocity
 52763            && response.NormalVelocity >= -Fixed64.Epsilon)
 1764            return false;
 765
 51766        Vector3d postSourceLinearVelocity = sourceLinearVelocity;
 51767        Vector3d postSourceAngularVelocity = sourceAngularVelocity;
 51768        Vector2d postTargetLinearVelocity = targetLinearVelocity;
 51769        Fixed64 postTargetAngularVelocity = targetAngularVelocity;
 51770        bool sourceStateAvailable = true;
 51771        if (sourceResponseBody != null)
 772        {
 7773            bool sourceLinearVelocityResolved = Vector3d.TryAdd(
 7774                sourceLinearVelocity,
 7775                response.LinearVelocityDelta3D,
 7776                out postSourceLinearVelocity);
 7777            bool sourceAngularVelocityResolved = Vector3d.TryAdd(
 7778                sourceAngularVelocity,
 7779                response.AngularVelocityDelta3D,
 7780                out postSourceAngularVelocity);
 7781            bool sourceTrajectoryAvailable = CanAppendContinuousCollisionSegment(
 7782                impactElapsedTime);
 7783            sourceStateAvailable = sourceLinearVelocityResolved
 7784                & sourceAngularVelocityResolved
 7785                & sourceTrajectoryAvailable;
 786        }
 787
 51788        bool targetStateAvailable = true;
 51789        if (targetResponseBody != null)
 790        {
 46791            bool targetLinearVelocityResolved = Vector2d.TryAdd(
 46792                targetLinearVelocity,
 46793                response.LinearVelocityDelta2D,
 46794                out postTargetLinearVelocity);
 46795            bool targetAngularVelocityResolved = Fixed64.TryAdd(
 46796                targetAngularVelocity,
 46797                response.AngularVelocityDelta2D,
 46798                out postTargetAngularVelocity);
 46799            bool targetTrajectoryAvailable = targetResponseBody.CanApplyContinuousCollisionHandoffState(
 46800                targetPosition,
 46801                targetRotation,
 46802                remainingAfterImpact,
 46803                out targetPosition);
 46804            targetStateAvailable = targetLinearVelocityResolved
 46805                & targetAngularVelocityResolved
 46806                & targetTrajectoryAvailable;
 807        }
 808
 51809        if (!(sourceStateAvailable & targetStateAvailable))
 810        {
 1811            return false;
 812        }
 813
 50814        if (sourceResponseBody != null)
 6815            _ = Context.Physics.TryReserveContinuousCollisionCandidateRefresh(this);
 816
 50817        if (targetResponseBody != null)
 818        {
 45819            _ = Context.Physics2D.TryReserveContinuousCollisionCandidateRefresh(targetResponseBody);
 45820            targetResponseBody.ApplyContinuousCollisionHandoffStateReserved(
 45821                targetPosition,
 45822                targetRotation,
 45823                postTargetLinearVelocity,
 45824                postTargetAngularVelocity,
 45825                remainingAfterImpact,
 45826                ignoredCollider3D: Collider);
 827        }
 828
 50829        if (sourceResponseBody != null)
 830        {
 6831            ApplyCollisionVelocityState(
 6832                postSourceLinearVelocity,
 6833                postSourceAngularVelocity);
 6834            AppendContinuousCollisionSegment(
 6835                sourcePosition,
 6836                sourceRotation,
 6837                _linearVelocity,
 6838                _angularVelocity,
 6839                impactElapsedTime);
 6840            Context.Physics.RefreshContinuousCollisionCandidate(this);
 841        }
 842
 50843        return true;
 844    }
 845}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.ContinuousCollision.Rotational.Response.cs

#LineLine coverage
 1//=======================================================================
 2// SolidBody.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 SolidBody
 15{
 16    internal bool TryApplyRotationalContinuousCollisionResponse(
 17        SolidBody target,
 18        ManifoldContact contact,
 19        Fixed64 localContactTime,
 20        Vector3d sourceSegmentStart,
 21        Vector3d sourceDisplacement,
 22        FixedQuaternion sourceSegmentStartRotation,
 23        Fixed64 elapsedTime,
 24        Fixed64 remainingTime,
 25        bool sourceIsKinematic)
 26    {
 5927        Fixed64 frameFraction = ResolveRotationalFrameFraction(
 5928            elapsedTime,
 5929            remainingTime,
 5930            localContactTime);
 5931        Fixed64 consumedTime = remainingTime * localContactTime;
 5932        Fixed64 impactElapsedTime = elapsedTime + consumedTime;
 5933        Fixed64 remainingAfterImpact = remainingTime - consumedTime;
 5934        Vector3d sourcePosition = sourceSegmentStart
 5935            + sourceDisplacement * localContactTime;
 5936        FixedQuaternion sourceRotation = sourceIsKinematic
 5937            ? FixedQuaternion.Slerp(
 5938                sourceSegmentStartRotation,
 5939                ContinuousCollisionFrameTargetRotation,
 5940                localContactTime).Normalized
 5941            : IntegrateAngularRotation(
 5942                sourceSegmentStartRotation,
 5943                _angularVelocity,
 5944                consumedTime);
 5945        Vector3d sourceLinearVelocity = sourceIsKinematic
 5946            ? SampleContinuousCollisionLinearVelocity(frameFraction)
 5947            : _linearVelocity;
 5948        Vector3d sourceAngularVelocity = sourceIsKinematic
 5949            ? SampleContinuousCollisionAngularVelocity(frameFraction)
 5950            : _angularVelocity;
 5951        Vector3d targetPosition = target.SampleContinuousCollisionPosition(frameFraction);
 5952        FixedQuaternion targetRotation = target.SampleContinuousCollisionRotation(frameFraction);
 5953        Vector3d targetLinearVelocity = target.SampleContinuousCollisionLinearVelocity(frameFraction);
 5954        Vector3d targetAngularVelocity = target.SampleContinuousCollisionAngularVelocity(frameFraction);
 55
 5956        OrderRotationalContinuousCollisionPair(
 5957            target.Collider,
 5958            out _,
 5959            out _,
 5960            out bool sourceIsA);
 5961        SolidBody bodyA = sourceIsA ? this : target;
 5962        SolidBody bodyB = sourceIsA ? target : this;
 5963        Vector3d positionA = sourceIsA ? sourcePosition : targetPosition;
 5964        Vector3d positionB = sourceIsA ? targetPosition : sourcePosition;
 5965        FixedQuaternion rotationA = sourceIsA ? sourceRotation : targetRotation;
 5966        FixedQuaternion rotationB = sourceIsA ? targetRotation : sourceRotation;
 5967        Vector3d linearVelocityA = sourceIsA ? sourceLinearVelocity : targetLinearVelocity;
 5968        Vector3d linearVelocityB = sourceIsA ? targetLinearVelocity : sourceLinearVelocity;
 5969        Vector3d angularVelocityA = sourceIsA ? sourceAngularVelocity : targetAngularVelocity;
 5970        Vector3d angularVelocityB = sourceIsA ? targetAngularVelocity : sourceAngularVelocity;
 5971        ContactLever3D contactArmA = ContactLever3D.Create(
 5972            contact.AnchorA,
 5973            new ContactAnchor(
 5974                positionA,
 5975                rotationA,
 5976                bodyA._localCenterOfMassOffset));
 5977        ContactLever3D contactArmB = ContactLever3D.Create(
 5978            contact.AnchorB,
 5979            new ContactAnchor(
 5980                positionB,
 5981                rotationB,
 5982                bodyB._localCenterOfMassOffset));
 83
 5984        Fixed64 restitution = PhysicsMaterial.CombineRestitution(
 5985            Collider.Material,
 5986            target.Collider.Material);
 5987        Fixed3x3 originalSourceInverseInertia = _inverseInertiaTensor;
 5988        Fixed3x3 originalTargetInverseInertia = target._inverseInertiaTensor;
 89        ContactNormalVelocityDeltaResult3D result;
 90        bool calculated;
 91        try
 92        {
 5993            _inverseInertiaTensor = ResolveContinuousCollisionInverseInertia(sourceRotation);
 5994            target._inverseInertiaTensor = target.ResolveContinuousCollisionInverseInertia(targetRotation);
 5995            if (contactArmA.IsExact || contactArmB.IsExact)
 96            {
 397                calculated = TryCalculateExactRotationalVelocityDeltas(
 398                    contact,
 399                    bodyA,
 3100                    positionA,
 3101                    rotationA,
 3102                    linearVelocityA,
 3103                    angularVelocityA,
 3104                    bodyB,
 3105                    positionB,
 3106                    rotationB,
 3107                    linearVelocityB,
 3108                    angularVelocityB,
 3109                    restitution,
 3110                    out result);
 111            }
 112            else
 113            {
 56114                calculated = ContactNormalImpulse3D.TryCalculateVelocityDeltas(
 56115                    bodyA,
 56116                    linearVelocityA,
 56117                    angularVelocityA,
 56118                    contactArmA.Vector,
 56119                    bodyB,
 56120                    linearVelocityB,
 56121                    angularVelocityB,
 56122                    contactArmB.Vector,
 56123                    contact.Normal,
 56124                    restitution,
 56125                    Context.Settings.RestitutionVelocityThreshold,
 56126                    out result);
 56127                if (!calculated)
 128                {
 44129                    calculated = TryCalculateExactRotationalVelocityDeltas(
 44130                        contact,
 44131                        bodyA,
 44132                        positionA,
 44133                        rotationA,
 44134                        linearVelocityA,
 44135                        angularVelocityA,
 44136                        bodyB,
 44137                        positionB,
 44138                        rotationB,
 44139                        linearVelocityB,
 44140                        angularVelocityB,
 44141                        restitution,
 44142                        out result);
 143                }
 144            }
 56145        }
 146        finally
 147        {
 59148            _inverseInertiaTensor = originalSourceInverseInertia;
 59149            target._inverseInertiaTensor = originalTargetInverseInertia;
 59150        }
 151
 59152        if (!calculated
 59153            || !result.IsClosing
 59154            || (result.HasRepresentableNormalVelocity
 59155                && result.NormalVelocity >= -Fixed64.Epsilon))
 156        {
 4157            return false;
 158        }
 159
 55160        Vector3d sourceLinearDelta = sourceIsA
 55161            ? result.LinearVelocityDeltaA
 55162            : result.LinearVelocityDeltaB;
 55163        Vector3d sourceAngularDelta = sourceIsA
 55164            ? result.AngularVelocityDeltaA
 55165            : result.AngularVelocityDeltaB;
 55166        Vector3d targetLinearDelta = sourceIsA
 55167            ? result.LinearVelocityDeltaB
 55168            : result.LinearVelocityDeltaA;
 55169        Vector3d targetAngularDelta = sourceIsA
 55170            ? result.AngularVelocityDeltaB
 55171            : result.AngularVelocityDeltaA;
 55172        bool sourceNeedsRefresh = HasSolverMobility;
 55173        bool targetNeedsRefresh = target.HasSolverMobility;
 55174        Vector3d postSourceLinearVelocity = sourceLinearVelocity;
 55175        Vector3d postSourceAngularVelocity = sourceAngularVelocity;
 55176        bool sourceStateResolved = !sourceNeedsRefresh
 55177            || (Vector3d.TryAdd(
 55178                    sourceLinearVelocity,
 55179                    ProjectLinearMotion(sourceLinearDelta),
 55180                    out postSourceLinearVelocity)
 55181                & Vector3d.TryAdd(
 55182                    sourceAngularVelocity,
 55183                    ProjectAngularMotion(sourceAngularDelta),
 55184                    out postSourceAngularVelocity)
 55185                & CanAppendContinuousCollisionSegment(impactElapsedTime));
 55186        if (!sourceStateResolved)
 187        {
 1188            return false;
 189        }
 190
 54191        Vector3d resolvedTargetPosition = default;
 54192        Vector3d resolvedTargetLinearVelocity = targetLinearVelocity;
 54193        Vector3d resolvedTargetAngularVelocity = targetAngularVelocity;
 54194        bool targetStateResolved = !targetNeedsRefresh
 54195            || (Vector3d.TryAdd(
 54196                    targetLinearVelocity,
 54197                    target.ProjectLinearMotion(targetLinearDelta),
 54198                    out resolvedTargetLinearVelocity)
 54199                & Vector3d.TryAdd(
 54200                    targetAngularVelocity,
 54201                    target.ProjectAngularMotion(targetAngularDelta),
 54202                    out resolvedTargetAngularVelocity)
 54203                & target.CanApplyContinuousCollisionHandoff(
 54204                    targetPosition,
 54205                    targetRotation,
 54206                    remainingAfterImpact,
 54207                    out resolvedTargetPosition));
 54208        if (!targetStateResolved)
 209        {
 1210            return false;
 211        }
 212
 53213        if (sourceNeedsRefresh && targetNeedsRefresh)
 7214            _ = Context.Physics.TryReserveContinuousCollisionCandidateRefresh(this, target);
 46215        else if (sourceNeedsRefresh)
 6216            _ = Context.Physics.TryReserveContinuousCollisionCandidateRefresh(this);
 217        else
 40218            _ = Context.Physics.TryReserveContinuousCollisionCandidateRefresh(target);
 219
 53220        if (sourceNeedsRefresh)
 221        {
 13222            ApplyCollisionVelocityState(
 13223                postSourceLinearVelocity,
 13224                postSourceAngularVelocity);
 13225            AppendContinuousCollisionSegment(
 13226                sourcePosition,
 13227                sourceRotation,
 13228                _linearVelocity,
 13229                _angularVelocity,
 13230                impactElapsedTime);
 13231            Context.Physics.RefreshContinuousCollisionCandidate(this);
 232        }
 233
 53234        if (targetNeedsRefresh)
 235        {
 47236            target.ApplyContinuousCollisionHandoffReserved(
 47237                resolvedTargetPosition,
 47238                targetRotation,
 47239                resolvedTargetLinearVelocity,
 47240                resolvedTargetAngularVelocity,
 47241                remainingAfterImpact,
 47242                ignoredCollider3D: Collider);
 243        }
 244
 53245        return true;
 246    }
 247
 248    [System.Runtime.CompilerServices.MethodImpl(
 249        System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
 250    private bool TryCalculateExactRotationalVelocityDeltas(
 251        ManifoldContact contact,
 252        SolidBody bodyA,
 253        Vector3d positionA,
 254        FixedQuaternion rotationA,
 255        Vector3d linearVelocityA,
 256        Vector3d angularVelocityA,
 257        SolidBody bodyB,
 258        Vector3d positionB,
 259        FixedQuaternion rotationB,
 260        Vector3d linearVelocityB,
 261        Vector3d angularVelocityB,
 262        Fixed64 restitution,
 263        out ContactNormalVelocityDeltaResult3D result)
 264    {
 47265        var centerA = new ContactAnchor(
 47266            positionA,
 47267            rotationA,
 47268            bodyA._localCenterOfMassOffset);
 47269        var centerB = new ContactAnchor(
 47270            positionB,
 47271            rotationB,
 47272            bodyB._localCenterOfMassOffset);
 47273        ExactLever3D exactA =
 47274            contact.AnchorA.GetLeverFrom(centerA);
 47275        ExactLever3D exactB =
 47276            contact.AnchorB.GetLeverFrom(centerB);
 47277        return ContactNormalImpulse3D.TryCalculateVelocityDeltasExact(
 47278                bodyA,
 47279                linearVelocityA,
 47280                angularVelocityA,
 47281                exactA,
 47282                bodyB,
 47283                linearVelocityB,
 47284                angularVelocityB,
 47285                exactB,
 47286                contact.Normal,
 47287                restitution,
 47288                Context.Settings.RestitutionVelocityThreshold,
 47289                out result);
 290    }
 291
 292    private Fixed3x3 ResolveContinuousCollisionInverseInertia(FixedQuaternion rotation)
 293    {
 118294        if (_inverseLocalInertiaTensor == Fixed3x3.Zero)
 52295            return Fixed3x3.Zero;
 296
 66297        Fixed3x3 orientation = rotation.ToMatrix3x3();
 66298        return orientation
 66299            * _inverseLocalInertiaTensor
 66300            * rotation.Conjugate().ToMatrix3x3();
 301    }
 302
 303    private void StopRotationalContinuousCollision(Vector3d contactNormal)
 304    {
 38305        Vector3d lastVelocity = _angularVelocity;
 38306        _angularVelocity = Vector3d.Zero;
 38307        _angularDirection = Vector3d.Zero;
 38308        _angularAccelerationStore = Vector3d.Zero;
 38309        _angularAcceleration = Vector3d.Zero;
 38310        _deltaTorque = Vector3d.Zero;
 38311        RefreshAngularMotionState(lastVelocity);
 38312        Context.Diagnostics.EmitAngularVelocityDelta(this, lastVelocity, _angularVelocity);
 38313        RemoveClosingContinuousCollisionVelocity(contactNormal);
 38314    }
 315}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.ContinuousCollision.Rotational.Search.cs

#LineLine coverage
 1//=======================================================================
 2// SolidBody.ContinuousCollision.Rotational.Search.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;
 14
 15namespace Gravitas;
 16
 17public partial class SolidBody
 18{
 19    internal bool HasNearbyRotationalContinuousCollisionTarget(
 20        Vector3d startPosition,
 21        Vector3d displacement,
 22        Fixed64 pivotRadius)
 23    {
 657924        if (pivotRadius == Fixed64.MaxValue)
 25        {
 426            int colliderCount = Context.Physics.ColliderCount;
 2227            for (int i = 0; i < colliderCount; i++)
 28            {
 829                if (IsRotatingContinuousCollisionTarget(
 830                    Context.Physics.GetColliderByServiceIndex(i).Body))
 31                {
 132                    return true;
 33                }
 34            }
 35
 336            return false;
 37        }
 38
 657539        SwiftList<int> candidateIds = Context.Physics.QueryContinuousCollisionCandidates(
 657540            DynamicCcdCandidateIndex.CreateSweptSphereBounds(
 657541                startPosition,
 657542                displacement,
 657543                pivotRadius));
 2666044        for (int i = 0; i < candidateIds.Count; i++)
 45        {
 675946            SolidBody target = Context.Physics.GetContinuousCollisionCandidate(candidateIds[i]);
 675947            if (IsRotatingContinuousCollisionTarget(target))
 48            {
 449                return true;
 50            }
 51        }
 52
 657153        return false;
 54    }
 55
 56    private bool IsRotatingContinuousCollisionTarget(SolidBody? target)
 57    {
 676758        if (target == null
 676759            || ReferenceEquals(target, this)
 676760            || !IsMovingRotationalContinuousCollisionTarget(target))
 61        {
 665062            return false;
 63        }
 64
 11765        target.EnsureContinuousCollisionFramePrepared(Context.LateSimulateToken);
 11766        return target.HasContinuousCollisionRotationalMotion;
 67    }
 68
 69    internal int GatherRotationalContinuousCollisionCandidates(
 70        Vector3d startPosition,
 71        Vector3d proposedPosition,
 72        Vector3d displacement,
 73        Fixed64 pivotRadius)
 74    {
 37175        _rotationalContinuousCollisionCandidateIds.FastClear();
 37176        if (pivotRadius == Fixed64.MaxValue)
 677            return GatherAllRegisteredRotationalContinuousCollisionCandidates();
 78
 36579        int staticHitCount = displacement.MagnitudeSquared <= Fixed64.Epsilon
 36580            ? Context.Query3D.OverlapSphereAgainstStaticAll(
 36581                startPosition,
 36582                pivotRadius,
 36583                PhysicsLayerMask.All,
 36584                _continuousCollisionHits,
 36585                Collider,
 36586                includeTriggers: false)
 36587            : Context.Query3D.SweepSphereAgainstStaticAll(
 36588                startPosition,
 36589                proposedPosition,
 36590                pivotRadius,
 36591                PhysicsLayerMask.All,
 36592                _continuousCollisionHits,
 36593                Collider,
 36594                includeTriggers: false);
 95
 36596        SwiftList<int> candidateIds = Context.Physics.QueryContinuousCollisionCandidates(
 36597            DynamicCcdCandidateIndex.CreateSweptSphereBounds(
 36598                startPosition,
 36599                displacement,
 365100                pivotRadius));
 365101        _rotationalContinuousCollisionCandidateIds.EnsureCapacity(candidateIds.Count);
 1790102        for (int i = 0; i < candidateIds.Count; i++)
 530103            _rotationalContinuousCollisionCandidateIds.Add(candidateIds[i]);
 104
 365105        return staticHitCount + _rotationalContinuousCollisionCandidateIds.Count;
 106    }
 107
 108    private int GatherAllRegisteredRotationalContinuousCollisionCandidates()
 109    {
 6110        _continuousCollisionHits.FastClear();
 6111        _rotationalContinuousCollisionCandidateIds.FastClear();
 6112        int colliderCount = Context.Physics.ColliderCount;
 6113        _continuousCollisionHits.EnsureCapacity(colliderCount);
 30114        for (int serviceIndex = 0; serviceIndex < colliderCount; serviceIndex++)
 115        {
 9116            LSCollider target = Context.Physics.GetColliderByServiceIndex(serviceIndex);
 9117            if (target.Body is SolidBody targetBody
 9118                && IsMovingRotationalContinuousCollisionTarget(targetBody))
 119            {
 1120                _rotationalContinuousCollisionCandidateIds.Add(targetBody.DynamicId);
 1121                continue;
 122            }
 123
 8124            if (!IsValidContinuousCollisionTarget(target))
 125                continue;
 126
 2127            _continuousCollisionHits.Add(new Physics3DHit(
 2128                target,
 2129                target.Center,
 2130                Vector3d.Zero,
 2131                Fixed64.Zero,
 2132                Vector3d.Zero));
 133        }
 134
 6135        return _continuousCollisionHits.Count
 6136            + _rotationalContinuousCollisionCandidateIds.Count;
 137    }
 138
 139    private bool TryFindEarliestRotationalContinuousCollision(
 140        Vector3d startPosition,
 141        Vector3d displacement,
 142        FixedQuaternion startRotation,
 143        FixedQuaternion targetRotation,
 144        Fixed64 angularDistance,
 145        Fixed64 pivotRadius,
 146        Fixed64 elapsedTime,
 147        Fixed64 remainingTime,
 148        bool isKinematic,
 149        LSCollider? ignoredTarget,
 150        out Fixed64 safeTime,
 151        out ManifoldContact contact,
 152        out bool hasContact,
 153        out Fixed64 contactTime,
 154        out LSCollider? hitTarget)
 155    {
 369156        safeTime = Fixed64.Zero;
 369157        contact = default;
 369158        hasContact = false;
 369159        contactTime = Fixed64.Zero;
 369160        hitTarget = null;
 161
 369162        bool foundCollision = false;
 369163        Fixed64 earliestTime = Fixed64.One;
 369164        int earliestTargetId = int.MaxValue;
 369165        ManifoldContact earliestContact = default;
 369166        bool earliestHasContact = false;
 369167        Fixed64 earliestContactTime = Fixed64.Zero;
 369168        LSCollider? earliestTarget = null;
 169
 868170        for (int hitIndex = 0; hitIndex < _continuousCollisionHits.Count; hitIndex++)
 171        {
 65172            LSCollider target = _continuousCollisionHits[hitIndex].Collider!;
 65173            if (target == ignoredTarget
 65174                || !IsValidContinuousCollisionTarget(target)
 65175                || ColliderSettings.GetCollisionType(Collider.Shape, target.Shape) == CollisionType.None
 65176                || !TryFindEarliestRotationalContinuousCollisionAgainstTarget(
 65177                    target,
 65178                    startPosition,
 65179                    displacement,
 65180                    startRotation,
 65181                    targetRotation,
 65182                    angularDistance,
 65183                    pivotRadius,
 65184                    elapsedTime,
 65185                    remainingTime,
 65186                    isKinematic,
 65187                    out Fixed64 candidateTime,
 65188                    out ManifoldContact candidateContact,
 65189                    out bool candidateHasContact,
 65190                    out Fixed64 candidateContactTime))
 191            {
 192                continue;
 193            }
 194
 54195            if (!ContinuousCollisionMath.ShouldReplaceContinuousCollisionHit(
 54196                    candidateTime,
 54197                    target.Id,
 54198                    foundCollision,
 54199                    earliestTime,
 54200                    earliestTargetId))
 201            {
 202                continue;
 203            }
 204
 53205            foundCollision = true;
 53206            earliestTime = candidateTime;
 53207            earliestTargetId = target.Id;
 53208            earliestContact = candidateContact;
 53209            earliestHasContact = candidateHasContact;
 53210            earliestContactTime = candidateContactTime;
 53211            earliestTarget = target;
 212        }
 213
 369214        for (int candidateIndex = 0;
 899215            candidateIndex < _rotationalContinuousCollisionCandidateIds.Count;
 530216            candidateIndex++)
 217        {
 530218            int dynamicId = _rotationalContinuousCollisionCandidateIds[candidateIndex];
 530219            SolidBody targetBody = Context.Physics.GetContinuousCollisionCandidate(dynamicId);
 530220            if (targetBody.Collider == ignoredTarget
 530221                || !IsMovingRotationalContinuousCollisionTarget(targetBody)
 530222                || ColliderSettings.GetCollisionType(
 530223                    Collider.Shape,
 530224                    targetBody.Collider.Shape) == CollisionType.None
 530225                || !TryFindEarliestRotationalContinuousCollisionAgainstTarget(
 530226                    targetBody.Collider,
 530227                    startPosition,
 530228                    displacement,
 530229                    startRotation,
 530230                    targetRotation,
 530231                    angularDistance,
 530232                    pivotRadius,
 530233                    elapsedTime,
 530234                    remainingTime,
 530235                    isKinematic,
 530236                    out Fixed64 candidateTime,
 530237                    out ManifoldContact candidateContact,
 530238                    out bool candidateHasContact,
 530239                    out Fixed64 candidateContactTime))
 240            {
 241                continue;
 242            }
 243
 105244            if (!ContinuousCollisionMath.ShouldReplaceContinuousCollisionHit(
 105245                    candidateTime,
 105246                    targetBody.Collider.Id,
 105247                    foundCollision,
 105248                    earliestTime,
 105249                    earliestTargetId))
 250            {
 251                continue;
 252            }
 253
 103254            foundCollision = true;
 103255            earliestTime = candidateTime;
 103256            earliestTargetId = targetBody.Collider.Id;
 103257            earliestContact = candidateContact;
 103258            earliestHasContact = candidateHasContact;
 103259            earliestContactTime = candidateContactTime;
 103260            earliestTarget = targetBody.Collider;
 261        }
 262
 369263        if (!foundCollision)
 217264            return false;
 265
 152266        safeTime = earliestTime;
 152267        contact = earliestContact;
 152268        hasContact = earliestHasContact;
 152269        contactTime = earliestContactTime;
 152270        hitTarget = earliestTarget;
 152271        return true;
 272    }
 273}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.cs

#LineLine coverage
 1//=======================================================================
 2// SolidBody.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 GridForge.Grids;
 14using SwiftCollections;
 15using System;
 16using System.Runtime.CompilerServices;
 17
 18namespace Gravitas;
 19
 20/// <summary>Represents deterministic 3D rigid-body state owned by one world context.</summary>
 21public partial class SolidBody : IRecordable
 22{
 23    /// <summary>Stores the host-controlled body diagnostic flag.</summary>
 24    public bool Debug = false;
 25
 26    /// <summary>Gets whether the body is initialized and registered.</summary>
 27    public bool Active { get; private set; }
 28
 507629    private int _dynamicId = -1;
 30
 31    /// <summary>
 32    /// Gets the ephemeral simulated-body slot, or <c>-1</c> for a static or
 33    /// unregistered body.
 34    /// </summary>
 8713435    public int DynamicId => _dynamicId;
 36
 37    private BodyFreezeAxes3D _freezeAxes;
 38
 39    /// <summary>
 40    /// Gets or sets the 3D translational and rotational degrees of freedom
 41    /// frozen for solver response, integration, and CCD.
 42    /// </summary>
 43    public BodyFreezeAxes3D FreezeAxes
 44    {
 45        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 9446        get => _freezeAxes;
 47        set
 48        {
 231249            SwiftThrowHelper.ThrowIfArgument(
 231250                (value & ~BodyFreezeAxes3D.All) != BodyFreezeAxes3D.None,
 231251                nameof(value),
 231252                "Unsupported 3D freeze axis bits.");
 53
 231254            if (_freezeAxes == value)
 208955                return;
 56
 22357            _freezeAxes = value;
 22358            ApplyFreezeConstraintsToMotion();
 22359            if (Active)
 15960                RefreshInertiaTensor();
 22361            RefreshPartitionMobility();
 22362        }
 63    }
 64
 65    /// <summary>
 66    /// Gets whether all translation axes are frozen.
 67    /// </summary>
 68    public bool IsPositionFullyFrozen
 69    {
 70        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 215566071        get => (_freezeAxes & BodyFreezeAxes3D.Position) == BodyFreezeAxes3D.Position;
 72    }
 73
 74    private BodyMotionType _motionType;
 75
 76    /// <summary>
 77    /// Gets this body's explicit solver-controlled, host-controlled, or static
 78    /// runtime role.
 79    /// </summary>
 28280    public BodyMotionType MotionType => _motionType;
 81
 82    /// <summary>
 83    /// Gets whether the solver controls this body.
 84    /// </summary>
 240070385    public bool IsDynamic => _motionType == BodyMotionType.Dynamic;
 86
 87    /// <summary>
 88    /// Gets whether this body is excluded from per-frame motion.
 89    /// </summary>
 10610290    public bool IsStatic => _motionType == BodyMotionType.Static;
 91
 92    /// <summary>
 93    /// Gets whether the host controls this body's pose.
 94    /// </summary>
 21741495    public bool IsKinematic => _motionType == BodyMotionType.Kinematic;
 96
 97    /// <summary>
 98    /// Changes this registered body's runtime role between fixed-step
 99    /// transactions.
 100    /// </summary>
 101    /// <remarks>
 102    /// The transition preserves body, collider, pair, joint, and host identity,
 103    /// but clears incompatible motion, sleep, CCD, and solver-cache state before
 104    /// repartitioning. Freeze axes remain independent of the selected role.
 105    /// </remarks>
 106    /// <exception cref="ArgumentOutOfRangeException">
 107    /// <paramref name="motionType"/> is undefined.
 108    /// </exception>
 109    /// <exception cref="InvalidOperationException">
 110    /// The body is not currently registered, its context registration has been
 111    /// reset, or a simulation transaction or callback is active.
 112    /// </exception>
 113    public void SetMotionType(BodyMotionType motionType)
 114    {
 43115        if (!PrepareMotionTypeTransition(motionType))
 1116            return;
 117
 34118        CommitMotionTypeTransition(motionType);
 34119    }
 120
 121    internal bool PrepareMotionTypeTransition(BodyMotionType motionType)
 122    {
 153123        motionType.ThrowIfInvalid(nameof(motionType));
 152124        ThrowIfRuntimeRegistrationMissing();
 148125        if (_motionType == motionType)
 93126            return false;
 127
 55128        Context.ThrowIfFixedStepMutationNotAllowed();
 53129        Collider.ValidateCurrentRuntimeTransform();
 51130        if (motionType != BodyMotionType.Dynamic)
 37131            PublishAuthoritativePose();
 132
 51133        return true;
 134    }
 135
 136    internal void CommitMotionTypeTransition(BodyMotionType motionType)
 137    {
 50138        ReconcileMotionTypeRegistration(motionType);
 139
 50140        ClearMotionForSleep();
 50141        _isSleeping = false;
 50142        _sleepFrameCount = 0;
 50143        InvalidateContinuousCollisionTrajectory();
 50144        ApplyFreezeConstraintsToMotion();
 50145        RefreshInertiaTensor();
 50146        RefreshPartitionMobility();
 50147    }
 148
 149    internal void ApplyLoadedMotionType(BodyMotionType motionType)
 150    {
 51151        motionType.ThrowIfInvalid(nameof(motionType));
 51152        if (_motionType == motionType)
 33153            return;
 154
 18155        if (!Active)
 156        {
 4157            _motionType = motionType;
 4158            return;
 159        }
 160
 14161        ReconcileMotionTypeRegistration(motionType);
 14162        InvalidateContinuousCollisionTrajectory();
 14163    }
 164
 165    internal void PreflightLoadedMotionType(BodyMotionType motionType)
 166    {
 55167        motionType.ThrowIfInvalid(nameof(motionType));
 54168        if (!Active)
 6169            return;
 170
 48171        ThrowIfRuntimeRegistrationMissing();
 46172        Context.ThrowIfFixedStepMutationNotAllowed();
 46173        Collider.ValidateCurrentRuntimeTransform();
 46174    }
 175
 176    private void ThrowIfRuntimeRegistrationMissing()
 177    {
 6698178        SwiftThrowHelper.ThrowIfTrue(
 6698179            !Active,
 6698180            nameof(SolidBody),
 6698181            "Body runtime state cannot change before initialization or after deactivation.");
 6696182        SwiftThrowHelper.ThrowIfTrue(
 6696183            !Context.Physics.TryGetColliderById(Collider.Id, out LSCollider? registeredCollider)
 6696184                || !ReferenceEquals(registeredCollider, Collider),
 6696185            nameof(SolidBody),
 6696186            "Body runtime state cannot change after its registration has been reset or replaced.");
 6690187    }
 188
 189    private void ReconcileMotionTypeRegistration(BodyMotionType motionType)
 190    {
 64191        BodyMotionType previousMotionType = _motionType;
 64192        Context.Physics.ClearWarmStartCachesForCollider(Collider);
 64193        Context.Constraints3D.ClearSolverCachesForBody(this);
 64194        Context.Physics.InvalidateContinuousCollisionStateForMotionTypeChange(this, DynamicId);
 195
 64196        _motionType = motionType;
 64197        Context.Physics.RefreshBodyMotionTypeRegistration(this, previousMotionType);
 64198        Context.Physics.RefreshColliderServiceRefreshRegistration(Collider);
 64199    }
 200
 29201    internal void SetDynamicId(int dynamicId) => _dynamicId = dynamicId;
 202
 203    private ContinuousCollisionMode _continuousCollisionMode = ContinuousCollisionMode.Inherit;
 5076204    private int _continuousCollisionFrameToken = int.MinValue;
 5076205    private readonly SwiftList<ContinuousCollisionMotionSegment3D> _continuousCollisionTrajectory =
 5076206        new(PhysicsSettings.DefaultContinuousCollisionMaxToiIterations + 1);
 207    private Vector3d _continuousCollisionAngularVelocityStepStart;
 208    private bool _continuousCollisionHandoffPending;
 5076209    private int _continuousCollisionHandoffToken = int.MinValue;
 210    private Fixed64 _continuousCollisionHandoffRemainingTime;
 211
 212    /// <summary>
 213    /// Selects the deterministic tunneling guard used when this body commits frame movement.
 214    /// Inherited values resolve through the cached top-parent body before falling back to context settings.
 215    /// </summary>
 216    /// <exception cref="ArgumentOutOfRangeException">The value is not a declared continuous-collision mode.</exception>
 217    public ContinuousCollisionMode ContinuousCollisionMode
 218    {
 219        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 94220        get => _continuousCollisionMode;
 221        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 222        set
 223        {
 673224            value.ThrowIfInvalid(nameof(value));
 669225            _continuousCollisionMode = value;
 669226        }
 227    }
 228
 229    /// <summary>
 230    /// Gets the number of continuous-collision impacts consumed by the most recent late simulation step.
 231    /// </summary>
 232    public int LastContinuousCollisionToiIterationCount { get; private set; }
 233
 234    /// <summary>
 235    /// Gets whether the most recent late simulation step reached the configured continuous-collision TOI iteration limi
 236    /// </summary>
 237    public bool LastContinuousCollisionToiIterationLimitReached { get; private set; }
 238
 239    private FixedTransform _positionTransform = null!;
 240    /// <summary>Gets the host transform that receives authoritative or visual position updates.</summary>
 96241    public FixedTransform PositionTransform => _positionTransform;
 242
 243    private FixedTransform _rotationTransform = null!;
 244    /// <summary>Gets the host transform that receives authoritative or visual rotation updates.</summary>
 24245    public FixedTransform RotationTransform => _rotationTransform;
 246
 247    #region Position Properties
 248
 249    private bool _positionMutated;
 250    private bool _positionChangedBuffer;
 251    /// <summary>Gets whether an authoritative position change awaits presentation.</summary>
 13644252    public bool PositionChangePending => _positionMutated || _positionChangedBuffer;
 253
 254    private Vector2d _position2dUnmarked;
 255    private void SetPosition2d(Vector2d value)
 256    {
 19330257        if (_position2dUnmarked == value)
 1645258            return;
 259
 17685260        _position2dUnmarked = value;
 17685261        _positionMutated = true;
 17685262    }
 263
 264    /// <summary>Gets the authoritative world-space position.</summary>
 265    public Vector3d Position3d
 266    {
 510197267        get => _position2dUnmarked.ToVector3d(_heightPosUnmarked);
 268        private set
 269        {
 27444270            if (Position3d == value)
 25354271                return;
 2090272            _position2dUnmarked.Set(value.X, value.Z);
 2090273            _heightPosUnmarked = value.Y;
 2090274            _positionMutated = true;
 2090275        }
 276    }
 277
 5076278    private Fixed64 _heightPosUnmarked = Fixed64.Zero;  // Actor's transform position Y
 279    /// <summary>Gets the authoritative world-space Y coordinate.</summary>
 280    public Fixed64 HeightPos
 281    {
 7282        get => _heightPosUnmarked;
 283        private set
 284        {
 19361285            if (_heightPosUnmarked == value)
 16039286                return;
 3322287            _heightPosUnmarked = value;
 3322288            _positionMutated = true;
 3322289        }
 290    }
 291
 292    private Vector3d _lastPosition;
 293
 294    private const int DefaultBodyHitBufferCapacity = 16;
 5076295    private readonly SwiftList<Physics3DHit> _continuousCollisionHits = new(DefaultBodyHitBufferCapacity);
 5076296    private readonly SwiftList<PhysicsMixedHit> _continuousMixedCollisionHits = new(DefaultBodyHitBufferCapacity);
 5076297    private readonly SwiftList<int> _rotationalContinuousCollisionCandidateIds = new(DefaultBodyHitBufferCapacity);
 5076298    private readonly ContactManifold _rotationalContinuousCollisionManifold = new();
 5076299    private readonly SweptSphereQueryWorker _shapeExactContinuousSweepWorker = new();
 5076300    private readonly ConvexSweepQueryWorker _shapeExactContinuousConvexSweepWorker = new();
 301    // Shape-exact CCD must remain separated even when a degenerate support
 302    // feature is recognized at the far edge of the convex sweep contact band.
 1303    private static readonly Fixed64 ShapeExactContinuousContactSlop =
 1304        ConvexSweepQueryWorker.ContactTolerance * (Fixed64)4;
 305
 306
 307    /// <summary>Enables publishing interpolated positions to <see cref="PositionTransform"/>.</summary>
 308    public bool CanSetVisualPosition;
 309
 310    private Vector3d _visualPosition;
 311    /// <summary>Gets the current position interpolation endpoint.</summary>
 1312    public Vector3d VisualPosition => _visualPosition;
 313
 314    private Vector3d _lastVisualPosition;
 315    /// <summary>Gets the previous position interpolation endpoint.</summary>
 1316    public Vector3d LastVisualPosition => _lastVisualPosition;
 317
 318    #endregion
 319
 320    #region Rotation Properties
 321
 322    private bool _rotationMutated;
 323    private bool _rotationChangedBuffer;
 324    /// <summary>Gets whether an authoritative rotation change awaits presentation.</summary>
 1146325    public bool RotationChangePending => _rotationMutated || _rotationChangedBuffer;
 326
 327    private FixedQuaternion _rotation;
 328
 329    /// <summary>Gets the authoritative world-space rotation.</summary>
 330    public FixedQuaternion Rotation
 331    {
 447670332        get => _rotation;
 333        private set
 334        {
 26011335            if (_rotation == value)
 15757336                return;
 10254337            _rotation = value;
 10254338            _rotationMutated = true;
 10254339        }
 340    }
 341
 342    /// <summary>Gets the body's world-space forward direction.</summary>
 1343    public Vector3d Forward => _rotation.Rotate(Vector3d.Forward);
 344    /// <summary>Gets the body's world-space up direction.</summary>
 1345    public Vector3d Up => _rotation.Rotate(Vector3d.Up);
 346    /// <summary>Gets the body's world-space right direction.</summary>
 4347    public Vector3d Right => _rotation.Rotate(Vector3d.Right);
 348
 349    /// <summary>Enables publishing interpolated rotations to <see cref="RotationTransform"/>.</summary>
 350    public bool CanSetVisualRotation;
 351
 352    private FixedQuaternion _visualRotation;
 353    /// <summary>Gets the current rotation interpolation endpoint.</summary>
 13354    public FixedQuaternion VisualRotation => _visualRotation;
 355
 356    private FixedQuaternion _lastVisualRotation;
 357    /// <summary>Gets the previous rotation interpolation endpoint.</summary>
 1358    public FixedQuaternion LastVisualRotation => _lastVisualRotation;
 359
 360    /// <summary>
 361    /// Gets whether all rotation axes are frozen.
 362    /// </summary>
 363    public bool IsRotationFullyFrozen
 364    {
 365        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 2090182366        get => (_freezeAxes & BodyFreezeAxes3D.Rotation) == BodyFreezeAxes3D.Rotation;
 367    }
 368
 369    /// <summary>Controls visual rotation interpolation when the host agent is not interacting.</summary>
 5076370    public Fixed64 DefaultRotationSpeed = (Fixed64)30; // 1 for NPC...
 371
 372    /// <summary>Controls visual rotation interpolation while the host agent is interacting.</summary>
 5076373    public Fixed64 InteractionRotationSpeed = (Fixed64)3; // 0.15 for NPC...
 374
 375    private Fixed64 _rotationSpeed;
 376    private Fixed64 _rotationInterpoleSpeed;
 377
 378    #endregion
 379
 380    private int _settingVisualsCounter;
 13522381    private bool SettingVisuals => _settingVisualsCounter > 0;
 382
 383    /// <summary>
 384    /// The desiredSpeed an object has in a specific direction
 385    /// AKA units per second the unit is moving
 386    /// </summary>
 387    private Vector3d _linearVelocity;
 388    /// <summary>Gets the authoritative world-space linear velocity.</summary>
 337812389    public Vector3d LinearVelocity => _linearVelocity;
 390
 391    private Vector3d _linearDirection;
 392
 393    /// <summary>
 394    /// Represents the angular velocity of the body.
 395    /// </summary>
 396    private Vector3d _angularVelocity;
 397    /// <summary>Gets the authoritative world-space angular velocity.</summary>
 357795398    public Vector3d AngularVelocity => _angularVelocity;
 399
 400    private Vector3d _angularDirection;
 401
 402    /// <summary>
 403    /// Represents the torque applied to the body.
 404    /// </summary>
 405    private Vector3d _deltaTorque;
 406
 407    private Fixed3x3 _inertiaTensor;
 408    private Fixed3x3 _worldInertiaTensor;
 409    private Fixed3x3 _inverseLocalInertiaTensor;
 410    private Fixed3x3 _inverseInertiaTensor;
 411    /// <summary>Gets the constrained world-space inverse inertia tensor.</summary>
 9412    public Fixed3x3 InverseInertiaTensor => _inverseInertiaTensor;
 413
 414    /// <summary>
 415    /// Gets whether solver-side response may translate this body.
 416    /// </summary>
 1299539417    public bool CanTranslate => Active && _dynamicId >= 0 && IsDynamic && !IsPositionFullyFrozen && InverseMass > Fixed6
 418
 419    /// <summary>
 420    /// Gets whether solver-side response may rotate this body.
 421    /// </summary>
 1131850422    public bool CanRotate => Active
 1131850423        && _dynamicId >= 0
 1131850424        && IsDynamic
 1131850425        && !IsRotationFullyFrozen
 1131850426        && _inverseInertiaTensor != Fixed3x3.Zero;
 427
 432717428    internal bool HasSolverMobility => CanTranslate || CanRotate;
 429
 430    /// <summary>
 431    /// Gets the inverse mass that should be used by collision response.
 432    /// Translation-frozen, static, and kinematic bodies expose their raw mass
 433    /// but contribute zero constrained inverse mass.
 434    /// </summary>
 34688435    public Fixed64 EffectiveInverseMass => CanTranslate ? InverseMass : Fixed64.Zero;
 436
 437    /// <summary>
 438    /// Gets the inverse inertia tensor that should be used by collision response.
 439    /// Bodies that cannot rotate expose a zero tensor even when raw inertia is available.
 440    /// </summary>
 184441    public Fixed3x3 EffectiveInverseInertiaTensor => CanRotate ? _inverseInertiaTensor : Fixed3x3.Zero;
 442
 443    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 444    internal Vector3d ProjectLinearMotion(Vector3d value)
 445    {
 924643446        if (value == Vector3d.Zero || IsPositionFullyFrozen)
 26308447            return Vector3d.Zero;
 448
 898335449        Fixed64 x = (_freezeAxes & BodyFreezeAxes3D.PositionX) == BodyFreezeAxes3D.PositionX ? Fixed64.Zero : value.X;
 898335450        Fixed64 y = (_freezeAxes & BodyFreezeAxes3D.PositionY) == BodyFreezeAxes3D.PositionY ? Fixed64.Zero : value.Y;
 898335451        Fixed64 z = (_freezeAxes & BodyFreezeAxes3D.PositionZ) == BodyFreezeAxes3D.PositionZ ? Fixed64.Zero : value.Z;
 898335452        return new Vector3d(x, y, z);
 453    }
 454
 455    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 456    private Vector3d ProjectLinearEndpoint(Vector3d start, Vector3d end)
 457    {
 12690458        Fixed64 x = (_freezeAxes & BodyFreezeAxes3D.PositionX) == BodyFreezeAxes3D.PositionX ? start.X : end.X;
 12690459        Fixed64 y = (_freezeAxes & BodyFreezeAxes3D.PositionY) == BodyFreezeAxes3D.PositionY ? start.Y : end.Y;
 12690460        Fixed64 z = (_freezeAxes & BodyFreezeAxes3D.PositionZ) == BodyFreezeAxes3D.PositionZ ? start.Z : end.Z;
 12690461        return new Vector3d(x, y, z);
 462    }
 463
 464    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 465    internal Vector3d ProjectAngularMotion(Vector3d value)
 466    {
 1062879467        if (value == Vector3d.Zero || IsRotationFullyFrozen)
 69011468            return Vector3d.Zero;
 469
 993868470        Fixed64 x = (_freezeAxes & BodyFreezeAxes3D.RotationX) == BodyFreezeAxes3D.RotationX ? Fixed64.Zero : value.X;
 993868471        Fixed64 y = (_freezeAxes & BodyFreezeAxes3D.RotationY) == BodyFreezeAxes3D.RotationY ? Fixed64.Zero : value.Y;
 993868472        Fixed64 z = (_freezeAxes & BodyFreezeAxes3D.RotationZ) == BodyFreezeAxes3D.RotationZ ? Fixed64.Zero : value.Z;
 993868473        return new Vector3d(x, y, z);
 474    }
 475
 476    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 477    internal Fixed64 GetConstrainedInverseMass(Vector3d axis)
 478    {
 556070479        if (!CanTranslate || axis == Vector3d.Zero)
 15753480            return Fixed64.Zero;
 481
 540317482        Fixed64 axisMagnitudeSquared = axis.MagnitudeSquared;
 540317483        if (axisMagnitudeSquared <= Fixed64.Epsilon)
 1484            return Fixed64.Zero;
 485
 540316486        Vector3d allowedAxis = ProjectLinearMotion(axis);
 540316487        Fixed64 allowedScale = Vector3d.Dot(allowedAxis, axis) / axisMagnitudeSquared;
 540316488        return allowedScale > Fixed64.Zero ? InverseMass * allowedScale : Fixed64.Zero;
 489    }
 490
 491    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 492    internal Vector3d ApplyConstrainedInverseInertia(Vector3d torqueAxis)
 493    {
 751005494        if (!CanRotate || torqueAxis == Vector3d.Zero)
 63875495            return Vector3d.Zero;
 496
 687130497        return ProjectAngularMotion(_inverseInertiaTensor * torqueAxis);
 498    }
 499
 500    internal Fixed3x3 GetConstrainedInverseInertiaTensor() =>
 58716501        new(
 58716502            ApplyConstrainedInverseInertia(Vector3d.Right),
 58716503            ApplyConstrainedInverseInertia(Vector3d.Up),
 58716504            ApplyConstrainedInverseInertia(Vector3d.Forward));
 505
 5076506    private Fixed64 _gravityScale = Fixed64.One;
 507
 508    /// <summary>
 509    /// Multiplies context environment gravity for this body. Zero disables gravity-derived acceleration and grounded we
 510    /// </summary>
 511    public Fixed64 GravityScale
 512    {
 513        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 92514        get => _gravityScale;
 515        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 516        set
 517        {
 56518            SwiftThrowHelper.ThrowIfArgument(
 56519                value < Fixed64.Zero,
 56520                nameof(value),
 56521                "Gravity scale cannot be negative.");
 55522            _gravityScale = value;
 55523            RefreshGroundNormalForce();
 55524        }
 525    }
 526
 527    /// <summary>Gets whether both linear and angular velocity are exactly zero.</summary>
 3528    public bool IsAtRest => _linearVelocity.IsZero && _angularVelocity.IsZero;
 529
 530    private bool _isSleeping;
 531    private int _sleepFrameCount;
 5076532    private bool _sleepEnabled = true;
 5076533    private int _sleepFrameThreshold = 16;
 5076534    private Fixed64 _sleepLinearSpeedThreshold = (Fixed64)0.001f;
 5076535    private Fixed64 _sleepAngularSpeedThreshold = (Fixed64)0.001f;
 536
 537    /// <summary>
 538    /// Gets whether this dynamic body is currently excluded from solver work until a deterministic wake event occurs.
 539    /// </summary>
 343393540    public bool IsSleeping => _isSleeping;
 541
 542    /// <summary>
 543    /// Enables deterministic sleep evaluation for this body.
 544    /// </summary>
 545    public bool SleepEnabled
 546    {
 13028547        get => _sleepEnabled;
 548        set
 549        {
 27550            if (_sleepEnabled == value)
 7551                return;
 552
 20553            _sleepEnabled = value;
 20554            if (!value)
 19555                Wake();
 20556        }
 557    }
 558
 559    /// <summary>
 560    /// Number of consecutive fixed frames below sleep thresholds required before the body sleeps.
 561    /// </summary>
 562    public int SleepFrameThreshold
 563    {
 2564        get => _sleepFrameThreshold;
 565        set
 566        {
 11567            SwiftThrowHelper.ThrowIfNegative(value, nameof(value));
 11568            _sleepFrameThreshold = value;
 11569        }
 570    }
 571
 572    /// <summary>
 573    /// Linear speed at or below which the body can count toward sleeping.
 574    /// </summary>
 575    public Fixed64 SleepLinearSpeedThreshold
 576    {
 4656577        get => _sleepLinearSpeedThreshold;
 578        set
 579        {
 4580            SwiftThrowHelper.ThrowIfArgument(
 4581                value < Fixed64.Zero,
 4582                nameof(value),
 4583                "Sleep linear speed threshold cannot be negative.");
 4584            _sleepLinearSpeedThreshold = value;
 4585        }
 586    }
 587
 588    /// <summary>
 589    /// Angular speed at or below which the body can count toward sleeping.
 590    /// </summary>
 591    public Fixed64 SleepAngularSpeedThreshold
 592    {
 926593        get => _sleepAngularSpeedThreshold;
 594        set
 595        {
 2596            SwiftThrowHelper.ThrowIfArgument(
 2597                value < Fixed64.Zero,
 2598                nameof(value),
 2599                "Sleep angular speed threshold cannot be negative.");
 2600            _sleepAngularSpeedThreshold = value;
 2601        }
 602    }
 603
 318970604    internal bool IsAwakeForCollision => HasSolverMobility && !IsSleeping;
 605
 606    // LinearVelocity magnitude
 607    private Fixed64 _linearSpeed;
 608    /// <summary>Gets the magnitude of <see cref="LinearVelocity"/>.</summary>
 861609    public Fixed64 LinearSpeed => _linearSpeed;
 610
 611    /// <summary>
 612    /// Represents the total accumulated force on the object. This can be the sum of all external forces acting on the o
 613    /// Changing this value directly affects the object's acceleration and, subsequently, its velocity and position.
 614    /// </summary>
 615    private Vector3d _linearAccelerationStore;
 616    private Vector3d _deltaAcceleration;
 617
 618    private Vector3d _linearAcceleration;
 619    /// <summary>Gets the linear acceleration measured during the latest integration step.</summary>
 3620    public Vector3d LinearAcceleration => _linearAcceleration;
 621
 622    private Fixed64 _angularSpeed;
 623    /// <summary>Gets the magnitude of <see cref="AngularVelocity"/>.</summary>
 11624    public Fixed64 AngularSpeed => _angularSpeed;
 625
 626    private Vector3d _angularAccelerationStore;
 627
 628    private Vector3d _angularAcceleration;
 629    /// <summary>Gets the angular acceleration measured during the latest integration step.</summary>
 9630    public Vector3d AngularAcceleration => _angularAcceleration;
 631
 632
 633    /// <summary>
 634    /// Represents a body's resistance to movement, akin to air resistance.
 635    /// Higher values slow down the body more quickly in absence of other forces.
 636    /// The effect is significant when bodies are expected to slow down or stop without sustained forces.
 637    /// It's not constrained between 0 and 1, depends on the object's shape and the flow conditions.
 638    /// </summary>
 5076639    public Fixed64 LinearDragCoefficient = (Fixed64)0.75f;
 640
 5076641    private Fixed64 AngularDragCoefficient = (Fixed64)0.75f;
 642
 643    /// <summary>
 644    /// Represents the normal force on the object.
 645    /// It's usually perpendicular to the contact surface and prevents the object from "falling" into the surface.
 646    /// Can be updated to simulate changes in terrain or surface inclination.
 647    /// </summary>
 648    private Vector3d _normalForce;
 649
 650    //  Mass (in kilograms) is the measure of the amount of matter in a body
 651    //  Divide the weight (in Newtons) by the acceleration of gravity to determine the mass of an object (measured in Ki
 652    //  On Earth, gravity accelerates at 9.8 meters per second squared (9.8 m/s^2)
 653    //  ex: 150 Pounds x PhysicsEnvironment.PoundToNewton = 667 Newtons / 9.8 m/s^2 = 68 kilograms * PhysicsEnvironment.
 654    /// <summary>Mass used by integration and collision response.</summary>
 655    public Fixed64 Mass;
 656
 657    // InverseMass is the reciprocal of mass, which is useful for performance reasons
 658    // when mass is used in calculations.
 659    /// <summary>Gets the reciprocal mass, or zero when <see cref="Mass"/> is zero.</summary>
 1832899660    public Fixed64 InverseMass => Mass != Fixed64.Zero
 1832899661        ? Fixed64.One / Mass
 1832899662        : Fixed64.Zero;
 663
 664    // Weight is a measure of how the force of gravity acts upon the mass.
 665    // Weight (in Newtons) is mass (in Kilograms) multiplied by the acceleration of gravity (g).
 666    // ex: 68 kg * 9.8 m/s^2 = 667 Newtons / PhysicsEnvironment.PoundToNewton = 150 Pounds
 306667    private Fixed64 Weight => Mass * Context.Environment.Gravity * _gravityScale;
 668
 669    /// <summary>Gets the host agent that owns this body.</summary>
 670    public IMatterAgent Agent { get; private set; } = null!;
 671
 672    /// <summary>Gets the world context supplied by the host agent.</summary>
 673    public GravitasWorldContext Context { get; private set; } = null!;
 674
 675    /// <summary>Gets the GridForge world owned by <see cref="Context"/>.</summary>
 11597676    public GridWorld World => Context.World;
 677
 678    /// <summary>Gets the runtime collider attached to this body.</summary>
 679    public LSCollider Collider { get; private set; } = null!;
 680
 681    /// <summary>
 682    /// Called after authoritative position or rotation changes have been
 683    /// committed during simulation.
 684    /// </summary>
 685    public Action? OnMoved;
 686
 687    /// <summary>Creates a 3D body bound to a host agent and collider.</summary>
 5076688    public SolidBody(IMatterAgent agent, LSCollider collider)
 689    {
 5076690        SwiftThrowHelper.ThrowIfNull(agent, nameof(agent));
 5076691        SwiftThrowHelper.ThrowIfNull(collider, nameof(collider));
 692
 5076693        GravitasWorldContext context = agent.Context;
 5076694        SwiftThrowHelper.ThrowIfNull(context, nameof(agent.Context));
 5076695        SwiftThrowHelper.ThrowIfArgument(
 5076696            collider.TryGetBoundContext(out GravitasWorldContext? colliderContext)
 5076697                && !ReferenceEquals(colliderContext, context),
 5076698            nameof(collider),
 5076699            "Agent and collider must be bound to the same context.");
 700
 5075701        Agent = agent;
 5075702        Collider = collider;
 5075703        Context = context;
 5075704        Collider.BindContext(context);
 705
 5075706        _positionTransform = agent.Transform;
 5075707        _rotationTransform = agent.Transform;
 708
 5075709        _rotationSpeed = DefaultRotationSpeed;
 5075710        _rotationInterpoleSpeed = Fixed64.Zero;
 5075711    }
 712
 713    /// <summary>Initializes and registers the body at an authoritative world pose.</summary>
 714    public void Initialize(
 715        Vector3d startPosition,
 716        FixedQuaternion startRotation,
 717        BodyMotionType motionType = BodyMotionType.Dynamic)
 718    {
 5091719        motionType.ThrowIfInvalid(nameof(motionType));
 5091720        SwiftThrowHelper.ThrowIfArgument(
 5091721            Collider.Id >= 0
 5091722                || (Collider.HasHostBinding && !ReferenceEquals(Collider.Body, this)),
 5091723            nameof(Collider),
 5091724            "Body collider must be unregistered and free of another host binding before initialization.");
 5089725        FixedQuaternion normalizedRotation = startRotation.Normalized;
 5089726        Collider.PreflightBodyInitialization(this, startPosition, normalizedRotation);
 727
 5082728        _motionType = motionType;
 5082729        Active = true;
 730
 5082731        InvalidateContinuousCollisionTrajectory();
 5082732        ClearMotionForSleep();
 5082733        _normalForce = Vector3d.Zero;
 5082734        _isSleeping = false;
 5082735        _sleepFrameCount = 0;
 736
 5082737        _isGrounded = false;
 5082738        _wasGrounded = false;
 5082739        _groundedTransitionCapturedForStep = false;
 5082740        _skipGroundingCheck = false;
 5082741        _lastGroundCheckFrame = int.MinValue;
 5082742        ResetGroundCalculations();
 743
 5082744        _positionChangedBuffer = true;
 5082745        _position2dUnmarked = startPosition.ToVector2d();
 5082746        _lastGroundedPosition = _lastPosition = startPosition;
 5082747        _heightPosUnmarked = startPosition.Y;
 748
 5082749        _rotationChangedBuffer = true;
 5082750        _rotation = normalizedRotation;
 751
 5082752        if (!IsKinematic)
 753        {
 4670754            _lastVisualPosition = _visualPosition = Position3d;
 4670755            _visualRotation = normalizedRotation;
 4670756            _lastVisualRotation = _visualRotation;
 757        }
 758
 5082759        OnVisualize();
 760
 5082761        _dynamicId = Context.Physics.AssimilateBody(this, motionType);
 5082762        Collider!.Initialize(this);
 5082763        RefreshMassPropertiesFromColliderShape();
 5081764        CheckGround(force: true);
 5081765    }
 766
 767
 768    /// <summary>Completes deferred body integration and post-step bookkeeping.</summary>
 769    public void LateSimulate()
 770    {
 7771        Context.EnterSimulationPhase();
 772        try
 773        {
 7774            LateSimulate(updateSleepState: true, updateColliderState: true);
 7775        }
 776        finally
 777        {
 7778            Context.ExitSimulationPhase();
 7779        }
 7780    }
 781
 782    internal void LateSimulate(bool updateSleepState, bool updateColliderState)
 783    {
 13556784        if (!Active) return;
 785
 13554786        CaptureGroundedStepState();
 787        try
 788        {
 13554789            LastContinuousCollisionToiIterationCount = 0;
 13554790            LastContinuousCollisionToiIterationLimitReached = false;
 791
 13554792            _lastPosition = Position3d;
 13554793            if (TryConsumeContinuousCollisionHandoff(updateSleepState, updateColliderState))
 62794                return;
 795
 13492796            if (IsKinematic)
 6337797                UpdateKinematicPositionAndRotation();
 798
 13492799            if (HasSolverMobility)
 800            {
 7148801                if (!IsSleeping)
 802                {
 6370803                    ProcessMovable();
 6369804                    if (updateSleepState)
 4805                        UpdateSleepState();
 806                }
 807
 7147808                if (updateColliderState)
 8809                    Collider!.Simulate();
 810            }
 811
 13491812            if (SettingVisuals)
 6130813                _settingVisualsCounter--;
 814
 13491815            if (PositionChangePending || RotationChangePending)
 12971816                OnMoved?.Invoke();
 536817        }
 818        finally
 819        {
 13554820            CompleteGroundedStepState();
 13554821        }
 13553822    }
 823
 824    internal void UpdateSleepStateAfterPhysicsStep()
 825    {
 13501826        if (!IsSleeping)
 12787827            UpdateSleepState();
 13501828    }
 829
 830    private void UpdateKinematicPositionAndRotation()
 831    {
 6337832        EnsureContinuousCollisionFramePrepared(Context.LateSimulateToken);
 6337833        Vector3d startPosition = Position3d;
 6337834        FixedQuaternion startRotation = Rotation;
 6337835        Vector3d requestedPosition = _positionTransform.WorldPosition;
 6337836        FixedQuaternion requestedRotation = _rotationTransform.WorldRotation;
 6337837        Vector3d kinematicPosition = ContinuousCollisionFrameEnd;
 6337838        FixedQuaternion kinematicRotation = ContinuousCollisionFrameTargetRotation;
 6337839        if (startPosition == kinematicPosition && startRotation == kinematicRotation)
 840        {
 6841            SetPositionTransformWorldPosition(kinematicPosition);
 6842            SetRotationTransformWorldRotation(kinematicRotation);
 6843            return;
 844        }
 845
 6331846        Wake();
 847
 6331848        Vector3d resolvedPosition = ProjectLinearEndpoint(startPosition, kinematicPosition);
 6331849        if (ShouldUseContinuousCollision(out _))
 6318850            _ = ContinuousCollisionSweepRange.ValidateEndpoint(startPosition, resolvedPosition, out _);
 6331851        FixedQuaternion resolvedRotation = kinematicRotation;
 6331852        if (!TryResolveKinematicRotationalContinuousCollision(
 6331853                startPosition,
 6331854                ref resolvedPosition,
 6331855                startRotation,
 6331856                ref resolvedRotation))
 857        {
 6221858            TryResolveKinematicContinuousCollision(startPosition, ref resolvedPosition);
 859        }
 860
 6331861        if (resolvedPosition != requestedPosition)
 6182862            SetPositionTransformWorldPosition(resolvedPosition);
 6331863        if (resolvedRotation != requestedRotation)
 23864            SetRotationTransformWorldRotation(resolvedRotation);
 865
 6331866        SetPosition2d(resolvedPosition.ToVector2d());
 6331867        HeightPos = resolvedPosition.Y;
 6331868        SetVisualPosition(resolvedPosition);
 869
 6331870        Rotation = resolvedRotation;
 6331871        StoreVisualRotation(resolvedRotation);
 6331872    }
 873
 874
 875    /// <summary>
 876    /// Puts the body to sleep and keeps it partitioned for queries and deterministic wake propagation.
 877    /// </summary>
 878    public void Sleep()
 879    {
 234880        if (!CanSleep)
 6881            return;
 882
 228883        _sleepFrameCount = _sleepFrameThreshold;
 228884        ClearMotionForSleep();
 228885        if (_isSleeping)
 1886            return;
 887
 227888        _isSleeping = true;
 227889        RefreshPartitionAwakeState();
 227890    }
 891
 892    /// <summary>
 893    /// Wakes a sleeping body because a deterministic simulation or host stimulus changed its state.
 894    /// </summary>
 895    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 896    public void Wake()
 897    {
 7448898        _sleepFrameCount = 0;
 7448899        if (!_isSleeping)
 7430900            return;
 901
 18902        _isSleeping = false;
 18903        RefreshPartitionAwakeState();
 18904    }
 905
 906    internal void WakeFromCollision()
 907    {
 481092908        if (!_isSleeping)
 480974909            return;
 910
 118911        _sleepFrameCount = 0;
 118912        _isSleeping = false;
 118913        RefreshPartitionAwakeState();
 118914    }
 915
 13026916    private bool CanSleep => SleepEnabled && HasSolverMobility;
 917
 918
 919    private void SetTransformWorldPose(Vector3d position, FixedQuaternion rotation)
 920    {
 6484921        Vector3d originalLocalPosition = _positionTransform.LocalPosition;
 6484922        FixedQuaternion originalLocalRotation = _positionTransform.LocalRotation;
 6484923        SwiftThrowHelper.ThrowIfTrue(
 6484924            !_positionTransform.TrySetWorldPose(position, rotation),
 6484925            nameof(FixedTransform),
 6484926            "Host transform cannot represent the requested world pose.");
 927
 928        try
 929        {
 6483930            if (Active)
 931            {
 6482932                SwiftThrowHelper.ThrowIfTrue(
 6482933                    !Collider.TryPrepareBodyPose(position, rotation),
 6482934                    nameof(position),
 6482935                    "The requested body pose produces collider geometry outside the representable coordinate domain.");
 936            }
 6479937        }
 4938        catch
 939        {
 4940            _positionTransform.LocalPosition = originalLocalPosition;
 4941            _positionTransform.LocalRotation = originalLocalRotation;
 4942            throw;
 943        }
 6479944    }
 945
 946    /// <summary>Releases the body's collider and context-local registration.</summary>
 947    public void Deactivate()
 948    {
 103949        if (!ReferenceEquals(Collider.Body, this))
 11950            return;
 951
 92952        DiscardContinuousCollisionHandoff();
 92953        InvalidateContinuousCollisionTrajectory();
 92954        Collider.DeactivateRuntimeRegistration();
 91955        Context.Physics.DessimilateBody(this);
 91956        _dynamicId = -1;
 91957        Active = false;
 91958    }
 959
 960    /// <summary>Sets an authoritative target rotation and visual interpolation rate.</summary>
 961    public void UpdateRotation(FixedQuaternion targetRotation, Fixed64 bufferInterpolation)
 962    {
 9963        FixedQuaternion normalizedRotation = targetRotation.Normalized;
 9964        PreflightStaticPoseChange();
 8965        bool preparedPose = PrepareExplicitBodyPose(
 8966            Position3d,
 8967            normalizedRotation,
 8968            nameof(targetRotation),
 8969            "The requested target rotation produces collider geometry outside the representable coordinate domain.");
 8970        _rotationInterpoleSpeed = bufferInterpolation;
 8971        _rotationSpeed = Agent.IsInteracting
 8972            ? InteractionRotationSpeed
 8973            : DefaultRotationSpeed;
 8974        Rotation = normalizedRotation;
 8975        PublishExplicitBodyPose(preparedPose);
 8976    }
 977
 978    /// <summary>
 979    /// Gets a world-space point from the authoritative body pose and committed collider scale.
 980    /// </summary>
 981    /// <param name="point">The body-local point.</param>
 982    /// <returns>The corresponding world-space point.</returns>
 983    /// <exception cref="InvalidOperationException">
 984    /// The collider has no committed scale or the final world-space point is not representable.
 985    /// </exception>
 986    public Vector3d GetWorldPoint(Vector3d point)
 987    {
 7988        bool transformed = TryGetWorldPoint(point, out Vector3d result);
 7989        SwiftThrowHelper.ThrowIfTrue(
 7990            !transformed,
 7991            nameof(Collider),
 7992            "Cannot get the world point because the collider has no committed scale or the final coordinate is not repre
 5993        return result;
 994    }
 995
 996    /// <summary>
 997    /// Attempts to get a world-space point from the authoritative body pose and committed collider scale.
 998    /// </summary>
 999    /// <param name="point">The body-local point.</param>
 1000    /// <param name="result">The world-space point on success; otherwise zero.</param>
 1001    /// <returns><see langword="true"/> when a committed scale exists and the final point is representable.</returns>
 1002    public bool TryGetWorldPoint(Vector3d point, out Vector3d result)
 1003    {
 2211004        if (!Collider.TryGetCommittedOwnerScale(out Vector3d scale))
 1005        {
 21006            result = Vector3d.Zero;
 21007            return false;
 1008        }
 1009
 2191010        return Rotation.TryTransformScaledPoint(Position3d, point, scale, out result);
 1011    }
 1012
 1013    /// <summary>
 1014    /// Gets a body-local point from the authoritative body pose and committed collider scale.
 1015    /// </summary>
 1016    /// <param name="point">The world-space point.</param>
 1017    /// <returns>The corresponding body-local point.</returns>
 1018    /// <exception cref="InvalidOperationException">
 1019    /// The collider has no committed scale, its scale is singular, or the final body-local point is not representable.
 1020    /// </exception>
 1021    public Vector3d GetLocalPoint(Vector3d point)
 1022    {
 51023        bool transformed = TryGetLocalPoint(point, out Vector3d result);
 51024        SwiftThrowHelper.ThrowIfTrue(
 51025            !transformed,
 51026            nameof(Collider),
 51027            "Cannot get the local point because the collider has no committed scale, its scale is singular, or the final
 31028        return result;
 1029    }
 1030
 1031    /// <summary>
 1032    /// Attempts to get a body-local point from the authoritative body pose and committed collider scale.
 1033    /// </summary>
 1034    /// <param name="point">The world-space point.</param>
 1035    /// <param name="result">The body-local point on success; otherwise zero.</param>
 1036    /// <returns>
 1037    /// <see langword="true"/> when a committed nonsingular scale exists and the final point is representable.
 1038    /// </returns>
 1039    public bool TryGetLocalPoint(Vector3d point, out Vector3d result)
 1040    {
 2191041        if (!Collider.TryGetCommittedOwnerScale(out Vector3d scale))
 1042        {
 21043            result = Vector3d.Zero;
 21044            return false;
 1045        }
 1046
 2171047        return Rotation.TryInverseTransformScaledPoint(Position3d, point, scale, out result);
 1048    }
 1049
 1050    /// <summary>Resets the authoritative pose, motion, grounding, and presentation state.</summary>
 1051    public void ResetPosition(Vector3d position = default, FixedQuaternion rotation = default)
 1052    {
 64861053        FixedQuaternion normalizedRotation = rotation.Normalized;
 64861054        PreflightResetPoseChange();
 64841055        SetTransformWorldPose(position, normalizedRotation);
 64791056        InvalidateContinuousCollisionTrajectory();
 64791057        ClearMotionForSleep();
 64791058        bool wasSleeping = _isSleeping;
 64791059        _isSleeping = false;
 64791060        _sleepFrameCount = 0;
 64791061        _normalForce = Vector3d.Zero;
 1062
 64791063        SetPosition2d(position.ToVector2d());
 64791064        HeightPos = position.Y;
 64791065        _lastPosition = position;
 64791066        _lastVisualPosition = _visualPosition = position;
 64791067        Rotation = normalizedRotation;
 1068
 64791069        _visualRotation = normalizedRotation;
 1070
 64791071        PublishExplicitBodyPose(Active);
 64791072        if (wasSleeping)
 271073            RefreshPartitionAwakeState();
 64791074    }
 1075
 1076}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.Grounding.cs

#LineLine coverage
 1//=======================================================================
 2// SolidBody.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 Gravitas.Support;
 13using SwiftCollections;
 14using System;
 15using System.Collections.Generic;
 16using System.Runtime.CompilerServices;
 17
 18namespace Gravitas;
 19
 20public partial class SolidBody
 21{
 22    #region Grounding
 23
 24    private bool _skipGroundingCheck = false;
 25
 26    /// <summary>
 27    /// Selects whether Gravitas probes for ground automatically or preserves host-supplied grounding state.
 28    /// </summary>
 29    public GroundingMode GroundingMode { get; private set; } = GroundingMode.Automatic;
 30
 31    // how close to the actor's feet (or whatever touches the ground) do we check for grounding
 32    /// <summary>Vertical offset applied to the origin of automatic ground probes.</summary>
 507633    public Fixed64 GroundOriginOffset = (Fixed64)0.5f;
 34
 35    /// <summary>Automatic ground-probe distance used while grounded.</summary>
 507636    public Fixed64 GroundedDistanceRay = (Fixed64)0.5f;
 37
 38    /// <summary>Automatic ground-probe distance used while airborne.</summary>
 507639    public Fixed64 GroundDownDistanceOnAir = (Fixed64)0.5f;
 40
 41    /// <summary>
 42    /// Selects the deterministic query primitive used for ground checks.
 43    /// </summary>
 44    public GroundProbeMode GroundProbeMode { get; set; } = GroundProbeMode.Auto;
 45
 46    /// <summary>
 47    /// Optional explicit radius for swept-sphere ground probes. A zero value derives the radius from the collider.
 48    /// </summary>
 49    public Fixed64 GroundProbeRadius { get; set; }
 50
 51    private int _lastGroundCheckFrame = 0;
 52    private const int _groundCheckFrameThreshold = 10;
 507653    private readonly Fixed64 _groundCheckThreshold = (Fixed64)0.01f;
 507654    private readonly SwiftList<Physics3DHit> _groundProbeHits = new(DefaultBodyHitBufferCapacity);
 55
 56    /// <summary>Minimum downward snap distance used to preserve grounding across steps.</summary>
 507657    public Fixed64 StepOffset = (Fixed64)0.5f;
 58
 507659    private Vector3d _groundNormal = Vector3d.Zero;
 60    /// <summary>Gets the world-space normal of the current supporting surface.</summary>
 1461    public Vector3d GroundNormal => _groundNormal;
 62
 63    private FixedTransform? _hitPlatform;
 64    /// <summary>Gets the transform of the current supporting collider, if available.</summary>
 165    public FixedTransform? HitPlatform => _hitPlatform;
 66
 67    private Vector3d _hitPlatformPosition;
 68
 69    private Vector3d _hitPoint;
 70    private bool _hasHitPoint;
 71
 72    /// <summary>
 73    /// Gets whether the current ground point is representable.
 74    /// </summary>
 375    public bool HasHitPoint => _hasHitPoint;
 76
 77    /// <summary>
 78    /// Gets the current materialized ground point.
 79    /// </summary>
 80    /// <exception cref="InvalidOperationException">
 81    /// No representable ground point is available.
 82    /// </exception>
 83    public Vector3d HitPoint =>
 1984        _hasHitPoint || !_isGrounded
 1985            ? _hitPoint
 1986            : throw new InvalidOperationException(
 1987                "No representable ground point is available. Use TryGetHitPoint.");
 88
 89    /// <summary>
 90    /// Attempts to get the current materialized ground point.
 91    /// </summary>
 92    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 93    public bool TryGetHitPoint(out Vector3d hitPoint)
 94    {
 159695        hitPoint = _hitPoint;
 159696        return _hasHitPoint;
 97    }
 98
 99    /// <summary>Raised when the grounded state changes.</summary>
 100    public Action<bool>? OnGrounded;
 101
 102    private bool _isGrounded;
 103    /// <summary>Gets whether the body currently has valid ground support.</summary>
 104    public bool IsGrounded
 105    {
 19862106        get => _isGrounded;
 107        private set
 108        {
 8782109            if (_isGrounded == value)
 8621110                return;
 161111            _isGrounded = value;
 161112            OnGrounded?.Invoke(value);
 1113        }
 114    }
 115
 116    private bool _wasGrounded;
 117    private bool _groundedTransitionCapturedForStep;
 118
 119    /// <summary>
 120    /// Gets whether this body was grounded before the latest authoritative grounding refresh or manual grounding change
 121    /// </summary>
 14122    public bool WasGrounded => _wasGrounded;
 123
 124    private Vector3d _lastGroundedPosition;
 125    /// <summary>Gets the body position captured before its latest grounded movement.</summary>
 1126    public Vector3d LastGroundedPosition => _lastGroundedPosition;
 127
 128    #endregion
 129
 130    /// <summary>Temporarily disables automatic ground probes and clears grounded state.</summary>
 131    public void SkipGrounding(Fixed64 secs)
 132    {
 1133        _skipGroundingCheck = true;
 1134        CaptureGroundedTransitionState();
 1135        ClearGrounding();
 1136        Context.Coroutines.StartCoroutine(SkipGroundingCoroutine(secs));
 1137    }
 138
 139    /// <summary>
 140    /// Gives the host ownership of grounded state and disables automatic ground probes.
 141    /// </summary>
 142    public void UseManualGrounding(bool clearGrounding = true)
 143    {
 8144        GroundingMode = GroundingMode.Manual;
 8145        _skipGroundingCheck = false;
 8146        if (clearGrounding)
 147        {
 7148            CaptureGroundedTransitionState();
 7149            ClearGrounding();
 150        }
 8151    }
 152
 153    /// <summary>
 154    /// Returns grounded-state ownership to Gravitas and optionally refreshes the automatic probe immediately.
 155    /// </summary>
 156    public void UseAutomaticGrounding(bool checkGroundImmediately = true)
 157    {
 3158        GroundingMode = GroundingMode.Automatic;
 3159        if (checkGroundImmediately && Active)
 1160            CheckGround();
 3161    }
 162
 163    /// <summary>
 164    /// Sets host-owned grounded state for manual grounding sources such as deterministic heightmaps.
 165    /// </summary>
 166    public void SetManualGrounding(Vector3d hitPoint, Vector3d groundNormal, FixedTransform? hitPlatform = null)
 167    {
 10168        SwiftThrowHelper.ThrowIfArgument(
 10169            groundNormal.MagnitudeSquared <= Fixed64.Epsilon,
 10170            nameof(groundNormal),
 10171            "Manual grounding normal must be non-zero.");
 172
 10173        GroundingMode = GroundingMode.Manual;
 10174        _skipGroundingCheck = false;
 10175        CaptureGroundedTransitionState();
 10176        SetGroundingState(hitPoint, groundNormal.Normalized, hitPlatform);
 10177    }
 178
 179    /// <summary>
 180    /// Clears host-owned grounded state while leaving automatic probes disabled.
 181    /// </summary>
 182    public void ClearManualGrounding()
 183    {
 3184        GroundingMode = GroundingMode.Manual;
 3185        _skipGroundingCheck = false;
 3186        CaptureGroundedTransitionState();
 3187        ClearGrounding();
 3188    }
 189
 190    private IEnumerator<ILockedYieldInstruction> SkipGroundingCoroutine(Fixed64 secs)
 191    {
 1192        yield return Context.Coroutines.WaitForRealSeconds(secs);
 1193        _skipGroundingCheck = false;
 1194    }
 195
 196    /// <summary>Immediately refreshes automatic grounded state.</summary>
 197    public void CheckGround()
 198    {
 19199        CaptureGroundedTransitionState();
 19200        CheckGround(force: true);
 19201    }
 202
 6520203    private void CheckGroundForSimulation() => CheckGround(force: false);
 204
 205    private void CaptureGroundedTransitionState()
 206    {
 13583207        _wasGrounded = _isGrounded;
 13583208        _groundedTransitionCapturedForStep = true;
 13583209    }
 210
 211    private void CaptureGroundedStepState()
 212    {
 13554213        if (!_groundedTransitionCapturedForStep)
 13543214            CaptureGroundedTransitionState();
 13554215    }
 216
 13554217    private void CompleteGroundedStepState() => _groundedTransitionCapturedForStep = false;
 218
 219    private void CheckGround(bool force)
 220    {
 11620221        if (GroundingMode == GroundingMode.Manual)
 23222            return;
 223
 11597224        if (_skipGroundingCheck || World is null)
 225        {
 1226            ClearGrounding();
 1227            return;
 228        }
 229
 230        // Only perform SphereCast if enough frames have passed
 11596231        bool hitPlatformMoved = _hitPlatform != null && _hitPlatform.WorldPosition != _hitPlatformPosition;
 11596232        bool frameGuard = !force
 11596233            && !hitPlatformMoved
 11596234            && Vector3d.Distance(_lastPosition, Position3d) < _groundCheckThreshold
 11596235            && Context.FrameCount - _lastGroundCheckFrame < _groundCheckFrameThreshold;
 11596236        if (frameGuard)
 2836237            return;
 238
 8760239        _lastGroundCheckFrame = Context.FrameCount;
 240        // We want origin to be close to the actor's feet
 8760241        Vector3d origin = Position3d;
 242        // but not to close...
 8760243        origin.Y += GroundOriginOffset;
 244
 8760245        Fixed64 dis = GroundedDistanceRay;
 8760246        if (!IsGrounded)
 8601247            dis = GroundDownDistanceOnAir;
 248
 8760249        GroundProbeMode mode = ResolveGroundProbeMode();
 8760250        Fixed64 radius = mode == GroundProbeMode.SweptSphere
 8760251            ? ResolveGroundProbeRadius()
 8760252            : Fixed64.Zero;
 8760253        Vector3d end = origin + Vector3d.Down * dis;
 8760254        bool foundGround = TryFindGroundHit(mode, radius, origin, dis, out Physics3DHit hit);
 8760255        Context.Diagnostics.EmitGroundProbe(this, mode, origin, end, radius, foundGround, hit);
 256
 8760257        if (!foundGround)
 258        {
 8467259            ClearGrounding();
 8467260            return;
 261        }
 262
 293263        SetGroundingState(hit.Anchor, hit.Normal, hit.Collider!.Transform);
 293264    }
 265
 266    private bool TryFindGroundHit(
 267        GroundProbeMode mode,
 268        Fixed64 radius,
 269        Vector3d origin,
 270        Fixed64 distance,
 271        out Physics3DHit hit)
 272    {
 8760273        if (mode == GroundProbeMode.SweptSphere && TryFindGroundHitWithSweptSphere(origin, distance, radius, out hit))
 283274            return true;
 275
 8477276        if (mode == GroundProbeMode.SweptSphere)
 277        {
 7623278            hit = default;
 7623279            return false;
 280        }
 281
 854282        return TryFindGroundHitWithRay(origin, distance, out hit);
 283    }
 284
 285    private bool TryFindGroundHitWithRay(Vector3d origin, Fixed64 distance, out Physics3DHit hit)
 286    {
 861287        Vector3d end = origin + Vector3d.Down * distance;
 861288        int hitCount = Context.Query3D.RaycastAll(origin, end, Context.Settings.GroundCheckLayerMask, _groundProbeHits);
 3136289        for (int i = 0; i < hitCount; i++)
 290        {
 719291            Physics3DHit current = _groundProbeHits[i];
 719292            if (!IsValidGroundHit(current))
 293                continue;
 294
 12295            hit = current;
 12296            return true;
 297        }
 298
 849299        hit = default;
 849300        return false;
 301    }
 302
 303    private bool TryFindGroundHitWithSweptSphere(Vector3d origin, Fixed64 distance, Fixed64 radius, out Physics3DHit hit
 304    {
 7906305        if (radius <= Fixed64.Epsilon)
 7306            return TryFindGroundHitWithRay(origin, distance, out hit);
 307
 7899308        Vector3d end = origin + Vector3d.Down * distance;
 7899309        int hitCount = Context.Query3D.SweepSphereAll(
 7899310            origin,
 7899311            end,
 7899312            radius,
 7899313            Context.Settings.GroundCheckLayerMask,
 7899314            _groundProbeHits,
 7899315            Collider);
 316
 16564317        for (int i = 0; i < hitCount; i++)
 318        {
 664319            Physics3DHit current = _groundProbeHits[i];
 664320            if (!IsValidGroundHit(current))
 321                continue;
 322
 281323            hit = current;
 281324            return true;
 325        }
 326
 7618327        hit = default;
 7618328        return false;
 329    }
 330
 331    private bool IsValidGroundHit(Physics3DHit hit)
 332    {
 1383333        LSCollider hitCollider = hit.Collider!;
 1383334        if (ReferenceEquals(hitCollider, Collider))
 695335            return false;
 336
 688337        if (!ColliderCollisionFilter.AllowsPhysicalPair(Collider, hitCollider))
 3338            return false;
 339
 685340        SolidBody? hitBody = hitCollider.Body;
 685341        return hitCollider.IsStatic || hitBody!.IsKinematic;
 342    }
 343
 344    private GroundProbeMode ResolveGroundProbeMode()
 345    {
 8760346        if (GroundProbeMode != GroundProbeMode.Auto)
 8347            return GroundProbeMode;
 348
 8752349        return Collider is LSSphereCollider
 8752350            || Collider is LSCapsuleCollider
 8752351            || Collider is LSCylinderCollider
 8752352            || (Collider is LSCuboidCollider && ResolveGroundProbeRadius() > Fixed64.FromFraction(1, 8))
 8752353            || (Collider is LSCompoundCollider && ResolveGroundProbeRadius() > Fixed64.FromFraction(1, 8))
 8752354                ? GroundProbeMode.SweptSphere
 8752355                : GroundProbeMode.Ray;
 356    }
 357
 358    private Fixed64 ResolveGroundProbeRadius()
 359    {
 9167360        if (GroundProbeRadius > Fixed64.Zero)
 2361            return GroundProbeRadius;
 362
 9165363        return Collider switch
 9165364        {
 9165365            LSSphereCollider or
 9165366            LSCapsuleCollider or
 9165367            LSCylinderCollider or
 9165368            LSCuboidCollider or
 9165369            LSCompoundCollider =>
 9164370                Collider.CanonicalGroundProbeRadius,
 1371            _ => Fixed64.Zero
 9165372        };
 373    }
 374
 375    private void ClearGrounding()
 376    {
 8479377        IsGrounded = false;
 8479378        ResetGroundCalculations();
 8479379    }
 380
 381    private void ApplyGroundedHeightOrReset()
 382    {
 6520383        if (_isGrounded)
 384        {
 1593385            if (TryGetHitPoint(out Vector3d hitPoint))
 1592386                HeightPos = hitPoint.Y;
 1593387            return;
 388        }
 389
 4927390        ResetGroundCalculations();
 4927391    }
 392
 393    private void SetGroundingState(Vector3d hitPoint, Vector3d groundNormal, FixedTransform? hitPlatform)
 394    {
 10395        _hitPlatform = hitPlatform;
 10396        _hitPlatformPosition = _hitPlatform?.WorldPosition ?? Vector3d.Zero;
 10397        _hitPoint = hitPoint;
 10398        _hasHitPoint = true;
 10399        _groundNormal = groundNormal;
 400
 10401        RefreshGroundNormalForce();
 10402        IsGrounded = true;
 10403    }
 404
 405    private void SetGroundingState(ContactAnchor anchor, Vector3d groundNormal, FixedTransform hitPlatform)
 406    {
 293407        _hitPlatform = hitPlatform;
 293408        _hitPlatformPosition = hitPlatform.WorldPosition;
 293409        _hasHitPoint = anchor.TryGetWorldPoint(out _hitPoint);
 293410        _groundNormal = groundNormal;
 411
 293412        RefreshGroundNormalForce();
 293413        IsGrounded = true;
 293414    }
 415
 416    private void RefreshGroundNormalForce()
 417    {
 358418        if (!_isGrounded && _groundNormal.MagnitudeSquared <= Fixed64.Epsilon)
 419        {
 52420            _normalForce = Vector3d.Zero;
 52421            return;
 422        }
 423
 306424        Vector3d weightVector = Weight * Vector3d.Down;
 306425        Fixed64 weightInNormalDirection = Vector3d.Dot(weightVector, _groundNormal);
 306426        _normalForce = weightInNormalDirection * _groundNormal;
 306427    }
 428
 429    private void ResetGroundCalculations()
 430    {
 18488431        _hitPlatform = null;
 18488432        _hitPlatformPosition = Vector3d.Zero;
 18488433        _hitPoint = Vector3d.Zero;
 18488434        _hasHitPoint = false;
 18488435        _groundNormal = Vector3d.Zero;
 18488436        _normalForce = Vector3d.Zero;
 18488437    }
 438
 439}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.Motion.cs

#LineLine coverage
 1//=======================================================================
 2// SolidBody.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 partial class SolidBody
 14{
 2416715    private bool CanUseAngularInertia => IsDynamic && !IsRotationFullyFrozen;
 16
 17    private void UpdateSleepState()
 18    {
 1279219        if (!CanSleep)
 20        {
 813821            _sleepFrameCount = 0;
 813822            return;
 23        }
 24
 465425        if (_linearSpeed > SleepLinearSpeedThreshold || _angularSpeed > SleepAngularSpeedThreshold)
 26        {
 411727            _sleepFrameCount = 0;
 411728            return;
 29        }
 30
 53731        if (_sleepFrameCount < _sleepFrameThreshold)
 53632            _sleepFrameCount++;
 33
 53734        if (_sleepFrameCount >= _sleepFrameThreshold)
 835            Sleep();
 53736    }
 37
 38    private void ClearMotionForSleep()
 39    {
 1183940        _linearVelocity = Vector3d.Zero;
 1183941        _linearDirection = Vector3d.Zero;
 1183942        _linearAccelerationStore = Vector3d.Zero;
 1183943        _deltaAcceleration = Vector3d.Zero;
 1183944        _linearAcceleration = Vector3d.Zero;
 1183945        _linearSpeed = Fixed64.Zero;
 1183946        _angularVelocity = Vector3d.Zero;
 1183947        _angularDirection = Vector3d.Zero;
 1183948        _deltaTorque = Vector3d.Zero;
 1183949        _angularAccelerationStore = Vector3d.Zero;
 1183950        _angularAcceleration = Vector3d.Zero;
 1183951        _angularSpeed = Fixed64.Zero;
 1183952    }
 53
 54    private void ApplyFreezeConstraintsToMotion()
 55    {
 32256        Vector3d lastLinearVelocity = _linearVelocity;
 32257        _linearVelocity = ProjectLinearMotion(_linearVelocity);
 32258        _linearAccelerationStore = ProjectLinearMotion(_linearAccelerationStore);
 32259        _deltaAcceleration = ProjectLinearMotion(_deltaAcceleration);
 32260        _linearAcceleration = ProjectLinearMotion(_linearAcceleration);
 32261        RefreshLinearMotionState(lastLinearVelocity);
 62
 32263        Vector3d lastAngularVelocity = _angularVelocity;
 32264        _angularVelocity = ProjectAngularMotion(_angularVelocity);
 32265        _deltaTorque = ProjectAngularMotion(_deltaTorque);
 32266        _angularAccelerationStore = ProjectAngularMotion(_angularAccelerationStore);
 32267        _angularAcceleration = ProjectAngularMotion(_angularAcceleration);
 32268        RefreshAngularMotionState(lastAngularVelocity);
 32269    }
 70
 71    private void RefreshPartitionAwakeState()
 72    {
 39073        if (Collider.IsPartitioned)
 38774            Context.Collisions.RefreshPartitionAwakeState(Collider);
 39075    }
 76
 77    private void RefreshPartitionMobility()
 78    {
 685979        if (!Active || !Collider.TryGetBoundContext(out GravitasWorldContext? context))
 6480            return;
 81
 679582        Collider.Simulate();
 83
 679584        if (context!.Settings.RuntimeMode.RunsMixedContacts())
 16085            context.MixedCollisions.Refresh3DColliderPartition(Collider);
 679586    }
 87
 88    /// <summary>
 89    /// Queues a torque in mass-distance-squared-per-time-squared units for
 90    /// integration during the next fixed step.
 91    /// </summary>
 92    /// <param name="torque">The world-space torque to apply.</param>
 93    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 94    public void AddTorque(Vector3d torque)
 95    {
 10596        Vector3d accelerationDelta = ProjectAngularMotion(torque * EffectiveInverseInertiaTensor);
 10597        if (accelerationDelta == Vector3d.Zero)
 398            return;
 99
 102100        Wake();
 102101        _deltaTorque += accelerationDelta;
 102102        Context.Diagnostics.EmitTorqueDelta(this, torque);
 102103    }
 104
 105    /// <summary>
 106    /// Queues a force in mass-distance-per-time-squared units for integration
 107    /// during the next fixed step.
 108    /// </summary>
 109    /// <param name="force">The world-space force to apply.</param>
 110    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 111    public void AddForce(Vector3d force)
 112    {
 312113        Vector3d accelerationDelta = ProjectLinearMotion(force * EffectiveInverseMass);
 312114        if (accelerationDelta == Vector3d.Zero)
 1115            return;
 116
 311117        Wake();
 311118        _deltaAcceleration += accelerationDelta;
 311119        Context.Diagnostics.EmitForceDelta(this, force, accelerationDelta);
 311120    }
 121
 122    /// <summary>
 123    /// Applies a world-space linear impulse immediately as a velocity change.
 124    /// The impulse is expressed in mass-distance-per-time units and does not
 125    /// advance the fixed-step simulation or apply a time-step factor.
 126    /// </summary>
 127    /// <param name="impulse">The world-space linear impulse to apply.</param>
 128    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 129    public void AddLinearImpulse(Vector3d impulse)
 130    {
 132131        Vector3d velocityDelta = ProjectLinearMotion(impulse * EffectiveInverseMass);
 132132        if (velocityDelta == Vector3d.Zero)
 3133            return;
 134
 129135        Wake();
 129136        Vector3d lastVelocity = _linearVelocity;
 129137        _linearVelocity += velocityDelta;
 129138        RefreshLinearMotionState(lastVelocity);
 129139    }
 140
 141    /// <summary>
 142    /// Applies a world-space angular impulse immediately as an angular-velocity
 143    /// change. The impulse is expressed in mass-distance-squared-per-time units
 144    /// and does not apply a time-step factor.
 145    /// </summary>
 146    /// <param name="impulse">The world-space angular impulse to apply.</param>
 147    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 148    public void AddAngularImpulse(Vector3d impulse)
 149    {
 56150        Vector3d velocityDelta = ProjectAngularMotion(impulse * EffectiveInverseInertiaTensor);
 56151        if (velocityDelta == Vector3d.Zero)
 2152            return;
 153
 54154        Wake();
 54155        _angularVelocity += velocityDelta;
 54156        RefreshAngularMotionState(_angularVelocity - velocityDelta);
 54157    }
 158
 159    internal void ApplyCollisionLinearVelocityDelta(Vector3d velocityDelta)
 160    {
 228321161        velocityDelta = ProjectLinearMotion(velocityDelta);
 228321162        if (!CanTranslate || velocityDelta == Vector3d.Zero)
 7377163            return;
 164
 220944165        WakeFromCollision();
 220944166        Vector3d lastVelocity = _linearVelocity;
 220944167        _linearVelocity += velocityDelta;
 220944168        RefreshLinearMotionState(lastVelocity);
 220944169        Context.Diagnostics.EmitLinearVelocityDelta(this, lastVelocity, _linearVelocity);
 220944170    }
 171
 172    internal void ApplyCollisionAngularVelocityDelta(Vector3d velocityDelta)
 173    {
 253102174        velocityDelta = ProjectAngularMotion(velocityDelta);
 253102175        if (!CanRotate || velocityDelta == Vector3d.Zero)
 25360176            return;
 177
 227742178        WakeFromCollision();
 227742179        Vector3d lastVelocity = _angularVelocity;
 227742180        _angularVelocity += velocityDelta;
 227742181        RefreshAngularMotionState(lastVelocity);
 227742182        Context.Diagnostics.EmitAngularVelocityDelta(this, lastVelocity, _angularVelocity);
 227742183    }
 184
 185    internal bool CanApplyCollisionVelocityDeltas(
 186        Vector3d linearVelocityDelta,
 187        Vector3d angularVelocityDelta) =>
 335188        TryPrepareCollisionVelocityState(
 335189            linearVelocityDelta,
 335190            angularVelocityDelta,
 335191            out _,
 335192            out _);
 193
 194    internal bool TryPrepareCollisionVelocityState(
 195        Vector3d linearVelocityDelta,
 196        Vector3d angularVelocityDelta,
 197        out Vector3d linearVelocity,
 198        out Vector3d angularVelocity)
 199    {
 27035200        linearVelocityDelta = ProjectLinearMotion(linearVelocityDelta);
 27035201        angularVelocityDelta = ProjectAngularMotion(angularVelocityDelta);
 27035202        linearVelocity = _linearVelocity;
 27035203        angularVelocity = _angularVelocity;
 27035204        bool linearVelocityFits = !CanTranslate
 27035205            || Vector3d.TryAdd(
 27035206                _linearVelocity,
 27035207                linearVelocityDelta,
 27035208                out linearVelocity);
 27035209        bool angularVelocityFits = !CanRotate
 27035210            || Vector3d.TryAdd(
 27035211                _angularVelocity,
 27035212                angularVelocityDelta,
 27035213                out angularVelocity);
 27035214        return linearVelocityFits & angularVelocityFits;
 215    }
 216
 217    internal void ApplyCollisionVelocityState(
 218        Vector3d linearVelocity,
 219        Vector3d angularVelocity)
 220    {
 27007221        linearVelocity = ProjectLinearMotion(linearVelocity);
 27007222        angularVelocity = ProjectAngularMotion(angularVelocity);
 27007223        if (_linearVelocity == linearVelocity && _angularVelocity == angularVelocity)
 213224            return;
 225
 26794226        WakeFromCollision();
 26794227        if (_linearVelocity != linearVelocity)
 228        {
 26777229            Vector3d lastLinearVelocity = _linearVelocity;
 26777230            _linearVelocity = linearVelocity;
 26777231            RefreshLinearMotionState(lastLinearVelocity);
 26777232            Context.Diagnostics.EmitLinearVelocityDelta(this, lastLinearVelocity, _linearVelocity);
 233        }
 234
 26794235        if (_angularVelocity != angularVelocity)
 236        {
 13083237            Vector3d lastAngularVelocity = _angularVelocity;
 13083238            _angularVelocity = angularVelocity;
 13083239            RefreshAngularMotionState(lastAngularVelocity);
 13083240            Context.Diagnostics.EmitAngularVelocityDelta(this, lastAngularVelocity, _angularVelocity);
 241        }
 26794242    }
 243
 244    internal void ApplyCollisionPositionCorrection(Vector3d positionCorrection)
 245    {
 1191246        positionCorrection = ProjectLinearMotion(positionCorrection);
 1191247        if (!CanTranslate || positionCorrection == Vector3d.Zero)
 128248            return;
 249
 1063250        Position3d += positionCorrection;
 1063251    }
 252
 253
 254    /// <summary>Sets the authoritative world position and refreshes collider state.</summary>
 255    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 256    public void SetPosition(Vector3d position)
 257    {
 57258        PreflightStaticPoseChange();
 54259        bool preparedPose = PrepareExplicitBodyPose(
 54260            position,
 54261            Rotation,
 54262            nameof(position),
 54263            "The requested body position produces collider geometry outside the representable coordinate domain.");
 53264        if (Position3d != position)
 49265            Wake();
 266
 53267        Position3d = position;
 53268        PublishExplicitBodyPose(preparedPose);
 53269    }
 270
 271    /// <summary>Sets the authoritative world-space Y coordinate.</summary>
 272    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 273    public void SetHeight(Fixed64 height)
 274    {
 3275        PreflightStaticPoseChange();
 2276        Vector3d position = Position3d;
 2277        position.Y = height;
 2278        bool preparedPose = PrepareExplicitBodyPose(
 2279            position,
 2280            Rotation,
 2281            nameof(height),
 2282            "The requested body height produces collider geometry outside the representable coordinate domain.");
 2283        if (HeightPos != height)
 1284            Wake();
 285
 2286        HeightPos = height;
 2287        PublishExplicitBodyPose(preparedPose);
 2288    }
 289
 290    /// <summary>Sets the authoritative world rotation and refreshes collider state.</summary>
 291    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 292    public void SetRotation(FixedQuaternion quaternion)
 293    {
 48294        FixedQuaternion normalizedRotation = quaternion.Normalized;
 48295        PreflightStaticPoseChange();
 47296        bool preparedPose = PrepareExplicitBodyPose(
 47297            Position3d,
 47298            normalizedRotation,
 47299            nameof(quaternion),
 47300            "The requested body rotation produces collider geometry outside the representable coordinate domain.");
 46301        if (Rotation != normalizedRotation)
 40302            Wake();
 303
 46304        Rotation = normalizedRotation;
 46305        PublishExplicitBodyPose(preparedPose);
 46306    }
 307
 308    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 309    private bool PrepareExplicitBodyPose(
 310        Vector3d position,
 311        FixedQuaternion rotation,
 312        string parameterName,
 313        string message)
 314    {
 111315        if (!Active)
 1316            return false;
 317
 110318        SwiftThrowHelper.ThrowIfTrue(
 110319            !Collider.TryPrepareBodyPose(position, rotation),
 110320            parameterName,
 110321            message);
 108322        return true;
 323    }
 324
 325    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 326    private void PublishExplicitBodyPose(bool preparedPose)
 327    {
 6588328        if (!preparedPose)
 2329            return;
 330
 6586331        Collider.PublishPreparedExplicitBodyPose();
 6586332        RefreshPartitionMobility();
 6586333    }
 334
 335    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 336    private void PreflightStaticPoseChange()
 337    {
 117338        if (Active && IsStatic)
 339        {
 13340            ThrowIfRuntimeRegistrationMissing();
 12341            Context.ThrowIfFixedStepMutationNotAllowed();
 8342            Collider.ValidateCurrentRuntimeTransform();
 343        }
 111344    }
 345
 346    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 347    private void PreflightResetPoseChange()
 348    {
 6486349        if (!Active)
 1350            return;
 351
 6485352        ThrowIfRuntimeRegistrationMissing();
 6484353        if (!IsStatic)
 6478354            return;
 355
 6356        Context.ThrowIfFixedStepMutationNotAllowed();
 5357        Collider.ValidateCurrentRuntimeTransform();
 5358    }
 359
 360    private void ProcessMovable()
 361    {
 6370362        EnsureContinuousCollisionFramePrepared(Context.LateSimulateToken);
 363
 364        // Non-kinematic bodies position is calculated from the motion prepared
 365        // for every body before ordered body processing begins.
 6370366        NonKinematicUpdate(_continuousCollisionAngularVelocityStepStart);
 367
 6369368    }
 369
 370    private void ApplyLinearForces()
 371    {
 6474372        _linearAccelerationStore = ProjectLinearMotion(_deltaAcceleration);
 6474373        _deltaAcceleration = Vector3d.Zero;
 374
 6474375        if (_linearSpeed <= Fixed64.Zero)
 1362376            return;
 377
 5112378        ApplyDragForce();
 5112379        ApplyFrictionForce();
 5112380    }
 381
 382    private void ApplyDragForce()
 383    {
 384        // Drag calculation and accumulation.
 5112385        Fixed64 dragMagnitude = LinearDragCoefficient * Context.Environment.AirDensity * Collider.GetFrontalArea(_linear
 5112386        _linearAccelerationStore += (-_linearDirection * dragMagnitude);
 5112387    }
 388
 389    private void ApplyFrictionForce()
 390    {
 5112391        if (!_isGrounded)
 3602392            return;
 393
 1510394        Vector2d horizontalVelocity = new(_linearVelocity.X, _linearVelocity.Z);
 1510395        Fixed64 horizontalSpeed = horizontalVelocity.Magnitude;
 1510396        if (horizontalSpeed <= Fixed64.Zero)
 1397            return;
 398
 399        // Object is moving on ground, add the friction force to the accumulated force
 400        // Adjust the friction with the normal force magnitude
 1509401        PhysicsEnvironment environment = Context.Environment;
 1509402        Fixed64 effectiveFriction = ResolveGroundDynamicFriction();
 1509403        if (horizontalSpeed <= environment.FrictionTransitionSpeed)
 404        {
 1407405            Fixed64 proportion = horizontalSpeed / environment.FrictionTransitionSpeed;
 1407406            effectiveFriction *= proportion;
 407        }
 408
 1509409        Fixed64 frictionMagnitude = effectiveFriction * _normalForce.Magnitude;
 1509410        _linearAccelerationStore += (-_linearDirection * frictionMagnitude) * InverseMass;
 1509411    }
 412
 413    private void UpdateLinearVelocity()
 414    {
 6474415        Fixed64 deltaTime = Context.DeltaTime;
 6474416        PhysicsEnvironment environment = Context.Environment;
 6474417        Vector3d lastVelocity = _linearVelocity;
 418
 6474419        _linearVelocity += ProjectLinearMotion(_linearAccelerationStore * deltaTime);
 420
 421        // Reset stores for the next frame
 6474422        _linearAccelerationStore = Vector3d.Zero;
 423
 424        // Apply gravity only if not grounded
 6474425        if (!IsGrounded && (_freezeAxes & BodyFreezeAxes3D.PositionY) != BodyFreezeAxes3D.PositionY)
 4912426            _linearVelocity.Y -= environment.Gravity * _gravityScale * deltaTime;
 427
 428        // Make sure we don't fall any faster than maxFallSpeed. This gives our character a terminal velocity
 6474429        _linearVelocity.Y = FixedMath.Max(_linearVelocity.Y, -environment.MaxFallSpeed);
 6474430        _linearVelocity = ProjectLinearMotion(_linearVelocity);
 431
 6474432        RefreshLinearMotionState(lastVelocity);
 6474433    }
 434
 435    private void RefreshLinearMotionState(Vector3d lastVelocity)
 436    {
 254737437        PhysicsEnvironment environment = Context.Environment;
 254737438        Fixed64 desiredSpeed = _linearVelocity.Magnitude;
 254737439        if (desiredSpeed > environment.MinSpeed)
 440        {
 252706441            if (desiredSpeed > environment.MaxSpeed)
 442            {
 2156443                _linearVelocity = _linearVelocity.Normalized * environment.MaxSpeed;
 2156444                _linearSpeed = environment.MaxSpeed;
 445            }
 446            else
 250550447                _linearSpeed = desiredSpeed;
 448        }
 449        else
 450        {
 2031451            _linearVelocity = Vector3d.Zero;
 2031452            _linearSpeed = Fixed64.Zero;
 453        }
 454
 455        // Update the direction of the linear velocity, if we're not moving maintain previous direction
 254737456        _linearDirection = _linearSpeed > Fixed64.Zero
 254737457            ? _linearVelocity.Normalized
 254737458            : _linearDirection;
 254737459        _linearAcceleration = _linearSpeed > Fixed64.Zero
 254737460            ? (_linearVelocity - lastVelocity) / Context.DeltaTime
 254737461            : Vector3d.Zero;
 254737462    }
 463
 464    private void ApplyAngularTorques()
 465    {
 6299466        _angularAccelerationStore = ResolveAngularAccelerationForStep();
 6299467        _deltaTorque = Vector3d.Zero;
 6299468    }
 469
 470    private Vector3d ResolveAngularAccelerationForStep()
 471    {
 6299472        Vector3d acceleration = ProjectAngularMotion(_deltaTorque);
 473
 6299474        if (_angularSpeed <= Fixed64.Zero)
 1710475            return acceleration;
 476
 477        // Angular drag should also be proportional to the square of the angular velocity, just like linear drag.
 4589478        Fixed64 angularDragMagnitude = AngularDragCoefficient * Context.Environment.AirDensity * Collider.GetFrontalArea
 4589479        acceleration += -_angularDirection * angularDragMagnitude;
 480
 4589481        if (!IsGrounded)
 3065482            return acceleration;
 483
 484        // Calculate the friction force and convert it into a torque
 1524485        PhysicsEnvironment environment = Context.Environment;
 1524486        Fixed64 effectiveFriction = ResolveGroundDynamicFriction();
 1524487        if (_angularSpeed < environment.FrictionTransitionSpeed)
 488        {
 296489            Fixed64 proportion = _angularSpeed / environment.FrictionTransitionSpeed;
 296490            effectiveFriction *= proportion;
 491        }
 492
 1524493        Fixed64 frictionMagnitude = effectiveFriction * _normalForce.Magnitude;
 1524494        acceleration += (-_angularDirection * frictionMagnitude) * _inverseInertiaTensor;
 1524495        return acceleration;
 496    }
 497
 498    private void UpdateAngularVelocity()
 499    {
 6299500        Vector3d lastVelocity = _angularVelocity;
 6299501        _angularVelocity = ResolveAngularVelocityForStep(_angularVelocity, _angularAccelerationStore);
 6299502        _angularAccelerationStore = Vector3d.Zero;
 6299503        RefreshAngularMotionState(lastVelocity);
 6299504    }
 505
 506    private Vector3d ResolveAngularVelocityForStep(
 507        Vector3d startVelocity,
 508        Vector3d angularAcceleration)
 509    {
 6299510        Fixed64 deltaTime = Context.DeltaTime;
 6299511        Vector3d velocity = startVelocity
 6299512            + ProjectAngularMotion(angularAcceleration * deltaTime);
 6299513        Vector3d dampingTorque = -Context.Environment.DampingFactor * velocity;
 6299514        velocity += ProjectAngularMotion(_inverseInertiaTensor * dampingTorque * deltaTime);
 6299515        velocity = ProjectAngularMotion(velocity);
 516
 6299517        Fixed64 desiredSpeed = velocity.Magnitude;
 6299518        if (desiredSpeed <= Context.Environment.MinSpeed)
 1692519            return Vector3d.Zero;
 520
 4607521        return desiredSpeed > Context.Environment.MaxSpeed
 4607522            ? velocity.Normalized * Context.Environment.MaxSpeed
 4607523            : velocity;
 524    }
 525
 526    private void RefreshAngularMotionState(Vector3d lastVelocity)
 527    {
 247542528        PhysicsEnvironment environment = Context.Environment;
 247542529        Fixed64 desiredSpeed = _angularVelocity.Magnitude;
 247542530        if (desiredSpeed > environment.MinSpeed)
 531        {
 221356532            if (desiredSpeed > environment.MaxSpeed)
 533            {
 664534                _angularVelocity = _angularVelocity.Normalized * environment.MaxSpeed;
 664535                _angularSpeed = environment.MaxSpeed;
 536
 537            }
 538            else
 220692539                _angularSpeed = desiredSpeed;
 540
 221356541            _angularDirection = _angularVelocity.Normalized;
 542        }
 543        else
 544        {
 26186545            _angularVelocity = Vector3d.Zero;
 26186546            _angularSpeed = Fixed64.Zero;
 547        }
 548
 247542549        _angularAcceleration = _angularSpeed > Fixed64.Zero
 247542550            ? (_angularVelocity - lastVelocity) / Context.DeltaTime
 247542551            : Vector3d.Zero;
 247542552    }
 553
 554    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 555    private Fixed64 ResolveGroundDynamicFriction() =>
 3033556        Collider.Material.DynamicFriction;
 557
 558    private void NonKinematicUpdate(Vector3d angularVelocityStateStart)
 559    {
 6370560        Vector3d rotationalCcdStartPosition = Position3d;
 6370561        Vector3d rotationalCcdProposedPosition = rotationalCcdStartPosition
 6370562            + _linearVelocity * Context.DeltaTime;
 6370563        FixedQuaternion rotationalCcdStartRotation = Rotation;
 6370564        FixedQuaternion rotationalCcdProposedRotation = IntegrateAngularRotation(
 6370565            rotationalCcdStartRotation,
 6370566            Context.DeltaTime);
 6370567        if (TryResolveRotationalContinuousCollision(
 6370568                rotationalCcdStartPosition,
 6370569                ref rotationalCcdProposedPosition,
 6370570                rotationalCcdStartRotation,
 6370571                ref rotationalCcdProposedRotation))
 572        {
 52573            ApplyResolvedPosition(rotationalCcdStartPosition, rotationalCcdProposedPosition);
 52574            Rotation = rotationalCcdProposedRotation;
 52575            Collider.RebuildRuntimeShapeOnly();
 576        }
 577        else
 578        {
 6318579            PositionBasedOnForce();
 6317580            rotationalCcdProposedPosition = Position3d;
 6317581            if (CanRotate && _angularSpeed > Fixed64.Zero)
 4548582                RotationBasedOnTorque(rotationalCcdStartPosition, rotationalCcdProposedPosition);
 583        }
 584
 6369585        CheckGroundForSimulation();
 6369586        ApplyGroundedHeightOrReset();
 587
 6369588        CheckChangedValues();
 6369589        if (!CanRotate && !RotationChangePending)
 179590            return;
 591
 6190592        UpdateInertiaTensorOrientation();
 6190593        ApplyGyroscopicPrecession(angularVelocityStateStart);
 6190594    }
 595
 596    private void PositionBasedOnForce()
 597    {
 6318598        Vector3d startPosition = Position3d;
 6318599        Vector3d velocityVector = startPosition + (_linearVelocity * Context.DeltaTime);
 6318600        TryResolveContinuousCollision(startPosition, ref velocityVector);
 6317601        ApplyResolvedPosition(startPosition, velocityVector);
 6317602    }
 603
 604    private void ApplyResolvedPosition(Vector3d startPosition, Vector3d velocityVector)
 605    {
 6369606        velocityVector = startPosition + ProjectLinearMotion(velocityVector - startPosition);
 607
 6369608        Vector2d velocityAxis = velocityVector.ToVector2d();
 609
 610        // Find out how much we need to push towards the ground to avoid loosing grounding
 611        // when walking down a step or over a sharp change in slope.
 6369612        if (_isGrounded && (_freezeAxes & BodyFreezeAxes3D.PositionY) != BodyFreezeAxes3D.PositionY)
 613        {
 1561614            velocityVector.Y -= FixedMath.Max(StepOffset, velocityVector.Magnitude);
 1561615            _lastGroundedPosition = Position3d;
 616        }
 4808617        else if ((_freezeAxes & BodyFreezeAxes3D.PositionY) != BodyFreezeAxes3D.PositionY)
 4806618            HeightPos = velocityVector.Y;
 619
 620        //  Apply the force
 6369621        SetPosition2d(velocityAxis);
 6369622    }
 623
 624
 625    private void RotationBasedOnTorque(Vector3d startPosition, Vector3d proposedPosition)
 626    {
 4548627        FixedQuaternion startRotation = Rotation;
 4548628        FixedQuaternion proposedRotation = IntegrateAngularRotation(startRotation, Context.DeltaTime);
 4548629        TryResolveRotationalContinuousCollision(startPosition, ref proposedPosition, startRotation, ref proposedRotation
 4548630        Position3d = proposedPosition;
 4548631        Rotation = proposedRotation;
 4548632        Collider.RebuildRuntimeShapeOnly();
 4548633    }
 634
 635    internal void RefreshMassPropertiesFromColliderShape()
 636    {
 23935637        if (!_centerOfMassOffsetExplicit)
 23914638            _localCenterOfMassOffset = Collider.CalculateLocalCenterOfMassOffset();
 639
 23935640        RefreshInertiaTensor();
 23934641    }
 642
 643    private void UpdateInertiaTensorOrientation()
 644    {
 16787645        if (_inertiaTensor == Fixed3x3.Zero || _inverseLocalInertiaTensor == Fixed3x3.Zero)
 646        {
 35647            _worldInertiaTensor = Fixed3x3.Zero;
 35648            _inverseInertiaTensor = Fixed3x3.Zero;
 35649            return;
 650        }
 651
 16752652        Fixed3x3 inverseOrientation = Rotation.Conjugate().ToMatrix3x3();
 16752653        Fixed3x3 orientation = Rotation.ToMatrix3x3();
 654
 16752655        _worldInertiaTensor = orientation * _inertiaTensor * inverseOrientation;
 16752656        _inverseInertiaTensor = orientation * _inverseLocalInertiaTensor * inverseOrientation;
 16752657    }
 658
 659    private void RefreshInertiaTensor()
 660    {
 24167661        if (!CanUseAngularInertia)
 662        {
 13955663            _inertiaTensor = Fixed3x3.Zero;
 13955664            _worldInertiaTensor = Fixed3x3.Zero;
 13955665            _inverseLocalInertiaTensor = Fixed3x3.Zero;
 13955666            _inverseInertiaTensor = Fixed3x3.Zero;
 13955667            return;
 668        }
 669
 10212670        _inertiaTensor = Collider.CalculateInertiaTensor(Mass, _localCenterOfMassOffset);
 10211671        _inverseLocalInertiaTensor = InertiaTensorMath.InvertForSolver(_inertiaTensor);
 10211672        UpdateInertiaTensorOrientation();
 10211673    }
 674
 675    //  gyroscopic precession is a correction to the object's angular velocity based on its rotation
 676    private void ApplyGyroscopicPrecession(Vector3d angularVelocityStateStart)
 677    {
 6190678        if (!CanRotate)
 1679            return;
 680
 6189681        Vector3d gyroscopicCorrection = ProjectAngularMotion(
 6189682            _inverseInertiaTensor
 6189683            * Vector3d.Cross(_angularVelocity, _worldInertiaTensor * _angularVelocity)
 6189684            * Context.DeltaTime);
 6189685        if (gyroscopicCorrection == Vector3d.Zero)
 6185686            return;
 687
 4688        _angularVelocity -= gyroscopicCorrection;
 4689        _angularVelocity = ProjectAngularMotion(_angularVelocity);
 4690        RefreshAngularMotionState(angularVelocityStateStart);
 4691    }
 692
 693}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.ReplayHash.cs

#LineLine coverage
 1//=======================================================================
 2// SolidBody.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 partial class SolidBody
 14{
 15    internal void ContributeReplayHash(
 16        ref ChronicleHashWriter writer,
 17        GravitasReplayHashMode mode)
 18    {
 204719        writer.WriteSection("body.3d", 5);
 204720        writer.WriteInt32(_dynamicId);
 204721        writer.WriteBool(Debug);
 204722        writer.WriteBool(Active);
 204723        writer.WriteEnum(_freezeAxes);
 204724        writer.WriteEnum(_motionType);
 204725        writer.WriteVector2d(_position2dUnmarked);
 204726        writer.WriteFixed64(_heightPosUnmarked);
 204727        writer.WriteVector3d(_lastPosition);
 204728        writer.WriteFixed64(GroundOriginOffset);
 204729        writer.WriteFixed64(GroundedDistanceRay);
 204730        writer.WriteFixed64(GroundDownDistanceOnAir);
 204731        writer.WriteEnum(GroundingMode);
 204732        writer.WriteEnum(GroundProbeMode);
 204733        writer.WriteFixed64(GroundProbeRadius);
 204734        writer.WriteBool(_skipGroundingCheck);
 204735        writer.WriteInt32(_lastGroundCheckFrame);
 204736        writer.WriteFixed64(StepOffset);
 204737        writer.WriteVector3d(_groundNormal);
 204738        writer.WriteVector3d(_hitPlatformPosition);
 204739        writer.WriteVector3d(_hitPoint);
 204740        writer.WriteBool(_hasHitPoint);
 204741        writer.WriteBool(_isGrounded);
 204742        writer.WriteBool(_wasGrounded);
 204743        writer.WriteVector3d(_lastGroundedPosition);
 204744        writer.WriteQuaternion(_rotation);
 204745        writer.WriteVector3d(_linearVelocity);
 204746        writer.WriteVector3d(_linearDirection);
 204747        writer.WriteVector3d(_angularVelocity);
 204748        writer.WriteVector3d(_angularDirection);
 204749        writer.WriteVector3d(_deltaTorque);
 204750        writer.WriteVector3d(_localCenterOfMassOffset);
 204751        writer.WriteBool(_centerOfMassOffsetExplicit);
 204752        writer.WriteFixed64(_gravityScale);
 204753        writer.WriteBool(_isSleeping);
 204754        writer.WriteInt32(_sleepFrameCount);
 204755        writer.WriteBool(_sleepEnabled);
 204756        writer.WriteInt32(_sleepFrameThreshold);
 204757        writer.WriteFixed64(_sleepLinearSpeedThreshold);
 204758        writer.WriteFixed64(_sleepAngularSpeedThreshold);
 204759        writer.WriteEnum(_continuousCollisionMode);
 204760        writer.WriteFixed64(_linearSpeed);
 204761        writer.WriteVector3d(_linearAccelerationStore);
 204762        writer.WriteVector3d(_deltaAcceleration);
 204763        writer.WriteVector3d(_linearAcceleration);
 204764        writer.WriteFixed64(_angularSpeed);
 204765        writer.WriteVector3d(_angularAccelerationStore);
 204766        writer.WriteVector3d(_angularAcceleration);
 204767        writer.WriteFixed64(LinearDragCoefficient);
 204768        writer.WriteFixed64(AngularDragCoefficient);
 204769        writer.WriteVector3d(_normalForce);
 204770        writer.WriteFixed64(Mass);
 71
 204772        writer.WriteSection("body.3d.ccd-authoritative", 3);
 204773        writer.WriteBool(_continuousCollisionHandoffPending);
 204774        if (_continuousCollisionHandoffPending)
 75        {
 3476            writer.WriteInt32(_continuousCollisionHandoffToken);
 3477            writer.WriteFixed64(_continuousCollisionHandoffRemainingTime);
 3478            writer.WriteInt32(_continuousCollisionHandoffIgnoredCollider3D?.ReplayOrdinal ?? -1);
 3479            writer.WriteInt32(_continuousCollisionHandoffIgnoredCollider2D?.ReplayOrdinal ?? -1);
 3480            WriteContinuousCollisionTrajectory(ref writer);
 81        }
 82
 204783        if (mode != GravitasReplayHashMode.AuthoritativeWithSolverCaches)
 37684            return;
 85
 167186        writer.WriteSection("body.3d.solver-caches", 3);
 167187        writer.WriteInt32(_continuousCollisionFrameToken);
 167188        writer.WriteVector3d(_continuousCollisionAngularVelocityStepStart);
 167189        WriteContinuousCollisionTrajectory(ref writer);
 167190        writer.WriteInt32(_continuousCollisionHandoffToken);
 167191        writer.WriteFixed64(_continuousCollisionHandoffRemainingTime);
 167192        writer.WriteInt32(LastContinuousCollisionToiIterationCount);
 167193        writer.WriteBool(LastContinuousCollisionToiIterationLimitReached);
 167194        writer.WriteFixed3x3(_inertiaTensor);
 167195        writer.WriteFixed3x3(_worldInertiaTensor);
 167196        writer.WriteFixed3x3(_inverseLocalInertiaTensor);
 167197        writer.WriteFixed3x3(_inverseInertiaTensor);
 167198        writer.WriteInt32(_continuousCollisionHits.Count);
 167199        writer.WriteInt32(_continuousMixedCollisionHits.Count);
 1671100        writer.WriteInt32(_rotationalContinuousCollisionManifold.Count);
 1671101    }
 102
 103    private void WriteContinuousCollisionTrajectory(ref ChronicleHashWriter writer)
 104    {
 1705105        writer.WriteInt32(_continuousCollisionTrajectory.Count);
 6388106        for (int i = 0; i < _continuousCollisionTrajectory.Count; i++)
 107        {
 1489108            Gravitas.CollisionHandling.ContinuousCollisionMotionSegment3D segment =
 1489109                _continuousCollisionTrajectory[i];
 1489110            writer.WriteFixed64(segment.StartFraction);
 1489111            writer.WriteFixed64(segment.EndFraction);
 1489112            writer.WriteVector3d(segment.StartPosition);
 1489113            writer.WriteVector3d(segment.EndPosition);
 1489114            writer.WriteVector3d(segment.Displacement);
 1489115            writer.WriteQuaternion(segment.StartRotation);
 1489116            writer.WriteVector3d(segment.AngularVelocity);
 1489117            writer.WriteQuaternion(segment.TargetRotation);
 1489118            writer.WriteFixed64(segment.AngularDistance);
 1489119            writer.WriteEnum(segment.RotationPath);
 120        }
 1705121    }
 122}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.Serialization.cs

#LineLine coverage
 1//=======================================================================
 2// SolidBody.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 partial class SolidBody
 14{
 15    /// <summary>Records or restores deterministic body state through Chronicler.</summary>
 16    public void RecordData(IChronicler chronicler)
 17    {
 8818        GroundingMode groundingMode = GroundingMode;
 8819        GroundProbeMode groundProbeMode = GroundProbeMode;
 8820        Fixed64 groundProbeRadius = GroundProbeRadius;
 8821        bool active = Active;
 8822        BodyFreezeAxes3D freezeAxes = FreezeAxes;
 8823        BodyMotionType motionType = MotionType;
 8824        Fixed64 gravityScale = GravityScale;
 8825        ContinuousCollisionMode continuousCollisionMode = ContinuousCollisionMode;
 26
 8827        RecordValues.Look(chronicler, ref active, "Active");
 8828        RecordValues.Look(chronicler, ref freezeAxes, "FreezeAxes", BodyFreezeAxes3D.None);
 8829        RecordValues.Look(chronicler, ref motionType, "MotionType", BodyMotionType.Dynamic);
 8830        if (chronicler.Mode == SerializationMode.Loading)
 31        {
 5532            PreflightLoadedMotionType(motionType);
 5233            SwiftThrowHelper.ThrowIfArgument(
 5234                (freezeAxes & ~BodyFreezeAxes3D.All) != BodyFreezeAxes3D.None,
 5235                nameof(freezeAxes),
 5236                "Unsupported 3D freeze axis bits.");
 37        }
 38
 8439        RecordValues.Look(chronicler, ref Debug, "Debug");
 8440        RecordValues.Look(chronicler, ref _position2dUnmarked, "Position2d");
 8441        RecordValues.Look(chronicler, ref _heightPosUnmarked, "HeightPos");
 8442        RecordValues.Look(chronicler, ref _lastPosition, "LastPosition");
 8443        RecordValues.Look(chronicler, ref GroundOriginOffset, "GroundOriginOffset");
 8444        RecordValues.Look(chronicler, ref GroundedDistanceRay, "GroundedDistanceRay");
 8445        RecordValues.Look(chronicler, ref GroundDownDistanceOnAir, "GroundDownDistanceOnAir");
 8446        RecordValues.Look(chronicler, ref groundingMode, "GroundingMode", GroundingMode.Automatic);
 8447        RecordValues.Look(chronicler, ref groundProbeMode, "GroundProbeMode", GroundProbeMode.Auto);
 8448        RecordValues.Look(chronicler, ref groundProbeRadius, "GroundProbeRadius");
 8449        RecordValues.Look(chronicler, ref _skipGroundingCheck, "SkipGroundingCheck", false);
 8450        RecordValues.Look(chronicler, ref _lastGroundCheckFrame, "LastGroundCheckFrame");
 8451        RecordValues.Look(chronicler, ref StepOffset, "StepOffset");
 8452        RecordValues.Look(chronicler, ref _groundNormal, "GroundNormal");
 8453        RecordValues.Look(chronicler, ref _hitPlatformPosition, "HitPlatformPosition");
 8454        RecordValues.Look(chronicler, ref _hitPoint, "HitPoint");
 8455        RecordValues.Look(chronicler, ref _hasHitPoint, "HasHitPoint");
 8456        RecordValues.Look(chronicler, ref _isGrounded, "IsGrounded");
 8457        RecordValues.Look(chronicler, ref _wasGrounded, "WasGrounded");
 8458        RecordValues.Look(chronicler, ref _lastGroundedPosition, "LastGroundedPosition");
 8459        RecordValues.Look(chronicler, ref _rotation, "Rotation");
 8460        RecordValues.Look(chronicler, ref _linearVelocity, "LinearVelocity");
 8461        RecordValues.Look(chronicler, ref _linearDirection, "LinearDirection");
 8462        RecordValues.Look(chronicler, ref _angularVelocity, "AngularVelocity");
 8463        RecordValues.Look(chronicler, ref _angularDirection, "AngularDirection");
 8464        RecordValues.Look(chronicler, ref _deltaTorque, "DeltaTorque");
 8465        RecordValues.Look(chronicler, ref _localCenterOfMassOffset, "LocalCenterOfMassOffset");
 8466        RecordValues.Look(chronicler, ref _centerOfMassOffsetExplicit, "CenterOfMassOffsetExplicit", false);
 8467        RecordValues.Look(chronicler, ref gravityScale, "GravityScale", Fixed64.One);
 8468        RecordValues.Look(chronicler, ref _isSleeping, "IsSleeping");
 8469        RecordValues.Look(chronicler, ref _sleepFrameCount, "SleepFrameCount");
 8470        RecordValues.Look(chronicler, ref _sleepEnabled, "SleepEnabled", true);
 8471        RecordValues.Look(chronicler, ref _sleepFrameThreshold, "SleepFrameThreshold", 16);
 8472        RecordValues.Look(chronicler, ref _sleepLinearSpeedThreshold, "SleepLinearSpeedThreshold", (Fixed64)0.001f);
 8473        RecordValues.Look(chronicler, ref _sleepAngularSpeedThreshold, "SleepAngularSpeedThreshold", (Fixed64)0.001f);
 8474        RecordValues.Look(chronicler, ref continuousCollisionMode, "ContinuousCollisionMode", ContinuousCollisionMode.In
 8475        RecordValues.Look(chronicler, ref _linearSpeed, "LinearSpeed");
 8476        RecordValues.Look(chronicler, ref _linearAccelerationStore, "LinearAccelerationStore");
 8477        RecordValues.Look(chronicler, ref _deltaAcceleration, "DeltaAcceleration");
 8478        RecordValues.Look(chronicler, ref _linearAcceleration, "LinearAcceleration");
 8479        RecordValues.Look(chronicler, ref _angularSpeed, "AngularSpeed");
 8480        RecordValues.Look(chronicler, ref _angularAccelerationStore, "AngularAccelerationStore");
 8481        RecordValues.Look(chronicler, ref _angularAcceleration, "AngularAcceleration");
 8482        RecordValues.Look(chronicler, ref LinearDragCoefficient, "LinearDragCoefficient");
 8483        RecordValues.Look(chronicler, ref AngularDragCoefficient, "AngularDragCoefficient");
 8484        RecordValues.Look(chronicler, ref _normalForce, "NormalForce");
 8485        RecordValues.Look(chronicler, ref Mass, "Mass");
 86
 8487        if (chronicler.Mode == SerializationMode.Loading)
 88        {
 5189            ApplyLoadedMotionType(motionType);
 5190            ContinuousCollisionMode = continuousCollisionMode;
 4991            _rotation = _rotation.Normalized;
 4992            _freezeAxes = freezeAxes;
 4993            GroundingMode = groundingMode;
 4994            GroundProbeMode = groundProbeMode;
 4995            GroundProbeRadius = groundProbeRadius;
 4996            GravityScale = gravityScale;
 4997            _hitPlatform = null;
 98        }
 99
 82100        Collider.RecordData(chronicler);
 101
 82102        if (chronicler.Mode == SerializationMode.Loading)
 103        {
 49104            ApplyLoadedState();
 49105            if (!active || Collider.Id < 0)
 8106                Deactivate();
 107        }
 82108    }
 109
 110    private void ApplyLoadedState()
 111    {
 49112        DiscardContinuousCollisionHandoff();
 49113        InvalidateContinuousCollisionTrajectory();
 49114        SetPositionTransformWorldPosition(Position3d);
 49115        SetRotationTransformWorldRotation(Rotation);
 116
 49117        _positionMutated = false;
 49118        _positionChangedBuffer = false;
 49119        _rotationMutated = false;
 49120        _rotationChangedBuffer = false;
 49121        _settingVisualsCounter = 0;
 49122        _rotationSpeed = DefaultRotationSpeed;
 49123        _rotationInterpoleSpeed = Fixed64.Zero;
 49124        _visualPosition = Position3d;
 49125        _lastVisualPosition = Position3d;
 49126        _visualRotation = Rotation;
 49127        _lastVisualRotation = Rotation;
 128
 49129        if (Collider.HasHostBinding)
 43130            RefreshMassPropertiesFromColliderShape();
 49131        ApplyFreezeConstraintsToMotion();
 132
 49133        if (Active)
 43134            Collider.Simulate();
 49135    }
 136
 137}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Core/3D/SolidBody.Visualization.cs

#LineLine coverage
 1//=======================================================================
 2// SolidBody.Visualization.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 partial class SolidBody
 14{
 15    internal void OnVisualize()
 16    {
 511317        if (!HasSolverMobility || !SettingVisuals)
 508218            return;
 19
 3120        if (Context.ResetAccumulationThisVisualize)
 21        {
 2922            if (CanSetVisualPosition)
 123                SetVisualPosition(Position3d);
 2924            if (CanSetVisualRotation)
 325                StoreVisualRotation(_rotation);
 26        }
 27
 3128        if (CanSetVisualPosition)
 29        {
 230            Vector3d expectedPosition = Vector3d.SpeedLerp(
 231                _lastVisualPosition,
 232                _visualPosition,
 233                Fixed64.One,
 234                Context.ExpectedAccumulation);
 235            SetPositionTransformWorldPosition(expectedPosition);
 36        }
 37
 3138        if (!CanSetVisualRotation)
 2739            return;
 40
 441        Fixed64 targetSpeed = ResolveVisualRotationStep();
 442        FixedQuaternion expectedRotation = _rotationInterpoleSpeed > Fixed64.Zero
 443            ? FixedQuaternion.Slerp(
 444                _rotationTransform.WorldRotation,
 445                _visualRotation,
 446                targetSpeed)
 447            : FixedQuaternion.Slerp(
 448                _lastVisualRotation,
 449                _visualRotation,
 450                targetSpeed);
 451        SetRotationTransformWorldRotation(expectedRotation);
 452    }
 53
 54    private Fixed64 ResolveVisualRotationStep()
 55    {
 456        if (_rotationInterpoleSpeed <= Fixed64.Zero)
 157            return Context.ExpectedAccumulation;
 58
 359        return FixedMath.Clamp01(
 360            Context.DeltaTime
 361            * _rotationInterpoleSpeed
 362            * _rotationSpeed);
 63    }
 64
 65    private void SetPositionTransformWorldPosition(Vector3d position)
 66    {
 627667        SwiftThrowHelper.ThrowIfTrue(
 627668            !_positionTransform.TrySetWorldPosition(position),
 627669            nameof(FixedTransform),
 627670            "Position transform cannot represent the requested world position.");
 627671    }
 72
 73    private void SetRotationTransformWorldRotation(FixedQuaternion rotation)
 74    {
 11975        SwiftThrowHelper.ThrowIfTrue(
 11976            !_rotationTransform.TrySetWorldPose(
 11977                _rotationTransform.WorldPosition,
 11978                rotation),
 11979            nameof(FixedTransform),
 11980            "Rotation transform cannot represent the requested world rotation.");
 11981    }
 82
 83    private void PublishAuthoritativePose()
 84    {
 3785        SetPositionTransformWorldPosition(Position3d);
 3786        SetRotationTransformWorldRotation(Rotation);
 3787    }
 88
 89    /// <summary>Advances authoritative pose-change buffers used by presentation and culling.</summary>
 90    public void CheckChangedValues()
 91    {
 92        // Keep each buffer set until the next visualization pass so the host
 93        // observes at least one presentation update after a state change.
 652994        if (_positionMutated)
 95        {
 556596            _positionChangedBuffer = _positionMutated;
 556597            _positionMutated = false;
 556598            _settingVisualsCounter = Context.FrameRate;
 99        }
 100        else
 101        {
 964102            _positionChangedBuffer = false;
 103        }
 104
 6529105        if (_rotationMutated)
 106        {
 4634107            _rotationChangedBuffer = _rotationMutated;
 4634108            _rotationMutated = false;
 4634109            _settingVisualsCounter = Context.FrameRate;
 110        }
 111        else
 112        {
 1895113            _rotationChangedBuffer = false;
 114        }
 1895115    }
 116
 117    /// <summary>Advances the position interpolation endpoints.</summary>
 118    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 119    public void SetVisualPosition(Vector3d position)
 120    {
 6336121        _lastVisualPosition = _visualPosition;
 6336122        _visualPosition = position;
 6336123    }
 124
 125    /// <summary>Advances the rotation interpolation endpoints.</summary>
 126    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 127    public void SetVisualRotation(FixedQuaternion rotation) =>
 5128        StoreVisualRotation(rotation.Normalized);
 129
 130    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 131    private void StoreVisualRotation(FixedQuaternion rotation)
 132    {
 6339133        _lastVisualRotation = _visualRotation;
 6339134        _visualRotation = rotation;
 6339135    }
 136}

Methods/Properties

get_LocalCenterOfMassOffset()
set_LocalCenterOfMassOffset(FixedMathSharp.Vector3d)
get_WorldCenterOfMass()
TryGetWorldCenterOfMass(FixedMathSharp.Vector3d&)
TryGetOffsetFromCenterOfMass(Gravitas.CollisionHandling.ContactAnchor,FixedMathSharp.Vector3d&)
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)
InvalidateContinuousCollisionTrajectory()
SetSingleContinuousCollisionSegment(Gravitas.CollisionHandling.ContinuousCollisionMotionSegment3D)
AppendContinuousCollisionSegment(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64)
AppendContinuousCollisionSegment(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64)
CanAppendContinuousCollisionSegment(FixedMathSharp.Fixed64)
TryResolveContinuousCollisionMotionBound(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
ResolveContinuousCollisionTrajectoryBounds(FixedMathSharp.Fixed64)
RemoveSupersededContinuousCollisionSegments(FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion)
ResolveContinuousCollisionSegment(FixedMathSharp.Fixed64)
GetContinuousCollisionTrajectorySegmentIndex(FixedMathSharp.Fixed64)
GetContinuousCollisionTrajectorySegmentRange(FixedMathSharp.Fixed64,System.Int32&,System.Int32&)
ApplyContinuousCollisionHandoff(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,Gravitas.Colliders.LSCollider,Gravitas.Colliders.LSCollider2D)
ApplyContinuousCollisionHandoff(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,Gravitas.Colliders.LSCollider,Gravitas.Colliders.LSCollider2D)
ApplyContinuousCollisionHandoffReserved(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,Gravitas.Colliders.LSCollider,Gravitas.Colliders.LSCollider2D)
CanApplyContinuousCollisionHandoff(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d&)
TryConsumeContinuousCollisionHandoff(System.Boolean,System.Boolean)
TryConsumeQueuedContinuousCollisionHandoff(System.Boolean,System.Boolean,System.Boolean&)
TryConsumeContinuousCollisionHandoff(System.Boolean,System.Boolean,System.Boolean,System.Boolean&)
NotifyAuthoritativeMovement()
DiscardContinuousCollisionHandoff()
TryResolveContinuousCollision(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&)
TryResolveContinuousCollision(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Boolean)
TryApplyContinuousCollisionDynamicResponse(FixedMathSharp.Vector3d,Gravitas.CollisionHandling.ContinuousCollisionTargetKind,Gravitas.Colliders.LSCollider,Gravitas.Colliders.LSCollider2D,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
TryApplyContinuousCollisionDynamicResponse(Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
TryApplyContinuousCollisionMixed2DResponse(Gravitas.SolidBody2D,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
UpdateContinuousCollisionFrameTrajectory(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64)
ResolveContinuousCollisionFrameFraction(FixedMathSharp.Fixed64)
ResolveContinuousCollisionRestitution(Gravitas.SolidBody,FixedMathSharp.Fixed64)
ResolveContinuousCollisionRestitution(Gravitas.SolidBody2D,FixedMathSharp.Fixed64)
RemoveClosingContinuousCollisionVelocity(FixedMathSharp.Vector3d)
IsEligibleDynamicContinuousCollisionTarget(Gravitas.SolidBody)
IsEligibleDynamicMixed2DTarget(Gravitas.SolidBody2D)
ShouldUseContinuousCollision(Gravitas.ContinuousCollisionMode&)
ResolveContinuousCollisionMode()
ResolveContinuousCollisionProxyRadius()
IsClosingContinuousCollisionHit(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
IsValidContinuousCollisionTarget(Gravitas.Colliders.LSCollider)
IsValidMixedContinuousCollisionHit(Gravitas.Colliders.LSCollider2D)
TryGetFirstContinuousCollisionHit(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d&,FixedMathSharp.Fixed64&,Gravitas.CollisionHandling.ContinuousCollisionTargetKind&,Gravitas.Colliders.LSCollider&,Gravitas.Colliders.LSCollider2D&)
TryGetFirstValidContinuousCollisionHit(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,System.Int32,System.Boolean,Gravitas.Queries.Physics3DHit&)
QueryStaticContinuousCollisionHits(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,System.Boolean&)
TryGetFirstValidMixedContinuousCollisionHit(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,System.Int32,Gravitas.Queries.PhysicsMixedHit&)
TryRefineShapeExactContinuousCollisionHit(Gravitas.Queries.Physics3DHit,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.Queries.Physics3DHit&,System.Boolean&)
TryRefineContinuousCollisionAgainstTargetSphere(Gravitas.Colliders.LSSphereCollider,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.Queries.Physics3DHit&,System.Boolean&)
ApplyShapeExactContinuousContactSlop(Gravitas.Queries.Physics3DHit)
PrepareExactConvexSourceSweep(FixedMathSharp.Vector3d)
IsExactConvexSourceSupported(Gravitas.Colliders.LSCollider)
ContinuousCollisionHitComesBefore(Gravitas.Queries.Physics3DHit,Gravitas.Queries.Physics3DHit)
TryGetFirstDynamicContinuousCollisionHit(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.Queries.Physics3DHit&,FixedMathSharp.Fixed64&)
TryGetDynamicRelativeContinuousCollisionHit(Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.Queries.Physics3DHit&,FixedMathSharp.Fixed64&)
TrySweepRelativeSourceSphere(Gravitas.Colliders.LSSphereCollider,Gravitas.Colliders.LSCollider,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.Queries.Physics3DHit&)
TryGetFirstDynamicMixedContinuousCollisionHit(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.DynamicMixedIntervalHit&)
TryGetDynamicMixed2DContinuousCollisionHit(Gravitas.SolidBody2D,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.DynamicMixedIntervalHit&)
TryResolveKinematicContinuousCollision(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&)
TryGetFirstKinematicStaticContinuousCollisionHit(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
TryApplyKinematicDynamicContinuousCollisionPushes(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Boolean&,FixedMathSharp.Fixed64&)
GatherKinematicDynamic3DContinuousCollisionHits(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
GatherKinematicDynamicMixed2DContinuousCollisionHits(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Boolean&,FixedMathSharp.Fixed64&)
ApplyKinematicContinuousCollisionHandoff(Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
ApplyKinematicContinuousCollisionHandoff(Gravitas.SolidBody2D,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
TryResolveRotationalContinuousCollision(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&,FixedMathSharp.FixedQuaternion,FixedMathSharp.FixedQuaternion&)
TryResolveRotationalContinuousCollision(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&,FixedMathSharp.FixedQuaternion,FixedMathSharp.FixedQuaternion&,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Boolean)
TryResolveKinematicRotationalContinuousCollision(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&,FixedMathSharp.FixedQuaternion,FixedMathSharp.FixedQuaternion&)
TryFindEarliestRotationalContinuousCollisionAgainstTarget(Gravitas.Colliders.LSCollider,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.FixedQuaternion,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Boolean,FixedMathSharp.Fixed64&,Gravitas.CollisionHandling.ManifoldContact&,System.Boolean&,FixedMathSharp.Fixed64&)
IsRotationalIntervalSeparated(Gravitas.Colliders.LSCollider,FixedMathSharp.Fixed64)
TryGetSpherePairSeparationGap(Gravitas.Colliders.LSCollider,FixedMathSharp.Fixed64&)
TryGetSphereSeparationGap(Gravitas.Colliders.LSSphereCollider,Gravitas.Colliders.LSCollider,FixedMathSharp.Fixed64&)
SampleDynamicRotationalContinuousPose(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
SampleKinematicRotationalContinuousPose(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.FixedQuaternion,FixedMathSharp.Fixed64)
ResolveKinematicAngularDistanceRadians(FixedMathSharp.FixedQuaternion,FixedMathSharp.FixedQuaternion)
TrySampleRotationalContinuousCollision(Gravitas.Colliders.LSCollider,Gravitas.CollisionHandling.ManifoldContact&)
ResolveSourceContactNormal(Gravitas.Colliders.LSCollider,Gravitas.CollisionHandling.ManifoldContact)
IsMovingRotationalContinuousCollisionTarget(Gravitas.SolidBody)
ResolveRotationalFrameFraction(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
OrderRotationalContinuousCollisionPair(Gravitas.Colliders.LSCollider,Gravitas.Colliders.LSCollider&,Gravitas.Colliders.LSCollider&,System.Boolean&)
IntegrateAngularRotation(FixedMathSharp.FixedQuaternion,FixedMathSharp.Fixed64)
IntegrateAngularRotation(FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64)
.ctor(Gravitas.IMatterAgent,Gravitas.Colliders.LSCollider)
SetMixedContinuousCollisionSamplePose(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion)
.ctor(FixedMathSharp.Fixed64,Gravitas.CollisionHandling.MixedContact,System.Boolean,FixedMathSharp.Fixed64,Gravitas.Colliders.LSCollider2D)
TryFindEarliestMixedRotationalContinuousCollision(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.FixedQuaternion,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Boolean,Gravitas.SolidBody/RotationalMixed2DHit&)
GatherMixedRotationalContinuousCollisionCandidates(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64)
ConsiderMixedRotationalContinuousCollisionCandidate(Gravitas.Colliders.LSCollider2D,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.FixedQuaternion,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Boolean,System.Boolean&,FixedMathSharp.Fixed64&,System.Int32&,Gravitas.CollisionHandling.MixedContact&,System.Boolean&,FixedMathSharp.Fixed64&,Gravitas.Colliders.LSCollider2D&)
TryFindEarliestMixedRotationalContinuousCollisionAgainstTarget(Gravitas.Colliders.LSCollider2D,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.FixedQuaternion,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Boolean,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&,Gravitas.CollisionHandling.MixedContact&,System.Boolean&,FixedMathSharp.Fixed64&)
SampleMixedRotationalContinuousPairPose(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.FixedQuaternion,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Boolean,Gravitas.SolidBody2D,System.Boolean)
TryGetExactSphereCircleTranslationalContact(Gravitas.SolidBody2D,Gravitas.CollisionHandling.ContinuousCollisionMotionSegment2D,FixedMathSharp.Geometry.FixedSegment2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Geometry.FixedSegment,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.FixedQuaternion,FixedMathSharp.Fixed64,System.Boolean&,FixedMathSharp.Fixed64&,FixedMathSharp.Fixed64&,FixedMathSharp.Fixed64&,Gravitas.CollisionHandling.MixedContact&)
IsValidMixedRotationalContinuousCollisionTarget(Gravitas.Colliders.LSCollider2D)
IsMovingMixedRotationalContinuousCollisionTarget(Gravitas.SolidBody2D)
TryApplyMixedRotationalContinuousCollisionResponse(Gravitas.Colliders.LSCollider2D,Gravitas.CollisionHandling.MixedContact,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.FixedQuaternion,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Boolean)
TryApplyRotationalContinuousCollisionResponse(Gravitas.SolidBody,Gravitas.CollisionHandling.ManifoldContact,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Boolean)
TryCalculateExactRotationalVelocityDeltas(Gravitas.CollisionHandling.ManifoldContact,Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ContactNormalVelocityDeltaResult3D&)
ResolveContinuousCollisionInverseInertia(FixedMathSharp.FixedQuaternion)
StopRotationalContinuousCollision(FixedMathSharp.Vector3d)
HasNearbyRotationalContinuousCollisionTarget(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64)
IsRotatingContinuousCollisionTarget(Gravitas.SolidBody)
GatherRotationalContinuousCollisionCandidates(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64)
GatherAllRegisteredRotationalContinuousCollisionCandidates()
TryFindEarliestRotationalContinuousCollision(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.FixedQuaternion,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Boolean,Gravitas.Colliders.LSCollider,FixedMathSharp.Fixed64&,Gravitas.CollisionHandling.ManifoldContact&,System.Boolean&,FixedMathSharp.Fixed64&,Gravitas.Colliders.LSCollider&)
.ctor(Gravitas.IMatterAgent,Gravitas.Colliders.LSCollider)
get_DynamicId()
get_FreezeAxes()
set_FreezeAxes(Gravitas.BodyFreezeAxes3D)
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)
SetDynamicId(System.Int32)
get_ContinuousCollisionMode()
set_ContinuousCollisionMode(Gravitas.ContinuousCollisionMode)
get_PositionTransform()
get_RotationTransform()
get_PositionChangePending()
SetPosition2d(FixedMathSharp.Vector2d)
get_Position3d()
set_Position3d(FixedMathSharp.Vector3d)
get_HeightPos()
set_HeightPos(FixedMathSharp.Fixed64)
.cctor()
get_VisualPosition()
get_LastVisualPosition()
get_RotationChangePending()
get_Rotation()
set_Rotation(FixedMathSharp.FixedQuaternion)
get_Forward()
get_Up()
get_Right()
get_VisualRotation()
get_LastVisualRotation()
get_IsRotationFullyFrozen()
get_SettingVisuals()
get_LinearVelocity()
get_AngularVelocity()
get_InverseInertiaTensor()
get_CanTranslate()
get_CanRotate()
get_HasSolverMobility()
get_EffectiveInverseMass()
get_EffectiveInverseInertiaTensor()
ProjectLinearMotion(FixedMathSharp.Vector3d)
ProjectLinearEndpoint(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
ProjectAngularMotion(FixedMathSharp.Vector3d)
GetConstrainedInverseMass(FixedMathSharp.Vector3d)
ApplyConstrainedInverseInertia(FixedMathSharp.Vector3d)
GetConstrainedInverseInertiaTensor()
get_GravityScale()
set_GravityScale(FixedMathSharp.Fixed64)
get_IsAtRest()
get_IsSleeping()
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_IsAwakeForCollision()
get_LinearSpeed()
get_LinearAcceleration()
get_AngularSpeed()
get_AngularAcceleration()
get_InverseMass()
get_Weight()
get_World()
Initialize(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,Gravitas.BodyMotionType)
LateSimulate()
LateSimulate(System.Boolean,System.Boolean)
UpdateSleepStateAfterPhysicsStep()
UpdateKinematicPositionAndRotation()
Sleep()
Wake()
WakeFromCollision()
get_CanSleep()
SetTransformWorldPose(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion)
Deactivate()
UpdateRotation(FixedMathSharp.FixedQuaternion,FixedMathSharp.Fixed64)
GetWorldPoint(FixedMathSharp.Vector3d)
TryGetWorldPoint(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&)
GetLocalPoint(FixedMathSharp.Vector3d)
TryGetLocalPoint(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&)
ResetPosition(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion)
.ctor(Gravitas.IMatterAgent,Gravitas.Colliders.LSCollider)
get_GroundNormal()
get_HitPlatform()
get_HasHitPoint()
get_HitPoint()
TryGetHitPoint(FixedMathSharp.Vector3d&)
get_IsGrounded()
set_IsGrounded(System.Boolean)
get_WasGrounded()
get_LastGroundedPosition()
SkipGrounding(FixedMathSharp.Fixed64)
UseManualGrounding(System.Boolean)
UseAutomaticGrounding(System.Boolean)
SetManualGrounding(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.FixedTransform)
ClearManualGrounding()
SkipGroundingCoroutine()
CheckGround()
CheckGroundForSimulation()
CaptureGroundedTransitionState()
CaptureGroundedStepState()
CompleteGroundedStepState()
CheckGround(System.Boolean)
TryFindGroundHit(Gravitas.GroundProbeMode,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,Gravitas.Queries.Physics3DHit&)
TryFindGroundHitWithRay(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,Gravitas.Queries.Physics3DHit&)
TryFindGroundHitWithSweptSphere(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.Queries.Physics3DHit&)
IsValidGroundHit(Gravitas.Queries.Physics3DHit)
ResolveGroundProbeMode()
ResolveGroundProbeRadius()
ClearGrounding()
ApplyGroundedHeightOrReset()
SetGroundingState(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.FixedTransform)
SetGroundingState(Gravitas.CollisionHandling.ContactAnchor,FixedMathSharp.Vector3d,FixedMathSharp.FixedTransform)
RefreshGroundNormalForce()
ResetGroundCalculations()
get_CanUseAngularInertia()
UpdateSleepState()
ClearMotionForSleep()
ApplyFreezeConstraintsToMotion()
RefreshPartitionAwakeState()
RefreshPartitionMobility()
AddTorque(FixedMathSharp.Vector3d)
AddForce(FixedMathSharp.Vector3d)
AddLinearImpulse(FixedMathSharp.Vector3d)
AddAngularImpulse(FixedMathSharp.Vector3d)
ApplyCollisionLinearVelocityDelta(FixedMathSharp.Vector3d)
ApplyCollisionAngularVelocityDelta(FixedMathSharp.Vector3d)
CanApplyCollisionVelocityDeltas(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
TryPrepareCollisionVelocityState(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&,FixedMathSharp.Vector3d&)
ApplyCollisionVelocityState(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
ApplyCollisionPositionCorrection(FixedMathSharp.Vector3d)
SetPosition(FixedMathSharp.Vector3d)
SetHeight(FixedMathSharp.Fixed64)
SetRotation(FixedMathSharp.FixedQuaternion)
PrepareExplicitBodyPose(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,System.String,System.String)
PublishExplicitBodyPose(System.Boolean)
PreflightStaticPoseChange()
PreflightResetPoseChange()
ProcessMovable()
ApplyLinearForces()
ApplyDragForce()
ApplyFrictionForce()
UpdateLinearVelocity()
RefreshLinearMotionState(FixedMathSharp.Vector3d)
ApplyAngularTorques()
ResolveAngularAccelerationForStep()
UpdateAngularVelocity()
ResolveAngularVelocityForStep(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
RefreshAngularMotionState(FixedMathSharp.Vector3d)
ResolveGroundDynamicFriction()
NonKinematicUpdate(FixedMathSharp.Vector3d)
PositionBasedOnForce()
ApplyResolvedPosition(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
RotationBasedOnTorque(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
RefreshMassPropertiesFromColliderShape()
UpdateInertiaTensorOrientation()
RefreshInertiaTensor()
ApplyGyroscopicPrecession(FixedMathSharp.Vector3d)
ContributeReplayHash(Chronicler.ChronicleHashWriter&,Gravitas.GravitasReplayHashMode)
WriteContinuousCollisionTrajectory(Chronicler.ChronicleHashWriter&)
RecordData(Chronicler.IChronicler)
ApplyLoadedState()
OnVisualize()
ResolveVisualRotationStep()
SetPositionTransformWorldPosition(FixedMathSharp.Vector3d)
SetRotationTransformWorldRotation(FixedMathSharp.FixedQuaternion)
PublishAuthoritativePose()
CheckChangedValues()
SetVisualPosition(FixedMathSharp.Vector3d)
SetVisualRotation(FixedMathSharp.FixedQuaternion)
StoreVisualRotation(FixedMathSharp.FixedQuaternion)