< Summary

Information
Class: Gravitas.CollisionHandling
Assembly: Gravitas
File(s): File 1: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/ColliderCollisionFilter.cs
File 2: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Contacts/2D/Contact2D.cs
File 3: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Contacts/2D/ContactAnchor2D.cs
File 4: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Contacts/2D/ContactManifold2D.cs
File 5: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Contacts/2D/ContactWarmStartCache2D.cs
File 6: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Contacts/2D/ManifoldContact2D.cs
File 7: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Contacts/3D/ContactAnchor.cs
File 8: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Contacts/3D/ContactManifold.cs
File 9: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Contacts/3D/ContactWarmStartCache.cs
File 10: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Contacts/3D/ManifoldContact.cs
File 11: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Contacts/ContactSelectionPolicy.cs
File 12: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Contacts/Mixed/MixedContact.cs
File 13: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Contacts/Shared/ContactWarmStartImpulse.cs
File 14: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Continuous/ContinuousCollisionCandidateOrdering.cs
File 15: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Continuous/ContinuousCollisionContactPolicy.cs
File 16: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Continuous/ContinuousCollisionImpulsePolicy.cs
File 17: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Continuous/ContinuousCollisionMath.cs
File 18: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Continuous/ContinuousCollisionMotionSegment2D.cs
File 19: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Continuous/ContinuousCollisionMotionSegment3D.cs
File 20: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Continuous/ContinuousCollisionSweepRange.cs
File 21: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Continuous/ContinuousCollisionTargetPolicy.cs
File 22: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Continuous/DynamicCcdCandidateIndex.cs
File 23: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/2D/CollisionDetection2D.cs
File 24: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/2D/CollisionWorkItem2D.cs
File 25: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/3D/AxisPenetration.cs
File 26: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/3D/CollisionDetection.Capsule.cs
File 27: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/3D/CollisionDetection.Compound.cs
File 28: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/3D/CollisionDetection.Cone.cs
File 29: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/3D/CollisionDetection.cs
File 30: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/3D/CollisionDetection.Cuboid.cs
File 31: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/3D/CollisionDetection.Cylinder.cs
File 32: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/3D/CollisionDetection.Mesh.cs
File 33: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/3D/CollisionDetection.Sphere.cs
File 34: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/3D/CollisionWorkItem.cs
File 35: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/3D/ConvexColliderSupport.cs
File 36: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/3D/CylinderContactGeometry.cs
File 37: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/3D/GjkSimplexPolicy.cs
File 38: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/3D/Mesh/MeshTriangleContactGenerator.cs
File 39: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/Mixed/CollisionDetectionMixed.Complex.cs
File 40: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/Mixed/CollisionDetectionMixed.cs
File 41: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/Mixed/CollisionDetectionMixed.FinitePrisms.cs
File 42: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/Mixed/CollisionDetectionMixed.Separation.cs
File 43: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/Mixed/CollisionDetectionMixed.Support.cs
File 44: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/Mixed/MixedEmbedded2DGeometry.cs
File 45: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Pairs/3D/CollisionPair.cs
File 46: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Pairs/3D/CollisionPair.ReplayHash.cs
File 47: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Pairs/CollisionNotificationExceptions.cs
File 48: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Pairs/Mixed/CollisionPairMixed.cs
File 49: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Pairs/Mixed/CollisionPairMixed.ReplayHash.cs
File 50: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/2D/CollisionResponse2D.cs
File 51: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/2D/ContactNormalImpulse2D.cs
File 52: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/2D/ExactContactLever2D.cs
File 53: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/2D/ResponseBody2D.cs
File 54: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/2D/SolverContact2D.cs
File 55: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/2D/SolverContactBuffer2D.cs
File 56: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/3D/CollisionResponse.cs
File 57: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/3D/ContactNormalImpulse3D.cs
File 58: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/3D/ContactResponseArithmetic3D.cs
File 59: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/3D/ExactContactLever3D.cs
File 60: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/3D/ResponseBody.cs
File 61: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/3D/SolverContact.cs
File 62: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/3D/SolverContactBuffer.cs
File 63: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/Exact/ExactContactResponseKernel.Coulomb.cs
File 64: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/Exact/ExactContactResponseKernel.CoulombDiskCache.cs
File 65: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/Exact/ExactContactResponseKernel.Normal.cs
File 66: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/Exact/ExactContactResponseOperand3D.cs
File 67: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/Exact/ExactCoulombResponse3D.cs
File 68: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/Exact/ExactLever3D.Arithmetic.cs
File 69: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/Exact/ExactLever3D.cs
File 70: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/Exact/ExactNormalConstraint3D.cs
File 71: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/Exact/ExactNormalResponse3D.cs
File 72: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/Mixed/CollisionResponseMixed.cs
File 73: /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/Mixed/ContactNormalImpulseMixed.cs
Line coverage
100%
Covered lines: 11932
Uncovered lines: 0
Coverable lines: 11932
Total lines: 21341
Line coverage: 100%
Branch coverage
100%
Covered branches: 2988
Total branches: 2988
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: AllowsPhysicalPair(...)100%22100%
File 1: AllowsPhysicalPair(...)100%22100%
File 1: AllowsPhysicalPair(...)100%22100%
File 2: .ctor(...)100%11100%
File 2: .ctor(...)100%11100%
File 2: get_PointA()100%11100%
File 2: get_PointB()100%11100%
File 2: GetRequiredWorldPoint(...)100%22100%
File 3: .ctor(...)100%11100%
File 3: .ctor(...)100%11100%
File 3: .ctor(...)100%11100%
File 3: .ctor(...)100%11100%
File 3: get_Origin()100%11100%
File 3: get_LocalPoint()100%11100%
File 3: get_LocalDisplacement()100%11100%
File 3: CompareLocalFeature(...)100%11100%
File 3: GetLocalFeatureHash64()100%11100%
File 3: get_Rotation()100%11100%
File 3: get_Offset()100%11100%
File 3: FromWorldPoint(...)100%11100%
File 3: TryGetOffset(...)100%11100%
File 3: TryGetWorldPoint(...)100%11100%
File 3: TryGetOffsetFrom(...)100%11100%
File 3: TryGetOffsetFrom(...)100%11100%
File 3: GetXZLeverFrom(...)100%11100%
File 3: TryRebase(...)100%22100%
File 3: TryRebase(...)100%22100%
File 4: .ctor()100%11100%
File 4: get_Count()100%11100%
File 4: get_HasContact()100%11100%
File 4: get_LastUpdatedFrame()100%11100%
File 4: get_PrimaryContact()100%88100%
File 4: get_Item(...)100%11100%
File 4: BeginUpdate(...)100%11100%
File 4: Reset()100%11100%
File 4: SetContact(...)100%11100%
File 4: SetContact(...)100%11100%
File 4: AddContact(...)100%11100%
File 4: AddContact(...)100%11100%
File 4: AddContact(...)100%11100%
File 4: AddContact(...)100%11100%
File 4: AddContactCore(...)100%1010100%
File 4: GetEnumerator()100%11100%
File 4: System.Collections.Generic.IEnumerable<Gravitas.CollisionHandling.ManifoldContact2D>.GetEnumerator()100%11100%
File 4: System.Collections.IEnumerable.GetEnumerator()100%11100%
File 4: FindShallowestReplacementIndex(...)100%66100%
File 4: IsDeeper(...)100%66100%
File 4: HasEqualDepth(...)100%22100%
File 4: SortContactsById()100%44100%
File 4: GetContactUnchecked(...)100%22100%
File 4: SetContactUnchecked(...)100%22100%
File 4: CreateContactId(...)100%22100%
File 4: CompareLocalFeature(...)100%22100%
File 4: Mix(...)100%11100%
File 4: .ctor(...)100%11100%
File 4: get_Current()100%11100%
File 4: System.Collections.IEnumerator.get_Current()100%11100%
File 4: MoveNext()100%22100%
File 4: Reset()100%11100%
File 4: Dispose()100%11100%
File 5: Clear()100%11100%
File 5: Set(...)100%66100%
File 5: TryGet(...)100%44100%
File 5: Remove(...)100%66100%
File 5: GetContactIdForReplayHash(...)100%11100%
File 5: GetImpulseForReplayHash(...)100%11100%
File 5: GetContactId(...)100%22100%
File 5: GetImpulseUnchecked(...)100%22100%
File 5: SetContactIdUnchecked(...)100%22100%
File 5: SetImpulseUnchecked(...)100%22100%
File 6: .ctor(...)100%11100%
File 6: .ctor(...)100%11100%
File 6: .ctor(...)100%66100%
File 6: get_PointA()100%11100%
File 6: get_PointB()100%11100%
File 6: TryGetPointA(...)100%11100%
File 6: TryGetPointB(...)100%11100%
File 6: GetRequiredWorldPoint(...)100%22100%
File 7: .ctor(...)100%11100%
File 7: .ctor(...)100%11100%
File 7: .ctor(...)100%11100%
File 7: .ctor(...)100%22100%
File 7: get_Origin()100%11100%
File 7: get_Rotation()100%11100%
File 7: get_IsValid()100%11100%
File 7: get_LocalPoint()100%11100%
File 7: get_LocalDisplacement()100%11100%
File 7: CompareLocalFeature(...)100%11100%
File 7: GetLocalFeatureHash64()100%11100%
File 7: get_Offset()100%11100%
File 7: FromWorldPoint(...)100%11100%
File 7: TryGetWorldPoint(...)100%11100%
File 7: TryGetOffset(...)100%11100%
File 7: TryGetOffsetFrom(...)100%11100%
File 7: TryGetOffsetFrom(...)100%11100%
File 7: GetLeverFrom(...)100%11100%
File 7: TryRebase(...)100%22100%
File 7: TryRebase(...)100%22100%
File 8: .ctor()100%11100%
File 8: get_Count()100%11100%
File 8: get_HasContact()100%11100%
File 8: get_LastUpdatedFrame()100%11100%
File 8: get_PrimaryContact()100%88100%
File 8: get_Item(...)100%11100%
File 8: BeginUpdate(...)100%11100%
File 8: Reset()100%11100%
File 8: SetContact(...)100%11100%
File 8: SetContact(...)100%11100%
File 8: AddContact(...)100%11100%
File 8: AddContact(...)100%11100%
File 8: AddContact(...)100%11100%
File 8: AddContact(...)100%11100%
File 8: AddContactCore(...)100%1010100%
File 8: GetEnumerator()100%11100%
File 8: System.Collections.Generic.IEnumerable<Gravitas.CollisionHandling.ManifoldContact>.GetEnumerator()100%11100%
File 8: System.Collections.IEnumerable.GetEnumerator()100%11100%
File 8: FindShallowestReplacementIndex(...)100%1010100%
File 8: IsDeeper(...)100%66100%
File 8: HasEqualDepth(...)100%22100%
File 8: SortContactsById()100%66100%
File 8: GetContactUnchecked(...)100%44100%
File 8: SetContactUnchecked(...)100%44100%
File 8: CreateContactId(...)100%22100%
File 8: CompareLocalFeature(...)100%22100%
File 8: Mix(...)100%11100%
File 8: MixLocalFeature(...)100%11100%
File 8: .ctor(...)100%11100%
File 8: get_Current()100%11100%
File 8: System.Collections.IEnumerator.get_Current()100%11100%
File 8: MoveNext()100%22100%
File 8: Reset()100%11100%
File 8: Dispose()100%11100%
File 9: Clear()100%11100%
File 9: Set(...)100%66100%
File 9: TryGet(...)100%44100%
File 9: Remove(...)100%66100%
File 9: GetContactIdForReplayHash(...)100%11100%
File 9: GetImpulseForReplayHash(...)100%11100%
File 9: GetContactId(...)100%44100%
File 9: GetImpulseUnchecked(...)100%44100%
File 9: SetContactIdUnchecked(...)100%44100%
File 9: SetImpulseUnchecked(...)100%44100%
File 10: .ctor(...)100%11100%
File 10: .ctor(...)100%11100%
File 10: .ctor(...)100%1010100%
File 10: get_PointA()100%11100%
File 10: get_PointB()100%11100%
File 10: TryGetPointA(...)100%11100%
File 10: TryGetPointB(...)100%11100%
File 10: GetRequiredWorldPoint(...)100%22100%
File 11: ShouldReplaceWithDeeper(...)100%88100%
File 11: ShouldReplaceWithShallower(...)100%88100%
File 12: .ctor(...)100%11100%
File 12: .ctor(...)100%11100%
File 12: .ctor(...)100%11100%
File 12: .ctor(...)100%11100%
File 12: .ctor(...)100%88100%
File 12: get_Point3D()100%11100%
File 12: get_Point2D()100%11100%
File 12: TryGetPoint3D(...)100%11100%
File 12: TryGetPoint2D(...)100%11100%
File 12: TryGetPlanarOffset2DFrom(...)100%11100%
File 12: TryGetPlanarOffset2DFrom(...)100%22100%
File 12: GetPlanarXZLeverFrom(...)100%11100%
File 12: WithMaterialOverride(...)100%11100%
File 12: WithFallbackMaterials(...)100%22100%
File 12: CreatePlanarReference(...)100%11100%
File 12: GetRequiredWorldPoint(...)100%22100%
File 13: .ctor(...)100%11100%
File 13: .ctor(...)100%11100%
File 14: Is2DHitFirst(...)100%11100%
File 14: ShouldReplaceHit(...)100%1212100%
File 14: ShouldReplaceHit(...)100%88100%
File 14: ShouldReplaceMixedHit(...)100%2020100%
File 14: IsIgnoredTarget(...)100%2020100%
File 14: IsIgnoredTarget(...)100%2020100%
File 15: TryResolveSweptSphereContact(...)100%3232100%
File 15: ResolveLegacyNormal(...)100%66100%
File 16: TryResolveVelocityDelta(...)100%22100%
File 16: TryResolveVelocityDelta(...)100%22100%
File 16: TryResolveVelocityDelta(...)100%88100%
File 16: TryResolveVelocityDelta(...)100%66100%
File 16: TryResolveSourceNormal(...)100%44100%
File 16: TryResolveSourceNormal(...)100%44100%
File 16: TryResolveImpactNormal(...)100%22100%
File 16: TryResolveImpactNormal(...)100%22100%
File 17: .cctor()100%11100%
File 17: .ctor(...)100%11100%
File 17: TryResolveRotationalSearchLimit(...)100%1414100%
File 17: ShouldContinueRotationalArbiter(...)100%22100%
File 17: TryResolveRotationalIntervalMotionBound(...)100%22100%
File 17: TryResolveRotationalIntervalMotionBound(...)100%22100%
File 17: AreBoundsSeparatedByMoreThan(...)100%11100%
File 17: AreBoundsSeparatedByMoreThan(...)100%11100%
File 17: TrySubtractClosestFeatureUncertainty(...)100%22100%
File 17: IsAxisGapGreaterThan(...)100%22100%
File 17: IsWithinProxyRadius(...)100%66100%
File 17: IsWithinProxyRadius(...)100%66100%
File 17: ResolveContactPointOnTarget(...)100%44100%
File 17: ShouldReplaceContinuousCollisionHit(...)100%44100%
File 17: ClipTranslationalTrajectoryInterval(...)100%11100%
File 17: IsSupersededTranslationalBoundaryHit(...)100%66100%
File 17: TryNormalizeTranslationalClosingSpeed(...)100%44100%
File 17: TryGetRelativeSphereOverlapDistanceInterval(...)100%66100%
File 17: TryGetRelativeCircleOverlapDistanceInterval(...)100%66100%
File 17: HasUsableCombinedRadius(...)100%44100%
File 17: GetSweepEnd(...)100%22100%
File 17: GetSweepEnd(...)100%22100%
File 17: ResolveNormal(...)100%22100%
File 17: ResolveNormal(...)100%22100%
File 18: .ctor(...)100%11100%
File 18: get_AngularDistance()100%11100%
File 18: SamplePosition(...)100%11100%
File 18: SampleRotation(...)100%11100%
File 18: TryResolveMotionBound(...)100%22100%
File 18: ResolveLocalFraction(...)100%11100%
File 19: .ctor(...)100%11100%
File 19: SamplePosition(...)100%11100%
File 19: SampleRotation(...)100%22100%
File 19: TryResolveMotionBound(...)100%22100%
File 19: ResolveLocalFraction(...)100%11100%
File 20: ValidateEndpoint(...)100%44100%
File 20: ValidateEndpoint(...)100%44100%
File 20: ValidateEndpoint(...)100%22100%
File 20: ValidateEndpoint(...)100%22100%
File 20: ValidateRelativeDisplacement(...)100%44100%
File 20: ValidateRelativeDisplacement(...)100%44100%
File 21: AllowsIndexed3DTarget(...)100%66100%
File 21: AllowsIndexed2DTarget(...)100%66100%
File 21: AllowsMixedIndexedTarget(...)100%66100%
File 21: AllowsStaticOrKinematic3DTarget(...)100%66100%
File 21: AllowsStaticOrKinematic2DTarget(...)100%66100%
File 21: AllowsMixedStaticOrKinematicTarget(...)100%44100%
File 22: .ctor(...)100%66100%
File 22: get_Count()100%11100%
File 22: Clear()100%22100%
File 22: Add(...)100%22100%
File 22: AddOrUpdate(...)100%88100%
File 22: Remove(...)100%88100%
File 22: Sort()100%44100%
File 22: Query(...)100%1212100%
File 22: CreateSweptSphereBounds(...)100%11100%
File 22: CreateSweptBounds(...)100%11100%
File 22: CreateBoundsBetween(...)100%11100%
File 22: IncludeExtent(...)100%66100%
File 22: RemoveExtent(...)100%44100%
File 22: RebuildExtents()100%22100%
File 22: FindFirstCandidateIndex(...)100%44100%
File 22: IsOrderedAt(...)100%66100%
File 22: HeapSort()100%44100%
File 22: SiftDown(...)100%1010100%
File 22: Swap(...)100%22100%
File 22: Compare(...)100%1212100%
File 22: .ctor(...)100%11100%
File 22: Intersects(...)100%1010100%
File 22: .ctor(...)100%11100%
File 22: .ctor(...)100%66100%
File 22: get_Count()100%11100%
File 22: Clear()100%22100%
File 22: Add(...)100%22100%
File 22: AddOrUpdate(...)100%88100%
File 22: Remove(...)100%88100%
File 22: Sort()100%44100%
File 22: Query(...)100%1212100%
File 22: CreateSweptCircleBounds(...)100%11100%
File 22: CreateBoundsBetween(...)100%11100%
File 22: IncludeExtent(...)100%66100%
File 22: RemoveExtent(...)100%44100%
File 22: RebuildExtents()100%22100%
File 22: FindFirstCandidateIndex(...)100%44100%
File 22: IsOrderedAt(...)100%66100%
File 22: HeapSort()100%44100%
File 22: SiftDown(...)100%1010100%
File 22: Swap(...)100%22100%
File 22: Compare(...)100%88100%
File 22: .ctor(...)100%11100%
File 22: Intersects(...)100%66100%
File 22: IsEquivalent(...)100%44100%
File 23: TryCollide(...)100%11100%
File 23: TryCollide(...)100%11100%
File 23: TryCollide(...)100%11100%
File 23: TryCollide(...)100%1919100%
File 23: TryCollide(...)100%1313100%
File 23: TryCircleCircle(...)100%66100%
File 23: TryCircleCircle(...)100%22100%
File 23: TryCircleConvex(...)100%22100%
File 23: TryCircleConvex(...)100%22100%
File 23: TryCircleConvexReversed(...)100%22100%
File 23: TryCapsuleCircle(...)100%22100%
File 23: TryCapsuleCircle(...)100%22100%
File 23: TryCapsuleCircleReversed(...)100%22100%
File 23: TryCapsuleCapsule(...)100%66100%
File 23: TryCapsuleCapsule(...)100%22100%
File 23: TryCapsuleConvex(...)100%22100%
File 23: TryGetCapsuleConvexContacts(...)100%11100%
File 23: GetConvexVertexOffsets(...)100%44100%
File 23: TryCapsuleConvex(...)100%44100%
File 23: TryCapsuleConvexReversed(...)100%44100%
File 23: TryConvexConvex(...)100%22100%
File 23: TryConvexConvex(...)100%44100%
File 23: TryCompound(...)100%66100%
File 23: TryCompoundCompound(...)100%1010100%
File 23: TryCompoundOther(...)100%1010100%
File 23: TryCompound(...)100%66100%
File 23: TryCompoundCompound(...)100%66100%
File 23: TryCompoundOther(...)100%1212100%
File 23: AddCompoundPartContacts(...)100%22100%
File 23: AddContact(...)100%11100%
File 23: BoundsOverlap(...)100%66100%
File 23: OrientCoincidentCapsuleNormal(...)100%44100%
File 24: .ctor(...)100%11100%
File 24: Create(...)100%11100%
File 25: .ctor(...)100%11100%
File 26: DoCapsuleSphereCheck(...)100%22100%
File 26: DoCapsulesCheck(...)100%22100%
File 26: ResolveCenteredCapsuleFallback(...)100%1212100%
File 27: DoCompoundCheck(...)100%22100%
File 27: DoCompoundOtherCheck(...)100%22100%
File 27: DoCompoundCompoundCheck(...)100%44100%
File 27: TryBuildCompoundPartContact(...)100%44100%
File 27: AddCompoundPartContactsInOwnerOrder(...)100%44100%
File 27: OrderPartPairForDetection(...)100%22100%
File 27: BoundsOverlapInclusive(...)100%1010100%
File 28: DoConeSphereCheck(...)100%66100%
File 28: DoConeConvexCheck(...)100%22100%
File 28: DoMeshConeCheck(...)100%66100%
File 28: TryFindMeshConeTriangleContact(...)100%1212100%
File 28: GetConeConvexColliders(...)100%44100%
File 28: OrientNormal(...)100%22100%
File 29: DoCollisionCheck(...)100%11100%
File 29: DoCollisionCheck(...)100%2121100%
File 30: DoCuboidSphereCheck(...)100%22100%
File 30: DoCuboidCapsuleCheck(...)100%22100%
File 30: DoCuboidsCheck(...)100%1010100%
File 30: CanBuildAxisAlignedManifold(...)100%44100%
File 30: TryBuildAxisAlignedCuboidManifold(...)100%2020100%
File 30: AddAxisAlignedCuboidContacts(...)100%1010100%
File 30: AddCuboidContact(...)100%11100%
File 31: DoCylinderSphereCheck(...)100%66100%
File 31: DoCylinderCapsuleCheck(...)100%44100%
File 31: DoCylindersCheck(...)100%44100%
File 31: DoCuboidCylinderCheck(...)100%66100%
File 31: TryAddCylinderCylinderCapContacts(...)100%44100%
File 31: TryAddCylinderCylinderCapContact(...)100%11100%
File 31: ResolvePenetrationDepth(...)100%22100%
File 31: ResolveNormal(...)100%22100%
File 31: SetContactInPairOrder(...)100%22100%
File 32: DoMeshSphereCheck(...)100%11100%
File 32: DoMeshCapsuleCheck(...)100%44100%
File 32: DoMeshCuboidCheck(...)100%66100%
File 32: DoMeshCylinderCheck(...)100%11100%
File 32: DoMeshesCheck(...)100%88100%
File 33: DoSpheresCheck(...)100%22100%
File 34: .ctor(...)100%11100%
File 34: Create(...)100%11100%
File 35: GetSupportAnchor(...)100%1414100%
File 35: IsSupported(...)100%1414100%
File 35: Intersects(...)100%2020100%
File 35: IntersectsConeVolume(...)100%1818100%
File 35: TrySupportMinkowski(...)100%44100%
File 35: TrySupportMinkowskiConeCollider(...)100%44100%
File 35: ResolveSupportDirection(...)100%22100%
File 35: GetLocalDisplacement(...)100%22100%
File 36: IsAxisAligned(...)100%11100%
File 36: GetCapBasis(...)100%11100%
File 37: AddPoint(...)100%44100%
File 37: Update(...)100%44100%
File 37: UpdateLine(...)100%44100%
File 37: UpdateTriangle(...)100%1010100%
File 37: UpdateTetrahedron(...)100%66100%
File 37: SameDirection(...)100%11100%
File 37: TripleCross(...)100%11100%
File 37: OrientFaceNormal(...)100%22100%
File 37: Perpendicular(...)100%44100%
File 38: TryBuildMeshSphereManifold(...)100%22100%
File 38: TryBuildMeshCapsuleManifold(...)100%22100%
File 38: TryBuildMeshCuboidManifold(...)100%22100%
File 38: TryBuildMeshCylinderManifold(...)100%22100%
File 38: TryBuildMeshMeshManifold(...)100%1212100%
File 38: TryAddSphereTriangleContact(...)100%22100%
File 38: TryAddCapsuleTriangleContact(...)100%22100%
File 38: TryAddCuboidTriangleContact(...)100%66100%
File 38: AddReversedBoxTriangleContact(...)100%11100%
File 38: AddReversedBoxTriangleContact(...)100%11100%
File 38: TryAddCylinderTriangleContact(...)100%66100%
File 38: TryAddCylinderCapTriangleContacts(...)100%44100%
File 38: TryAddCylinderCapTriangleContact(...)100%22100%
File 38: GetTriangleBoundsInFrame(...)100%11100%
File 38: CreateBounds(...)100%11100%
File 38: OrientNormal(...)100%22100%
File 38: AddContact(...)100%11100%
File 39: TryCompoundEmbedded2D(...)100%1010100%
File 39: TryMeshEmbedded2D(...)100%1616100%
File 39: TryGetTriangleFiniteSurfaceContact(...)100%44100%
File 40: TryCollide(...)100%1313100%
File 40: TryEmbeddedCompound2D(...)100%1010100%
File 40: TrySphereEmbedded2D(...)100%2020100%
File 40: TryCapsuleEmbedded2D(...)100%1010100%
File 40: TryCylinderEmbedded2D(...)100%1010100%
File 40: TryConeEmbedded2D(...)100%1010100%
File 40: TryTestCapsuleCircleSlab(...)100%66100%
File 40: TryTestCylinderCircleSlab(...)100%66100%
File 40: TryTestConeCircleSlab(...)100%66100%
File 40: TryTestCapsuleCapsuleSlab(...)100%1212100%
File 40: TryTestCylinderCapsuleSlab(...)100%1212100%
File 40: TryTestConeCapsuleSlab(...)100%1010100%
File 40: CheckCapsuleCircleSlabAxis(...)100%88100%
File 40: CheckCylinderCircleSlabAxis(...)100%44100%
File 40: CheckConeCircleSlabAxis(...)100%44100%
File 40: CheckCapsulePrismAxis(...)100%44100%
File 40: CheckCylinderPrismAxis(...)100%44100%
File 40: CheckConePrismAxis(...)100%44100%
File 40: KeepAxisPenetration(...)100%44100%
File 40: CheckEmbeddedCapsuleAxes(...)100%22100%
File 40: CheckEmbeddedCapsuleAxes(...)100%22100%
File 40: CheckEmbeddedCapsuleAxes(...)100%22100%
File 40: CheckCapsuleEmbeddedCapsuleEdgeAxis(...)100%11100%
File 40: CheckCylinderEmbeddedCapsuleEdgeAxis(...)100%11100%
File 40: CheckConeEmbeddedCapsuleEdgeAxis(...)100%11100%
File 40: BuildCapsuleContact(...)100%11100%
File 40: BuildCylinderContact(...)100%11100%
File 40: BuildConeContact(...)100%11100%
File 40: BuildCanonicalSupportContact(...)100%11100%
File 41: TryCuboidEmbedded2D(...)100%66100%
File 41: TryGetCuboidPrismContact(...)100%11100%
File 41: TryGetCuboidRelationContact(...)100%22100%
File 41: TryGetTriangleConvexPrismContact(...)100%11100%
File 41: TryGetSphereConvexPrismContact(...)100%11100%
File 41: TryGetCapsuleConvexPrismContact(...)100%11100%
File 41: TryGetCylinderConvexPrismContact(...)100%11100%
File 41: TryGetConeConvexPrismContact(...)100%11100%
File 41: GetConvexPrismOffsets(...)100%22100%
File 41: TryBuildFinitePrismContact(...)100%22100%
File 42: TryGetRotationalSeparationGap(...)100%88100%
File 42: TryGetSpherePolygonSlabSeparationGap(...)100%66100%
File 42: TryGetPolygonExteriorGap(...)100%66100%
File 42: TryGetPointIntervalGap(...)100%11100%
File 42: TryGetIntervalGap(...)100%44100%
File 43: TryNormalizeAxis(...)100%22100%
File 43: GetEmbeddedCenter3D(...)100%11100%
File 43: GetEmbeddedCapsuleAxes(...)100%22100%
File 43: GetRigidUpAxis(...)100%11100%
File 43: ResolveFallbackNormal(...)100%22100%
File 43: NoContact(...)100%11100%
File 44: GetCenter3D(...)100%11100%
File 44: GetSupportAnchor(...)100%66100%
File 44: GetClosestAnchorOnEmbeddedVolume(...)100%1414100%
File 44: ContainsPointInSlab(...)100%11100%
File 44: TryGetPlanarBoundaryPoint(...)100%44100%
File 44: TryGetPlanarBoundaryAnchor(...)100%22100%
File 44: TryGetPlanarBoundaryAnchor(...)100%22100%
File 44: TryGetPlanarSupportAnchor(...)100%88100%
File 44: TryGetRoundSupportAnchor(...)100%22100%
File 44: EmbedPlanarAnchor(...)100%11100%
File 44: GetClosestSlabOffset(...)100%88100%
File 45: .ctor(...)100%11100%
File 45: get_World()100%11100%
File 45: get_IsNotificationInProgress()100%11100%
File 45: get_LifetimeVersion()100%11100%
File 45: Initialize(...)100%66100%
File 45: AssignPriority(...)100%22100%
File 45: ShouldFirstColliderLead(...)100%88100%
File 45: UpdateCollision()100%11100%
File 45: UpdateCollisionDeferred()100%11100%
File 45: UpdateCollision(...)100%1010100%
File 45: UpdateLastFrame()100%11100%
File 45: DeactivateAndPoolIfRequired()100%22100%
File 45: ProcessCollision(...)100%1414100%
File 45: WakeSleepingBodiesForCollision()100%88100%
File 45: NotifyCollidersOfContact()100%1818100%
File 45: EndNotification(...)100%22100%
File 45: NotifySeparation(...)100%66100%
File 45: ClearPendingNotificationState()100%11100%
File 45: HandleCullingIfNotColliding()100%22100%
File 45: TryPreserveSleepingRestingContact()100%1212100%
File 45: CheckCollision()100%44100%
File 45: IsCullStateInvalidated()100%44100%
File 45: BroadPhaseVersionChanged()100%22100%
File 45: RefreshBroadPhaseVersions()100%11100%
File 45: ShouldPerformCollisionCheck()100%11100%
File 45: BoundsOverlapInclusive(...)100%1010100%
File 45: CalculateCullScore()100%88100%
File 45: GetCullDistanceStep(...)100%11100%
File 45: Reset()100%11100%
File 45: StoreWarmStartImpulse(...)100%11100%
File 45: TryGetWarmStartImpulse(...)100%11100%
File 45: RemoveWarmStartImpulse(...)100%11100%
File 45: ClearWarmStart()100%11100%
File 45: Deactivate()100%1212100%
File 46: ContributeReplayHash(...)100%22100%
File 46: ContributeManifoldReplayHash(...)100%44100%
File 46: WriteMaterial(...)100%11100%
File 46: ContributeWarmStartReplayHash(...)100%22100%
File 47: Capture(...)100%22100%
File 47: ThrowIfAny(...)100%44100%
File 48: .ctor(...)100%11100%
File 48: get_Context()100%11100%
File 48: get_LifetimeVersion()100%11100%
File 48: get_IsNotificationInProgress()100%11100%
File 48: Initialize(...)100%22100%
File 48: MarkColliding(...)100%1414100%
File 48: MarkResting(...)100%11100%
File 48: MarkResting(...)100%11100%
File 48: MarkSeparated()100%66100%
File 48: EndNotification(...)100%22100%
File 48: NotifySeparation(...)100%44100%
File 48: WakeSleepingBodiesForCollision()100%88100%
File 49: ContributeReplayHash(...)100%44100%
File 49: WriteMaterial(...)100%11100%
File 50: .cctor()100%11100%
File 50: Resolve(...)100%11100%
File 50: Resolve(...)100%5252100%
File 50: TryCreateBodyPair(...)100%88100%
File 50: BuildContactBuffer(...)100%44100%
File 50: TryCreateContact(...)100%1212100%
File 50: TryCalculateExactNormalResult(...)100%11100%
File 50: ApplyPositionCorrection(...)100%44100%
File 50: TryApplyCachedImpulse(...)100%44100%
File 50: TryApplyNormalImpulse(...)100%88100%
File 50: ApplyVelocityDelta(...)100%66100%
File 50: TrySolveFrictionImpulse(...)100%3636100%
File 50: TrySolveFrictionImpulseExact(...)100%66100%
File 50: CanUseCompactAxisResponse(...)100%11100%
File 50: ApplyPositionCorrection(...)100%44100%
File 50: TryApplyContactImpulseCombination(...)100%1010100%
File 50: TryApplyCompactImpulse(...)100%22100%
File 50: TryGetLinearVelocityDelta(...)100%22100%
File 50: TryGetAngularVelocityDelta(...)100%44100%
File 50: TryApplyVelocityDeltas(...)100%66100%
File 50: ResolveLinearVelocity(...)100%44100%
File 50: ResolveAngularVelocity(...)100%44100%
File 50: ResolveContactNormal(...)100%1010100%
File 50: GetExactLevers(...)100%44100%
File 50: HasFailedResponse(...)100%11100%
File 50: RejectResponse(...)100%22100%
File 51: .ctor(...)100%11100%
File 51: .ctor(...)100%11100%
File 51: .ctor(...)100%11100%
File 51: .ctor(...)100%11100%
File 51: TryCalculateVelocityDeltas(...)100%2020100%
File 51: TryCalculateAccumulatedDelta(...)100%2424100%
File 51: TryCalculateVelocityDeltasExact(...)100%22100%
File 51: TryCalculateAccumulatedDeltaExact(...)100%44100%
File 51: TryComputeNormalVelocity(...)100%22100%
File 51: TryComputeDenominator(...)100%22100%
File 51: TryComputeAngularDenominator(...)100%1414100%
File 51: TryGetConstrainedInverseMass(...)100%66100%
File 51: TryComputeLinearVelocityDelta(...)100%44100%
File 51: TryComputeAngularVelocityDelta(...)100%66100%
File 51: TryResolveAngularVelocityDelta(...)100%66100%
File 51: Zero(...)100%11100%
File 51: ZeroVelocityDelta(...)100%11100%
File 52: CanUseCompactResponse(...)100%44100%
File 52: CreateResponseOperand(...)100%44100%
File 52: TryGetNormalResponse(...)100%11100%
File 52: TryGetAccumulatedNormalResponse(...)100%11100%
File 52: ToSpatial(...)100%11100%
File 52: ToPlanar(...)100%11100%
File 52: ToPlanarAngular(...)100%11100%
File 52: TryGetImpulseVelocityDeltas(...)100%11100%
File 52: TryGetParticipantVelocityDeltas(...)100%88100%
File 52: CreateInverseInertia(...)100%44100%
File 53: .ctor(...)100%11100%
File 53: get_CanTranslate()100%22100%
File 53: get_CanRotate()100%22100%
File 53: get_HasSolverMobility()100%22100%
File 53: GetConstrainedInverseMass(...)100%22100%
File 53: Create(...)100%22100%
File 54: .ctor(...)100%11100%
File 54: get_Zero()100%11100%
File 54: Create(...)100%22100%
File 54: .ctor(...)100%11100%
File 55: Add(...)100%22100%
File 55: GetContact(...)100%22100%
File 55: GetNormalImpulse(...)100%22100%
File 55: GetNormalResult(...)100%22100%
File 55: GetTangentImpulse(...)100%22100%
File 55: SetNormalImpulse(...)100%22100%
File 55: SetTangentImpulse(...)100%22100%
File 56: .cctor()100%11100%
File 56: CalculateImpulse(...)100%11100%
File 56: CalculateImpulse(...)100%4242100%
File 56: TryCreateBodyPair(...)100%1212100%
File 56: BuildContactBuffer(...)100%44100%
File 56: TryCreateContact(...)100%1010100%
File 56: TryCalculateExactNormalResult(...)100%11100%
File 56: ApplyPositionCorrection(...)100%44100%
File 56: TryApplyCachedImpulse(...)100%66100%
File 56: TryApplyContactImpulseCombination(...)100%44100%
File 56: TryApplyContactImpulse(...)100%66100%
File 56: TryApplyContactImpulseExact(...)100%11100%
File 56: TryApplyContactImpulseCombinationExact(...)100%22100%
File 56: TryApplyNormalImpulse(...)100%1616100%
File 56: TrySolveFrictionImpulse(...)100%1212100%
File 56: TryGetCompactFrictionResponse(...)100%2626100%
File 56: TrySolveFrictionImpulseExact(...)100%44100%
File 56: TryGetCompactTangentImpulseDelta(...)100%1212100%
File 56: TryGetCompactConstrainedInverseMass(...)100%44100%
File 56: TryGetFrictionLimit(...)100%66100%
File 56: TryGetMagnitudeSquared(...)100%22100%
File 56: TryGetSquare(...)100%44100%
File 56: ResolveLinearVelocity(...)100%22100%
File 56: ResolveAngularVelocity(...)100%22100%
File 56: TryApplyExactImpulse(...)100%22100%
File 56: TryApplyCompactImpulse(...)100%22100%
File 56: CanNegate(...)100%44100%
File 56: TryGetLinearVelocityDelta(...)100%44100%
File 56: TryGetAngularVelocityDelta(...)100%1010100%
File 56: TryPrepareVelocityStates(...)100%11100%
File 56: TryApplyVelocityDeltas(...)100%22100%
File 56: ApplyVelocityStates(...)100%11100%
File 56: GetExactLevers(...)100%11100%
File 56: HasFailedResponse(...)100%11100%
File 56: RejectResponse(...)100%22100%
File 56: ClearWarmStartImpulse(...)100%11100%
File 56: IsWarmStartCompatible(...)100%11100%
File 56: ResolveContactNormal(...)100%1010100%
File 57: .ctor(...)100%11100%
File 57: .ctor(...)100%11100%
File 57: .ctor(...)100%11100%
File 57: .ctor(...)100%11100%
File 57: .ctor(...)100%11100%
File 57: get_SaturatedSum()100%11100%
File 57: TryGetValue(...)100%22100%
File 57: TryCalculateVelocityDeltas(...)100%2020100%
File 57: TryCalculateVelocityDeltasExact(...)100%22100%
File 57: CalculateAccumulatedDelta(...)100%11100%
File 57: TryCalculateAccumulatedDelta(...)100%1212100%
File 57: TryCalculateAccumulatedDeltaExact(...)100%44100%
File 57: TryCalculateAccumulatedImpulseDelta(...)100%1616100%
File 57: GetConstrainedInverseMass(...)100%22100%
File 57: TryComputeNormalVelocity(...)100%1010100%
File 57: TryComputeDenominator(...)100%22100%
File 57: TryComputeAngularDenominator(...)100%1616100%
File 57: TryComputeLinearVelocityDelta(...)100%66100%
File 57: TryComputeAngularVelocityDelta(...)100%88100%
File 57: TryResolveAngularVelocityDelta(...)100%44100%
File 57: TryResolveVelocityDelta(...)100%22100%
File 57: Zero(...)100%11100%
File 57: ZeroVelocityDelta(...)100%11100%
File 58: CanUseFastPointVelocity(...)100%11100%
File 58: CanUseFastAngularResponse(...)100%11100%
File 58: TryGetRelativePointVelocity(...)100%88100%
File 58: TryCross(...)100%44100%
File 58: TryDot(...)100%44100%
File 58: TryTransformDirection(...)100%44100%
File 58: TryLinearCombination(...)100%44100%
File 58: TryScale(...)100%44100%
File 58: PreservesNonzeroCrossProduct(...)100%88100%
File 58: PreservesNonzeroDotProduct(...)100%44100%
File 58: PreservesNonzeroTransformDirection(...)100%88100%
File 58: PreservesNonzeroLinearCombination(...)100%88100%
File 58: PreservesExactValue(...)100%22100%
File 58: HasSafeProductInputs(...)100%11100%
File 58: HasSafeProductInputs(...)100%11100%
File 58: HasSafeProductInputs(...)100%11100%
File 58: HasSafeProductInputs(...)100%11100%
File 58: HasNonzeroTerm(...)100%1010100%
File 58: HasNonzeroDifferenceTerm(...)100%66100%
File 58: GetLinearCombinationComponent(...)100%11100%
File 58: IsSafeMagnitude(...)100%11100%
File 58: GetAggregateMagnitude(...)100%11100%
File 58: GetAggregateMagnitude(...)100%11100%
File 58: GetRawMagnitude(...)100%11100%
File 59: TryGetNormalResponse(...)100%11100%
File 59: TryGetAccumulatedNormalResponse(...)100%11100%
File 59: CreateResponseOperand(...)100%66100%
File 59: TryGetAngularVelocityDelta(...)100%44100%
File 59: TryGetImpulseCombinationVelocityDeltas(...)100%11100%
File 59: TryGetLinearCombinationVelocityDelta(...)100%44100%
File 59: TryGetAngularCombinationVelocityDelta(...)100%44100%
File 60: .ctor(...)100%11100%
File 60: get_HasSolverMobility()100%11100%
File 60: get_CanRotate()100%11100%
File 60: GetConstrainedInverseMass(...)100%11100%
File 60: Create(...)100%11100%
File 60: Create(...)100%11100%
File 61: .ctor(...)100%11100%
File 61: Create(...)100%22100%
File 61: .ctor(...)100%11100%
File 61: CreateTangent(...)100%66100%
File 62: Add(...)100%44100%
File 62: GetContact(...)100%44100%
File 62: GetNormalImpulse(...)100%44100%
File 62: GetNormalResult(...)100%44100%
File 62: GetTangentImpulse(...)100%44100%
File 62: GetSecondaryTangentImpulse(...)100%44100%
File 62: SetNormalImpulse(...)100%44100%
File 62: SetTangentImpulse(...)100%44100%
File 63: TryGetCoulombLineResponse(...)100%1212100%
File 63: TryGetCoulombDiskResponse(...)100%44100%
File 63: TryGetCoulombDiskResponse(...)100%44100%
File 63: TryGetCoulombDiskResponseCore(...)100%2626100%
File 63: AreCoulombDiskInputsValid(...)100%11100%
File 63: TryGetCompletedNormalAccumulatorRatio(...)100%1616100%
File 63: GetBilateralImpulseRatio(...)100%1010100%
File 63: GetFrictionLimit(...)100%11100%
File 63: AddFixedToRatio(...)100%44100%
File 63: SubtractFixedFromRatio(...)100%44100%
File 63: CompareRatios(...)100%11100%
File 63: IsVectorWithinLimit(...)100%11100%
File 63: AddSignedMagnitudes(...)100%44100%
File 63: TryGetSignedRatioOverSquareRoot(...)100%1414100%
File 63: CompareRatioOverSquareRoot(...)100%11100%
File 63: CompareRatioOverSquareRoot(...)100%11100%
File 63: HaveMatchingParticipants(...)100%11100%
File 64: IsRadialProjectionEqualToFixed(...)100%44100%
File 64: TryGetSignedRadicalAndRationalSum(...)100%2222100%
File 64: CompareSignedRadicalAndRationalSumToMagnitude(...)100%11100%
File 64: CompareSignedRadicalAndRationalSumToMagnitude(...)100%11100%
File 64: GetWeightedFixedSum(...)100%11100%
File 64: GetWeightedSignedWideSum(...)100%11100%
File 64: GetProductSign(...)100%11100%
File 64: TrimMagnitude(...)100%44100%
File 64: TryGetDiskVelocityDeltas(...)100%11100%
File 64: TryGetDiskLinearVelocityDelta(...)100%22100%
File 64: TryGetDiskLinearComponent(...)100%66100%
File 64: TryGetDiskAngularVelocityDelta(...)100%22100%
File 64: TryGetDiskAngularComponent(...)100%44100%
File 64: GetWeightedSignedSum(...)100%88100%
File 65: TryGetNormalResponse(...)100%11100%
File 65: TryGetAccumulatedNormalResponse(...)100%11100%
File 65: TryGetNormalResponseCore(...)100%4242100%
File 65: CreateZeroResponse(...)100%11100%
File 65: GetEffectiveMassRatio(...)100%88100%
File 65: BuildImpulseRatio(...)100%11100%
File 65: ResolveUnilateralImpulse(...)100%1212100%
File 65: TryGetLinearVelocityDelta(...)100%88100%
File 65: TryGetLinearComponent(...)100%22100%
File 65: TryGetAngularVelocityDelta(...)100%44100%
File 65: TryGetAngularComponent(...)100%22100%
File 65: GetRawDot(...)100%11100%
File 65: SetNonNegativeRatio(...)100%22100%
File 65: SetUnsignedSum(...)100%11100%
File 65: SetMagnitude(...)100%11100%
File 65: SetMagnitude(...)100%11100%
File 65: SetMagnitude(...)100%11100%
File 65: SetMagnitude(...)100%11100%
File 65: SetMagnitude(...)100%11100%
File 65: Multiply3(...)100%11100%
File 65: Multiply4(...)100%11100%
File 66: .ctor(...)100%11100%
File 67: .ctor(...)100%22100%
File 67: TryGetPrimaryAccumulatedImpulse(...)100%11100%
File 67: TryGetSecondaryAccumulatedImpulse(...)100%11100%
File 68: GetRelativePointVelocityRatio(...)100%11100%
File 68: GetCrossProductQuadraticFormRatio(...)100%11100%
File 68: TryGetTransformedScaledCrossProduct(...)100%11100%
File 68: TryGetTransformedWeightedCrossProduct(...)100%22100%
File 68: GetTransformedCrossProduct(...)100%11100%
File 68: TryGetScaledVector(...)100%22100%
File 68: GetRawCrossProduct(...)100%11100%
File 68: GetCrossProductProjectionNumerator(...)100%11100%
File 68: GetCrossProductNumerators(...)100%11100%
File 68: TransformCrossProduct(...)100%11100%
File 68: GetTransformedCrossProductDenominator(...)100%11100%
File 68: TryGetScaledComponent(...)100%11100%
File 68: TryGetWeightedComponent(...)100%11100%
File 68: GetQuadraticTerm(...)100%22100%
File 69: .ctor(...)100%11100%
File 69: Create(...)100%11100%
File 69: CreateXZ(...)100%11100%
File 70: .ctor(...)100%11100%
File 71: .ctor(...)100%44100%
File 71: TryGetNormalVelocity(...)100%11100%
File 71: TryGetAppliedImpulse(...)100%11100%
File 71: TryGetAccumulatedImpulse(...)100%11100%
File 72: .cctor()100%11100%
File 72: Resolve(...)100%11100%
File 72: Resolve(...)100%4040100%
File 72: HasPlanarResponseCoupling(...)100%11100%
File 72: ApplyPositionCorrection(...)100%66100%
File 72: TryGetNormalImpulse(...)100%1010100%
File 72: CanApplyNormalVelocityDeltas(...)100%11100%
File 72: HasVelocityDelta(...)100%66100%
File 72: ApplyNormalVelocityDeltas(...)100%11100%
File 72: CanApplyVelocityDeltas(...)100%44100%
File 72: ApplyVelocityDeltas(...)100%44100%
File 72: TryApplyFrictionImpulse(...)100%1818100%
File 72: TryGetCompactFrictionImpulse(...)100%3434100%
File 72: TryApplyFrictionImpulseExact(...)100%2020100%
File 72: TryApplyImpulse(...)100%2222100%
File 72: TryGetCompactTangent(...)100%88100%
File 72: TryGetCompactConstrainedInverseMass(...)100%66100%
File 72: TryGetCompactConstrainedPlanarInverseMass(...)100%88100%
File 72: ResolveLinearVelocity(...)100%22100%
File 72: ResolveLinearVelocity(...)100%22100%
File 72: ResolveAngularVelocity(...)100%22100%
File 72: ResolveAngularVelocity(...)100%22100%
File 72: ResolveNormal(...)100%88100%
File 72: ResolveFallbackDirection(...)100%88100%
File 72: GetExactLevers(...)100%44100%
File 72: GetConstrainedInverseMass(...)100%22100%
File 72: GetConstrainedPlanarInverseMass(...)100%44100%
File 73: .ctor(...)100%11100%
File 73: .ctor(...)100%11100%
File 73: .ctor(...)100%11100%
File 73: .ctor(...)100%11100%
File 73: CanUseCompactResponse(...)100%66100%
File 73: TryCalculateVelocityDeltas(...)100%2020100%
File 73: TryCalculateAccumulatedDelta(...)100%2424100%
File 73: TryCalculateVelocityDeltasExact(...)100%22100%
File 73: TryCalculateAccumulatedDeltaExact(...)100%44100%
File 73: TryComputeNormalVelocity(...)100%11100%
File 73: TryComputeDenominator(...)100%22100%
File 73: GetConstrainedInverseMass3D(...)100%22100%
File 73: GetConstrainedInverseMass2D(...)100%44100%
File 73: TryResolveAngularVelocityDelta3D(...)100%44100%
File 73: TryResolveAngularVelocityDelta2D(...)100%66100%
File 73: ZeroImpulse(...)100%11100%
File 73: ZeroVelocityDelta(...)100%11100%

File(s)

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/ColliderCollisionFilter.cs

#LineLine coverage
 1//=======================================================================
 2// ColliderCollisionFilter.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 Gravitas.Colliders;
 9using System.Runtime.CompilerServices;
 10
 11namespace Gravitas.CollisionHandling;
 12
 13internal static class ColliderCollisionFilter
 14{
 15    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 16    internal static bool AllowsPhysicalPair(LSCollider first, LSCollider second) =>
 15488317        !first.IgnoresCollisionLayer(second.Layer)
 15488318        && !second.IgnoresCollisionLayer(first.Layer);
 19
 20    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 21    internal static bool AllowsPhysicalPair(LSCollider2D first, LSCollider2D second) =>
 997122        !first.IgnoresCollisionLayer(second.Layer)
 997123        && !second.IgnoresCollisionLayer(first.Layer);
 24
 25    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 26    internal static bool AllowsPhysicalPair(LSCollider collider3D, LSCollider2D collider2D) =>
 514227        !collider3D.IgnoresCollisionLayer(collider2D.Layer)
 514228        && !collider2D.IgnoresCollisionLayer(collider3D.Layer);
 29}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Contacts/2D/Contact2D.cs

#LineLine coverage
 1//=======================================================================
 2// Contact2D.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;
 10
 11namespace Gravitas.CollisionHandling;
 12
 13/// <summary>
 14/// Deterministic pure 2D contact data generated by the 2D narrow phase.
 15/// </summary>
 16internal readonly struct Contact2D
 17{
 18    public Contact2D(
 19        Vector2d pointA,
 20        Vector2d pointB,
 21        Vector2d normal,
 22        Fixed64 depth,
 23        bool depthIsClamped = false)
 524        : this(
 525            ContactAnchor2D.FromWorldPoint(pointA),
 526            ContactAnchor2D.FromWorldPoint(pointB),
 527            normal,
 528            depth,
 529            depthIsClamped)
 530    { }
 31
 32    public Contact2D(
 33        ContactAnchor2D anchorA,
 34        ContactAnchor2D anchorB,
 35        Vector2d normal,
 36        Fixed64 depth,
 37        bool depthIsClamped = false)
 38    {
 153839        AnchorA = anchorA;
 153840        AnchorB = anchorB;
 153841        Normal = normal;
 153842        Depth = depth;
 153843        DepthIsClamped = depthIsClamped;
 153844        HasContact = true;
 153845    }
 46
 47    public bool HasContact { get; }
 48
 49    public ContactAnchor2D AnchorA { get; }
 50
 51    public ContactAnchor2D AnchorB { get; }
 52
 1253    public Vector2d PointA => GetRequiredWorldPoint(AnchorA, nameof(PointA));
 54
 1155    public Vector2d PointB => GetRequiredWorldPoint(AnchorB, nameof(PointB));
 56
 57    /// <summary>
 58    /// Contact normal pointing from collider A toward collider B.
 59    /// </summary>
 60    public Vector2d Normal { get; }
 61
 62    public Fixed64 Depth { get; }
 63
 64    public bool DepthIsClamped { get; }
 65
 66    private static Vector2d GetRequiredWorldPoint(ContactAnchor2D anchor, string propertyName)
 67    {
 2368        if (anchor.TryGetWorldPoint(out Vector2d point))
 2269            return point;
 70
 171        throw new InvalidOperationException(
 172            $"{propertyName} is outside the representable coordinate range.");
 73    }
 74
 75}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Contacts/2D/ContactAnchor2D.cs

#LineLine coverage
 1//=======================================================================
 2// ContactAnchor2D.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 System.Runtime.CompilerServices;
 11
 12namespace Gravitas.CollisionHandling;
 13
 14/// <summary>
 15/// Represents a planar contact point as a world-space origin plus a rotated
 16/// local offset.
 17/// </summary>
 18public readonly struct ContactAnchor2D
 19{
 20    private readonly FixedPointAnchor2d _point;
 21
 22    /// <summary>
 23    /// Creates a planar contact anchor in an identity-rotation rigid frame.
 24    /// </summary>
 25    public ContactAnchor2D(Vector2d origin, Vector2d offset)
 2742426        : this(origin, Fixed64.Zero, offset)
 27    {
 2742428    }
 29
 30    /// <summary>
 31    /// Creates a planar contact anchor from a canonical local point.
 32    /// </summary>
 33    public ContactAnchor2D(
 34        Vector2d origin,
 35        Fixed64 rotation,
 36        Vector2d localPoint)
 3265137        : this(origin, rotation, localPoint, Vector2d.Zero)
 38    {
 3265139    }
 40
 41    /// <summary>
 42    /// Creates a planar anchor whose two local terms remain separate until
 43    /// the exact rotated point is evaluated.
 44    /// </summary>
 45    public ContactAnchor2D(
 46        Vector2d origin,
 47        Fixed64 rotation,
 48        Vector2d localPoint,
 49        Vector2d localDisplacement)
 50    {
 3292751        _point = new FixedPointAnchor2d(
 3292752            origin,
 3292753            rotation,
 3292754            localPoint,
 3292755            localDisplacement);
 3292756    }
 57
 58    /// <summary>
 59    /// Creates a physics-domain wrapper for an exact planar point anchor.
 60    /// </summary>
 61    public ContactAnchor2D(FixedPointAnchor2d point)
 62    {
 2705663        _point = point;
 2705664    }
 65
 66    /// <summary>
 67    /// Gets the representable world-space origin.
 68    /// </summary>
 279569    public Vector2d Origin => _point.Origin;
 70
 71    /// <summary>
 72    /// Gets the point in the contact frame's local coordinates.
 73    /// </summary>
 272674    public Vector2d LocalPoint => _point.LocalPoint;
 75
 76    /// <summary>
 77    /// Gets the secondary local displacement retained separately from
 78    /// <see cref="LocalPoint"/>.
 79    /// </summary>
 272480    public Vector2d LocalDisplacement => _point.LocalDisplacement;
 81
 82    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 83    internal int CompareLocalFeature(in ContactAnchor2D other) =>
 77384        _point.CompareLocalFeature(other._point);
 85
 86    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 87    internal ulong GetLocalFeatureHash64() =>
 162088        _point.GetLocalFeatureHash64();
 89
 90    /// <summary>
 91    /// Gets the rotation applied to <see cref="LocalPoint"/>.
 92    /// </summary>
 150393    public Fixed64 Rotation => _point.Rotation;
 94
 95    /// <summary>
 96    /// Gets the rotated composite offset from the rigid-frame origin.
 97    /// </summary>
 98    /// <exception cref="System.InvalidOperationException">
 99    /// The rotated offset is outside the representable scalar domain. Use
 100    /// <see cref="TryGetOffset(out Vector2d)"/> when handling domain edges.
 101    /// </exception>
 102    public Vector2d Offset
 103    {
 104        get
 105        {
 44106            bool succeeded = TryGetOffset(out Vector2d offset);
 44107            SwiftThrowHelper.ThrowIfTrue(
 44108                !succeeded,
 44109                nameof(Offset),
 44110                "The planar contact offset is outside the representable coordinate domain.");
 43111            return offset;
 112        }
 113    }
 114
 115    /// <summary>
 116    /// Creates an anchor for an already materialized world point.
 117    /// </summary>
 118    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 119    public static ContactAnchor2D FromWorldPoint(Vector2d point) =>
 2443120        new(Vector2d.Zero, point);
 121
 122    /// <summary>
 123    /// Attempts to materialize the rotated composite rigid-frame offset.
 124    /// </summary>
 125    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 126    public bool TryGetOffset(out Vector2d offset) =>
 47127        _point.TryGetOffsetFrom(
 47128            new FixedPointAnchor2d(
 47129                Origin,
 47130                Fixed64.Zero,
 47131                Vector2d.Zero),
 47132            out offset);
 133
 134    /// <summary>
 135    /// Attempts to materialize the absolute world point without saturation.
 136    /// </summary>
 137    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 138    public bool TryGetWorldPoint(out Vector2d point) =>
 373139        _point.TryGetPoint(out point);
 140
 141    /// <summary>
 142    /// Attempts to rebase the conceptual point onto another origin with one
 143    /// final exact component-wise add/subtract operation.
 144    /// </summary>
 145    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 146    public bool TryGetOffsetFrom(Vector2d origin, out Vector2d offset) =>
 1923147        _point.TryGetOffsetFrom(
 1923148            new FixedPointAnchor2d(
 1923149                origin,
 1923150                Fixed64.Zero,
 1923151                Vector2d.Zero),
 1923152            out offset);
 153
 154    /// <summary>
 155    /// Attempts to obtain this point's exact world-space offset from another
 156    /// planar contact anchor.
 157    /// </summary>
 158    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 159    public bool TryGetOffsetFrom(
 160        ContactAnchor2D other,
 161        out Vector2d offset) =>
 5130162        _point.TryGetOffsetFrom(other._point, out offset);
 163
 164    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 165    internal ExactLever3D GetXZLeverFrom(in ContactAnchor2D other) =>
 192166        ExactLever3D.CreateXZ(_point, other._point);
 167
 168    /// <summary>
 169    /// Attempts to express the same conceptual point relative to another
 170    /// representable origin.
 171    /// </summary>
 172    public bool TryRebase(Vector2d origin, out ContactAnchor2D anchor)
 173    {
 2174        if (!_point.TryReframe(
 2175                origin,
 2176                Fixed64.Zero,
 2177                out FixedPointAnchor2d point))
 178        {
 1179            anchor = default;
 1180            return false;
 181        }
 182
 1183        anchor = new ContactAnchor2D(point);
 1184        return true;
 185    }
 186
 187    /// <summary>
 188    /// Attempts to express the same conceptual point in another rotated frame.
 189    /// </summary>
 190    public bool TryRebase(
 191        Vector2d origin,
 192        Fixed64 rotation,
 193        out ContactAnchor2D anchor)
 194    {
 2195        if (!_point.TryReframe(
 2196                origin,
 2197                rotation,
 2198                out FixedPointAnchor2d point))
 199        {
 1200            anchor = default;
 1201            return false;
 202        }
 203
 1204        anchor = new ContactAnchor2D(point);
 1205        return true;
 206    }
 207}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Contacts/2D/ContactManifold2D.cs

#LineLine coverage
 1//=======================================================================
 2// ContactManifold2D.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.Materials;
 10using System.Collections;
 11using System.Collections.Generic;
 12using System.Runtime.CompilerServices;
 13
 14namespace Gravitas.CollisionHandling;
 15
 16/// <summary>
 17/// Fixed-capacity deterministic pure 2D contact manifold owned by one collision pair.
 18/// </summary>
 19public sealed class ContactManifold2D : IEnumerable<ManifoldContact2D>
 20{
 21    /// <summary>Maximum number of contacts retained by a pure 2D manifold.</summary>
 22    public const int MaxContactCount = 2;
 23
 24    private ManifoldContact2D _contact0;
 25    private ManifoldContact2D _contact1;
 26    private int _count;
 59827    private int _lastUpdatedFrame = -1;
 28
 29    /// <summary>
 30    /// Number of active contacts in this manifold.
 31    /// </summary>
 32    public int Count
 33    {
 34        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 314235        get => _count;
 36    }
 37
 38    /// <summary>
 39    /// Gets whether this manifold currently contains narrow-phase contact data.
 40    /// </summary>
 41    public bool HasContact
 42    {
 43        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 209244        get => _count > 0;
 45    }
 46
 47    /// <summary>
 48    /// Simulation frame in which the active contacts were last rebuilt.
 49    /// </summary>
 50    public int LastUpdatedFrame
 51    {
 52        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 54453        get => _lastUpdatedFrame;
 54    }
 55
 56    /// <summary>
 57    /// Deepest contact in the manifold. Ties use the lowest contact identity.
 58    /// </summary>
 59    public ManifoldContact2D PrimaryContact
 60    {
 61        get
 62        {
 16463            SwiftThrowHelper.ThrowIfListIndexInvalid(0, _count);
 64
 16465            if (_count == 1)
 14766                return _contact0;
 67
 1768            return IsDeeper(_contact1, _contact0)
 1769                || HasEqualDepth(_contact1, _contact0) && _contact1.ContactId < _contact0.ContactId
 1770                ? _contact1
 1771                : _contact0;
 72        }
 73    }
 74
 75    /// <summary>Gets the contact at the specified deterministic order index.</summary>
 76    public ManifoldContact2D this[int index]
 77    {
 78        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 79        get
 80        {
 185581            SwiftThrowHelper.ThrowIfListIndexInvalid(index, _count);
 185582            return GetContactUnchecked(index);
 83        }
 84    }
 85
 86    /// <summary>
 87    /// Clears contacts and records the frame for a new narrow-phase pass.
 88    /// </summary>
 89    public void BeginUpdate(int frame)
 90    {
 65591        _count = 0;
 65592        _lastUpdatedFrame = frame;
 65593    }
 94
 95    /// <summary>
 96    /// Clears all contact data.
 97    /// </summary>
 98    public void Reset()
 99    {
 432100        _count = 0;
 432101        _lastUpdatedFrame = -1;
 432102        _contact0 = default;
 432103        _contact1 = default;
 432104    }
 105
 106    /// <summary>
 107    /// Replaces the manifold with one contact.
 108    /// </summary>
 109    public void SetContact(Vector2d pointA, Vector2d pointB, Fixed64 depth, Vector2d normal)
 110    {
 65111        _count = 0;
 65112        AddContact(pointA, pointB, depth, normal);
 65113    }
 114
 115    /// <summary>
 116    /// Replaces the manifold with one canonical rigid-frame planar contact.
 117    /// </summary>
 118    public void SetContact(
 119        ContactAnchor2D anchorA,
 120        ContactAnchor2D anchorB,
 121        Fixed64 depth,
 122        Vector2d normal,
 123        bool depthIsClamped = false)
 124    {
 11125        _count = 0;
 11126        AddContact(anchorA, anchorB, depth, normal, depthIsClamped);
 11127    }
 128
 129    /// <summary>
 130    /// Adds a contact, keeping the deepest two contacts and exposing them by stable contact identity.
 131    /// </summary>
 132    public void AddContact(Vector2d pointA, Vector2d pointB, Fixed64 depth, Vector2d normal)
 133    {
 126134        AddContact(
 126135            ContactAnchor2D.FromWorldPoint(pointA),
 126136            ContactAnchor2D.FromWorldPoint(pointB),
 126137            depth,
 126138            normal);
 126139    }
 140
 141    /// <summary>
 142    /// Adds a canonical rigid-frame contact, keeping the deepest two contacts and
 143    /// exposing them by stable anchor identity.
 144    /// </summary>
 145    public void AddContact(
 146        ContactAnchor2D anchorA,
 147        ContactAnchor2D anchorB,
 148        Fixed64 depth,
 149        Vector2d normal,
 150        bool depthIsClamped = false)
 151    {
 144152        AddContactCore(
 144153            anchorA,
 144154            anchorB,
 144155            depth,
 144156            normal,
 144157            hasMaterialOverride: false,
 144158            default,
 144159            default,
 144160            depthIsClamped,
 144161            featureNamespaceA: 0,
 144162            featureNamespaceB: 0);
 144163    }
 164
 165    internal void AddContact(
 166        Vector2d pointA,
 167        Vector2d pointB,
 168        Fixed64 depth,
 169        Vector2d normal,
 170        PhysicsMaterial materialA,
 171        PhysicsMaterial materialB,
 172        bool depthIsClamped = false)
 173    {
 2174        AddContactCore(
 2175            ContactAnchor2D.FromWorldPoint(pointA),
 2176            ContactAnchor2D.FromWorldPoint(pointB),
 2177            depth,
 2178            normal,
 2179            hasMaterialOverride: true,
 2180            materialA,
 2181            materialB,
 2182            depthIsClamped,
 2183            featureNamespaceA: 0,
 2184            featureNamespaceB: 0);
 2185    }
 186
 187    internal void AddContact(
 188        ContactAnchor2D anchorA,
 189        ContactAnchor2D anchorB,
 190        Fixed64 depth,
 191        Vector2d normal,
 192        PhysicsMaterial materialA,
 193        PhysicsMaterial materialB,
 194        bool depthIsClamped = false,
 195        int featureNamespaceA = 0,
 196        int featureNamespaceB = 0)
 197    {
 652198        AddContactCore(
 652199            anchorA,
 652200            anchorB,
 652201            depth,
 652202            normal,
 652203            hasMaterialOverride: true,
 652204            materialA,
 652205            materialB,
 652206            depthIsClamped,
 652207            featureNamespaceA,
 652208            featureNamespaceB);
 652209    }
 210
 211    private void AddContactCore(
 212        ContactAnchor2D anchorA,
 213        ContactAnchor2D anchorB,
 214        Fixed64 depth,
 215        Vector2d normal,
 216        bool hasMaterialOverride,
 217        PhysicsMaterial materialA,
 218        PhysicsMaterial materialB,
 219        bool depthIsClamped,
 220        int featureNamespaceA,
 221        int featureNamespaceB)
 222    {
 798223        ulong contactId = CreateContactId(
 798224            anchorA,
 798225            featureNamespaceA,
 798226            anchorB,
 798227            featureNamespaceB);
 798228        var contact = new ManifoldContact2D(
 798229            contactId,
 798230            anchorA,
 798231            anchorB,
 798232            depth,
 798233            normal,
 798234            hasMaterialOverride,
 798235            materialA,
 798236            materialB,
 798237            depthIsClamped,
 798238            featureNamespaceA,
 798239            featureNamespaceB);
 240
 1714241        for (int i = 0; i < _count; i++)
 242        {
 64243            ManifoldContact2D existing = GetContactUnchecked(i);
 64244            if (existing.ContactId != contactId)
 245                continue;
 246
 5247            if (IsDeeper(contact, existing))
 3248                SetContactUnchecked(i, contact);
 5249            SortContactsById();
 5250            return;
 251        }
 252
 793253        if (_count < MaxContactCount)
 254        {
 789255            SetContactUnchecked(_count, contact);
 789256            _count++;
 789257            SortContactsById();
 789258            return;
 259        }
 260
 4261        int replaceIndex = FindShallowestReplacementIndex(contact);
 4262        if (replaceIndex < 0)
 1263            return;
 264
 3265        SetContactUnchecked(replaceIndex, contact);
 3266        SortContactsById();
 3267    }
 268
 269    /// <summary>Returns an allocation-free enumerator over the active contacts.</summary>
 41270    public Enumerator GetEnumerator() => new(this);
 271
 40272    IEnumerator<ManifoldContact2D> IEnumerable<ManifoldContact2D>.GetEnumerator() => GetEnumerator();
 273
 1274    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
 275
 276    private int FindShallowestReplacementIndex(ManifoldContact2D candidate)
 277    {
 4278        int replaceIndex = !IsDeeper(_contact1, _contact0)
 4279            ? 1
 4280            : 0;
 281
 4282        ManifoldContact2D shallowest = GetContactUnchecked(replaceIndex);
 4283        if (IsDeeper(candidate, shallowest))
 2284            return replaceIndex;
 285
 2286        if (HasEqualDepth(candidate, shallowest) && candidate.ContactId < shallowest.ContactId)
 1287            return replaceIndex;
 288
 1289        return -1;
 290    }
 291
 292    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 293    private static bool IsDeeper(ManifoldContact2D candidate, ManifoldContact2D existing) =>
 30294        candidate.Depth > existing.Depth
 30295        || candidate.Depth == existing.Depth
 30296        && candidate.DepthIsClamped
 30297        && !existing.DepthIsClamped;
 298
 299    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 300    private static bool HasEqualDepth(ManifoldContact2D left, ManifoldContact2D right) =>
 17301        left.Depth == right.Depth
 17302        && left.DepthIsClamped == right.DepthIsClamped;
 303
 304    private void SortContactsById()
 305    {
 797306        if (_count < 2 || _contact0.ContactId <= _contact1.ContactId)
 761307            return;
 308
 36309        (_contact0, _contact1) = (_contact1, _contact0);
 36310    }
 311
 312    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 313    private ManifoldContact2D GetContactUnchecked(int index) =>
 1923314        index == 0 ? _contact0 : _contact1;
 315
 316    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 317    private void SetContactUnchecked(int index, ManifoldContact2D contact)
 318    {
 795319        if (index == 0)
 320        {
 742321            _contact0 = contact;
 742322            return;
 323        }
 324
 53325        _contact1 = contact;
 53326    }
 327
 328    private static ulong CreateContactId(
 329        ContactAnchor2D anchorA,
 330        int featureNamespaceA,
 331        ContactAnchor2D anchorB,
 332        int featureNamespaceB)
 333    {
 798334        if (CompareLocalFeature(
 798335                featureNamespaceB,
 798336                anchorB,
 798337                featureNamespaceA,
 798338                anchorA) < 0)
 339        {
 581340            (anchorA, anchorB) = (anchorB, anchorA);
 581341            (featureNamespaceA, featureNamespaceB) =
 581342                (featureNamespaceB, featureNamespaceA);
 343        }
 344
 798345        ulong hash = 14695981039346656037UL;
 798346        Mix(ref hash, featureNamespaceA);
 798347        Mix(
 798348            ref hash,
 798349            unchecked((long)anchorA.GetLocalFeatureHash64()));
 798350        Mix(ref hash, featureNamespaceB);
 798351        Mix(
 798352            ref hash,
 798353            unchecked((long)anchorB.GetLocalFeatureHash64()));
 798354        return hash;
 355    }
 356
 357    private static int CompareLocalFeature(
 358        int leftNamespace,
 359        ContactAnchor2D left,
 360        int rightNamespace,
 361        ContactAnchor2D right)
 362    {
 798363        int comparison = leftNamespace.CompareTo(rightNamespace);
 798364        return comparison != 0
 798365            ? comparison
 798366            : left.CompareLocalFeature(right);
 367    }
 368
 369    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 370    private static void Mix(ref ulong hash, long value)
 371    {
 372        unchecked
 373        {
 3192374            hash ^= (ulong)value;
 3192375            hash *= 1099511628211UL;
 376        }
 3192377    }
 378
 379    /// <summary>Enumerates the active contacts in deterministic order.</summary>
 380    public struct Enumerator : IEnumerator<ManifoldContact2D>
 381    {
 382        private readonly ContactManifold2D _manifold;
 383        private int _index;
 384
 385        internal Enumerator(ContactManifold2D manifold)
 386        {
 41387            _manifold = manifold;
 41388            _index = -1;
 41389        }
 390
 391        /// <summary>Gets the current contact.</summary>
 83392        public ManifoldContact2D Current => _manifold[_index];
 393
 3394        object IEnumerator.Current => Current;
 395
 396        /// <summary>Advances to the next active contact.</summary>
 397        public bool MoveNext()
 398        {
 123399            int next = _index + 1;
 123400            if (next >= _manifold._count)
 40401                return false;
 402
 83403            _index = next;
 83404            return true;
 405        }
 406
 407        /// <summary>Resets the enumerator to its initial position.</summary>
 1408        public void Reset() => _index = -1;
 409
 410        /// <summary>Releases enumerator resources.</summary>
 40411        public void Dispose() { }
 412    }
 413}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Contacts/2D/ContactWarmStartCache2D.cs

#LineLine coverage
 1//=======================================================================
 2// ContactWarmStartCache2D.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.CollisionHandling;
 12
 13/// <summary>
 14/// Fixed-size 2D warm-start cache keyed by stable manifold contact identity.
 15/// </summary>
 16internal struct ContactWarmStartCache2D
 17{
 18    private ulong _contactId0;
 19    private ulong _contactId1;
 20    private ContactWarmStartImpulse _impulse0;
 21    private ContactWarmStartImpulse _impulse1;
 22
 23    public int Count { get; private set; }
 24
 25    public void Clear()
 26    {
 43327        Count = 0;
 43328        _contactId0 = 0UL;
 43329        _contactId1 = 0UL;
 43330        _impulse0 = default;
 43331        _impulse1 = default;
 43332    }
 33
 34    public void Set(ulong contactId, Fixed64 normalImpulse, Fixed64 tangentImpulse)
 35    {
 103736        ContactWarmStartImpulse impulse = new(normalImpulse, tangentImpulse);
 216237        for (int i = 0; i < Count; i++)
 38        {
 76539            if (GetContactId(i) != contactId)
 40                continue;
 41
 72142            SetImpulseUnchecked(i, impulse);
 72143            return;
 44        }
 45
 31646        if (Count < ContactManifold2D.MaxContactCount)
 47        {
 30848            SetContactIdUnchecked(Count, contactId);
 30849            SetImpulseUnchecked(Count, impulse);
 30850            Count++;
 30851            return;
 52        }
 53
 854        SetContactIdUnchecked(ContactManifold2D.MaxContactCount - 1, contactId);
 855        SetImpulseUnchecked(ContactManifold2D.MaxContactCount - 1, impulse);
 856    }
 57
 58    public bool TryGet(ulong contactId, out ContactWarmStartImpulse impulse)
 59    {
 216060        for (int i = 0; i < Count; i++)
 61        {
 77562            if (GetContactId(i) != contactId)
 63                continue;
 64
 73665            impulse = GetImpulseUnchecked(i);
 73666            return true;
 67        }
 68
 30569        impulse = default;
 30570        return false;
 71    }
 72
 73    public bool Remove(ulong contactId)
 74    {
 2075        for (int i = 0; i < Count; i++)
 76        {
 677            if (GetContactId(i) != contactId)
 78                continue;
 79
 580            Count--;
 1481            for (int shift = i; shift < Count; shift++)
 82            {
 283                SetContactIdUnchecked(
 284                    shift,
 285                    GetContactId(shift + 1));
 286                SetImpulseUnchecked(
 287                    shift,
 288                    GetImpulseUnchecked(shift + 1));
 89            }
 90
 591            SetContactIdUnchecked(Count, 0UL);
 592            SetImpulseUnchecked(Count, default);
 593            return true;
 94        }
 95
 496        return false;
 97    }
 98
 99    internal ulong GetContactIdForReplayHash(int index)
 100    {
 3101        SwiftThrowHelper.ThrowIfArrayIndexInvalid(index, Count, nameof(index));
 3102        return GetContactId(index);
 103    }
 104
 105    internal ContactWarmStartImpulse GetImpulseForReplayHash(int index)
 106    {
 2107        SwiftThrowHelper.ThrowIfArrayIndexInvalid(index, Count, nameof(index));
 2108        return GetImpulseUnchecked(index);
 109    }
 110
 111    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1551112    private ulong GetContactId(int index) => index == 0 ? _contactId0 : _contactId1;
 113
 114    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 740115    private ContactWarmStartImpulse GetImpulseUnchecked(int index) => index == 0 ? _impulse0 : _impulse1;
 116
 117    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 118    private void SetContactIdUnchecked(int index, ulong contactId)
 119    {
 323120        if (index == 0)
 121        {
 300122            _contactId0 = contactId;
 300123            return;
 124        }
 125
 23126        _contactId1 = contactId;
 23127    }
 128
 129    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 130    private void SetImpulseUnchecked(int index, ContactWarmStartImpulse impulse)
 131    {
 1044132        if (index == 0)
 133        {
 1006134            _impulse0 = impulse;
 1006135            return;
 136        }
 137
 38138        _impulse1 = impulse;
 38139    }
 140}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Contacts/2D/ManifoldContact2D.cs

#LineLine coverage
 1//=======================================================================
 2// ManifoldContact2D.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.Materials;
 10using System;
 11using System.Runtime.CompilerServices;
 12
 13namespace Gravitas.CollisionHandling;
 14
 15/// <summary>
 16/// A deterministic pure 2D narrow-phase contact point for a collision manifold.
 17/// </summary>
 18public readonly struct ManifoldContact2D
 19{
 20    /// <summary>Creates a deterministic planar contact from world-space witness points.</summary>
 21    public ManifoldContact2D(
 22        ulong contactId,
 23        Vector2d pointA,
 24        Vector2d pointB,
 25        Fixed64 depth,
 26        Vector2d normal,
 27        bool hasMaterialOverride = false,
 28        PhysicsMaterial materialA = default,
 29        PhysicsMaterial materialB = default,
 30        bool depthIsClamped = false)
 2431        : this(
 2432            contactId,
 2433            ContactAnchor2D.FromWorldPoint(pointA),
 2434            ContactAnchor2D.FromWorldPoint(pointB),
 2435            depth,
 2436            normal,
 2437            hasMaterialOverride,
 2438            materialA,
 2439            materialB,
 2440            depthIsClamped)
 2441    { }
 42
 43    /// <summary>
 44    /// Creates a deterministic planar contact from authoritative relative
 45    /// anchors.
 46    /// </summary>
 47    public ManifoldContact2D(
 48        ulong contactId,
 49        ContactAnchor2D anchorA,
 50        ContactAnchor2D anchorB,
 51        Fixed64 depth,
 52        Vector2d normal,
 53        bool hasMaterialOverride = false,
 54        PhysicsMaterial materialA = default,
 55        PhysicsMaterial materialB = default,
 56        bool depthIsClamped = false)
 2657        : this(
 2658            contactId,
 2659            anchorA,
 2660            anchorB,
 2661            depth,
 2662            normal,
 2663            hasMaterialOverride,
 2664            materialA,
 2665            materialB,
 2666            depthIsClamped,
 2667            featureNamespaceA: 0,
 2668            featureNamespaceB: 0)
 2669    { }
 70
 71    internal ManifoldContact2D(
 72        ulong contactId,
 73        ContactAnchor2D anchorA,
 74        ContactAnchor2D anchorB,
 75        Fixed64 depth,
 76        Vector2d normal,
 77        bool hasMaterialOverride,
 78        PhysicsMaterial materialA,
 79        PhysicsMaterial materialB,
 80        bool depthIsClamped,
 81        int featureNamespaceA,
 82        int featureNamespaceB)
 83    {
 82484        ContactId = contactId;
 82485        AnchorA = anchorA;
 82486        AnchorB = anchorB;
 82487        FeatureNamespaceA = featureNamespaceA;
 82488        FeatureNamespaceB = featureNamespaceB;
 82489        Depth = depth.Abs();
 82490        DepthIsClamped = depthIsClamped;
 82491        Normal = normal.MagnitudeSquared > Fixed64.Epsilon
 82492            ? normal.Normalized
 82493            : Vector2d.Zero;
 82494        HasMaterialOverride = hasMaterialOverride;
 82495        MaterialA = hasMaterialOverride ? materialA : PhysicsMaterial.Default;
 82496        MaterialB = hasMaterialOverride ? materialB : PhysicsMaterial.Default;
 82497    }
 98
 99    /// <summary>
 100    /// Stable identity derived from the unordered pair of pose-invariant
 101    /// canonical local feature terms.
 102    /// </summary>
 103    public ulong ContactId { get; }
 104
 105    /// <summary>
 106    /// Authoritative canonical contact anchor on collider A.
 107    /// </summary>
 108    public ContactAnchor2D AnchorA { get; }
 109
 110    /// <summary>
 111    /// Authoritative canonical contact anchor on collider B.
 112    /// </summary>
 113    public ContactAnchor2D AnchorB { get; }
 114
 115    internal int FeatureNamespaceA { get; }
 116
 117    internal int FeatureNamespaceB { get; }
 118
 119    /// <summary>
 120    /// Gets the world-space X/Z-plane contact point on collider A.
 121    /// </summary>
 122    /// <exception cref="InvalidOperationException">
 123    /// The conceptual world point is outside the scalar range.
 124    /// </exception>
 54125    public Vector2d PointA => GetRequiredWorldPoint(AnchorA, nameof(PointA));
 126
 127    /// <summary>
 128    /// Gets the world-space X/Z-plane contact point on collider B.
 129    /// </summary>
 130    /// <exception cref="InvalidOperationException">
 131    /// The conceptual world point is outside the scalar range.
 132    /// </exception>
 50133    public Vector2d PointB => GetRequiredWorldPoint(AnchorB, nameof(PointB));
 134
 135    /// <summary>
 136    /// Attempts to materialize the world-space contact point on collider A.
 137    /// </summary>
 138    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 31139    public bool TryGetPointA(out Vector2d point) => AnchorA.TryGetWorldPoint(out point);
 140
 141    /// <summary>
 142    /// Attempts to materialize the world-space contact point on collider B.
 143    /// </summary>
 144    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 26145    public bool TryGetPointB(out Vector2d point) => AnchorB.TryGetWorldPoint(out point);
 146
 147    /// <summary>
 148    /// Penetration depth along <see cref="Normal"/>.
 149    /// </summary>
 150    public Fixed64 Depth { get; }
 151
 152    /// <summary>
 153    /// Gets whether the conceptual penetration depth exceeded the scalar range.
 154    /// </summary>
 155    public bool DepthIsClamped { get; }
 156
 157    /// <summary>
 158    /// Unit normal pointing from collider A toward collider B.
 159    /// </summary>
 160    public Vector2d Normal { get; }
 161
 162    /// <summary>
 163    /// Gets whether this contact carries materials from private compound parts.
 164    /// </summary>
 165    public bool HasMaterialOverride { get; }
 166
 167    /// <summary>
 168    /// Material for collider A when <see cref="HasMaterialOverride"/> is true.
 169    /// </summary>
 170    public PhysicsMaterial MaterialA { get; }
 171
 172    /// <summary>
 173    /// Material for collider B when <see cref="HasMaterialOverride"/> is true.
 174    /// </summary>
 175    public PhysicsMaterial MaterialB { get; }
 176
 177    private static Vector2d GetRequiredWorldPoint(ContactAnchor2D anchor, string propertyName)
 178    {
 104179        if (anchor.TryGetWorldPoint(out Vector2d point))
 103180            return point;
 181
 1182        throw new InvalidOperationException(
 1183            $"{propertyName} is outside the representable coordinate range. Use the corresponding TryGet method.");
 184    }
 185}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Contacts/3D/ContactAnchor.cs

#LineLine coverage
 1//=======================================================================
 2// ContactAnchor.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 System.Runtime.CompilerServices;
 11
 12namespace Gravitas.CollisionHandling;
 13
 14/// <summary>
 15/// Represents a 3D contact point in one rigid world-space frame.
 16/// </summary>
 17/// <remarks>
 18/// The underlying origin, rotation, and local point remain authoritative when
 19/// either the rotated offset or absolute world point lies outside the
 20/// <see cref="Fixed64"/> scalar range.
 21/// </remarks>
 22public readonly struct ContactAnchor
 23{
 24    private readonly FixedPointAnchor _point;
 25
 26    /// <summary>
 27    /// Creates a contact anchor in an identity-rotation rigid frame.
 28    /// </summary>
 29    public ContactAnchor(Vector3d origin, Vector3d offset)
 10698830        : this(origin, FixedQuaternion.Identity, offset)
 31    {
 10698832    }
 33
 34    /// <summary>
 35    /// Creates a contact anchor in a rigid local frame.
 36    /// </summary>
 37    public ContactAnchor(
 38        Vector3d origin,
 39        FixedQuaternion rotation,
 40        Vector3d localPoint)
 23374141        : this(origin, rotation, localPoint, Vector3d.Zero)
 42    {
 23374143    }
 44
 45    /// <summary>
 46    /// Creates a contact anchor whose two local terms remain separate until
 47    /// the exact rotated point is evaluated.
 48    /// </summary>
 49    public ContactAnchor(
 50        Vector3d origin,
 51        FixedQuaternion rotation,
 52        Vector3d localPoint,
 53        Vector3d localDisplacement)
 54    {
 23495855        _point = new FixedPointAnchor(
 23495856            origin,
 23495857            rotation,
 23495858            localPoint,
 23495859            localDisplacement);
 23495860    }
 61
 62    /// <summary>
 63    /// Creates a physics-domain wrapper for an exact point anchor.
 64    /// </summary>
 65    public ContactAnchor(FixedPointAnchor point)
 66    {
 6798067        if (!point.Rotation.IsNormalized())
 68        {
 169            throw new System.ArgumentException(
 170                "The point anchor must contain a normalized rotation.",
 171                nameof(point));
 72        }
 73
 6797974        _point = point;
 6797975    }
 76
 77    /// <summary>
 78    /// Gets the representable world-space origin.
 79    /// </summary>
 65380    public Vector3d Origin => _point.Origin;
 81
 82    /// <summary>
 83    /// Gets the normalized local-to-world frame rotation.
 84    /// </summary>
 8141385    public FixedQuaternion Rotation => _point.Rotation;
 86
 8137387    internal bool IsValid => Rotation.IsNormalized();
 88
 89    /// <summary>
 90    /// Gets the point in the contact frame's local coordinates.
 91    /// </summary>
 4392    public Vector3d LocalPoint => _point.LocalPoint;
 93
 94    /// <summary>
 95    /// Gets the secondary local displacement retained separately from
 96    /// <see cref="LocalPoint"/>.
 97    /// </summary>
 3198    public Vector3d LocalDisplacement => _point.LocalDisplacement;
 99
 100    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 101    internal int CompareLocalFeature(in ContactAnchor other) =>
 30361102        _point.CompareLocalFeature(other._point);
 103
 104    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 105    internal ulong GetLocalFeatureHash64() =>
 60786106        _point.GetLocalFeatureHash64();
 107
 108    /// <summary>
 109    /// Gets the rotated offset from <see cref="Origin"/> to the conceptual
 110    /// contact point.
 111    /// </summary>
 112    /// <exception cref="System.InvalidOperationException">
 113    /// The rotated offset lies outside the representable coordinate domain.
 114    /// </exception>
 115    public Vector3d Offset
 116    {
 117        get
 118        {
 13119            bool succeeded = TryGetOffset(out Vector3d offset);
 13120            SwiftThrowHelper.ThrowIfTrue(
 13121                !succeeded,
 13122                nameof(Offset),
 13123                "The contact offset is outside the representable coordinate domain.");
 12124            return offset;
 125        }
 126    }
 127
 128    /// <summary>
 129    /// Creates an anchor for an already materialized world point.
 130    /// </summary>
 131    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 132    public static ContactAnchor FromWorldPoint(Vector3d point) =>
 5006133        new(Vector3d.Zero, point);
 134
 135    /// <summary>
 136    /// Attempts to materialize the absolute world point without saturation.
 137    /// </summary>
 138    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 139    public bool TryGetWorldPoint(out Vector3d point) =>
 1064140        _point.TryGetPoint(out point);
 141
 142    /// <summary>
 143    /// Attempts to obtain the rotated offset from <see cref="Origin"/> to the
 144    /// conceptual contact point.
 145    /// </summary>
 146    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 147    public bool TryGetOffset(out Vector3d offset) =>
 17148        _point.TryGetOffsetFrom(
 17149            new FixedPointAnchor(
 17150                Origin,
 17151                FixedQuaternion.Identity,
 17152                Vector3d.Zero),
 17153            out offset);
 154
 155    /// <summary>
 156    /// Attempts to rebase the conceptual point onto another origin with one
 157    /// final exact component-wise add/subtract operation.
 158    /// </summary>
 159    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 160    public bool TryGetOffsetFrom(Vector3d origin, out Vector3d offset) =>
 2012161        _point.TryGetOffsetFrom(
 2012162            new FixedPointAnchor(
 2012163                origin,
 2012164                FixedQuaternion.Identity,
 2012165                Vector3d.Zero),
 2012166            out offset);
 167
 168    /// <summary>
 169    /// Attempts to obtain this point's exact world-space offset from another
 170    /// rigid-frame contact anchor.
 171    /// </summary>
 172    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 173    public bool TryGetOffsetFrom(
 174        in ContactAnchor other,
 175        out Vector3d offset) =>
 118595176        _point.TryGetOffsetFrom(other._point, out offset);
 177
 178    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 179    internal ExactLever3D GetLeverFrom(in ContactAnchor other) =>
 4044180        ExactLever3D.Create(_point, other._point);
 181
 182    /// <summary>
 183    /// Attempts to express the same conceptual point relative to another
 184    /// representable origin.
 185    /// </summary>
 186    public bool TryRebase(Vector3d origin, out ContactAnchor anchor)
 187    {
 2188        if (!_point.TryReframe(
 2189                origin,
 2190                FixedQuaternion.Identity,
 2191                out FixedPointAnchor point))
 192        {
 1193            anchor = default;
 1194            return false;
 195        }
 196
 1197        anchor = new ContactAnchor(point);
 1198        return true;
 199    }
 200
 201    /// <summary>
 202    /// Attempts to express the same conceptual point in another rigid frame.
 203    /// </summary>
 204    public bool TryRebase(
 205        Vector3d origin,
 206        FixedQuaternion rotation,
 207        out ContactAnchor anchor)
 208    {
 3209        if (!_point.TryReframe(
 3210                origin,
 3211                rotation,
 3212                out FixedPointAnchor point))
 213        {
 1214            anchor = default;
 1215            return false;
 216        }
 217
 2218        anchor = new ContactAnchor(point);
 2219        return true;
 220    }
 221}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Contacts/3D/ContactManifold.cs

#LineLine coverage
 1//=======================================================================
 2// ContactManifold.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.Materials;
 10using System.Collections;
 11using System.Collections.Generic;
 12using System.Runtime.CompilerServices;
 13
 14namespace Gravitas.CollisionHandling;
 15
 16/// <summary>
 17/// Fixed-capacity deterministic contact manifold owned by one collision pair.
 18/// </summary>
 19public sealed class ContactManifold : IEnumerable<ManifoldContact>
 20{
 21    /// <summary>Maximum number of contacts retained by a 3D manifold.</summary>
 22    public const int MaxContactCount = 4;
 23
 24    private ManifoldContact _contact0;
 25    private ManifoldContact _contact1;
 26    private ManifoldContact _contact2;
 27    private ManifoldContact _contact3;
 28    private int _count;
 1086629    private int _lastUpdatedFrame = -1;
 30
 31    /// <summary>
 32    /// Number of active contacts in this manifold.
 33    /// </summary>
 34    public int Count
 35    {
 36        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 2078137        get => _count;
 38    }
 39
 40    /// <summary>
 41    /// Gets whether this manifold currently contains narrow-phase contact data.
 42    /// </summary>
 43    public bool HasContact
 44    {
 45        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1702646        get => _count > 0;
 47    }
 48
 49    /// <summary>
 50    /// Simulation frame in which the active contacts were last rebuilt.
 51    /// </summary>
 52    public int LastUpdatedFrame
 53    {
 54        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 4455        get => _lastUpdatedFrame;
 56    }
 57
 58    /// <summary>
 59    /// Deepest contact in the manifold. Ties use the lowest contact identity.
 60    /// </summary>
 61    public ManifoldContact PrimaryContact
 62    {
 63        get
 64        {
 156665            SwiftThrowHelper.ThrowIfListIndexInvalid(0, _count);
 66
 156667            int bestIndex = 0;
 156668            ManifoldContact best = _contact0;
 357469            for (int i = 1; i < _count; i++)
 70            {
 22171                ManifoldContact candidate = this[i];
 22172                if (candidate.Depth > best.Depth
 22173                    || candidate.Depth == best.Depth && candidate.ContactId < best.ContactId)
 74                {
 875                    best = candidate;
 876                    bestIndex = i;
 77                }
 78            }
 79
 156680            return this[bestIndex];
 81        }
 82    }
 83
 84    /// <summary>Gets the contact at the specified deterministic order index.</summary>
 85    public ManifoldContact this[int index]
 86    {
 87        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 88        get
 89        {
 1301390            SwiftThrowHelper.ThrowIfListIndexInvalid(index, _count);
 1301391            return GetContactUnchecked(index);
 92        }
 93    }
 94
 95    /// <summary>
 96    /// Clears contacts and records the frame for a new narrow-phase pass.
 97    /// </summary>
 98    public void BeginUpdate(int frame)
 99    {
 10052100        _count = 0;
 10052101        _lastUpdatedFrame = frame;
 10052102    }
 103
 104    /// <summary>
 105    /// Clears all contact data.
 106    /// </summary>
 107    public void Reset()
 108    {
 2534109        _count = 0;
 2534110        _lastUpdatedFrame = -1;
 2534111        _contact0 = default;
 2534112        _contact1 = default;
 2534113        _contact2 = default;
 2534114        _contact3 = default;
 2534115    }
 116
 117    /// <summary>
 118    /// Replaces the manifold with one contact.
 119    /// </summary>
 120    public void SetContact(Vector3d pointA, Vector3d pointB, Fixed64 depth, Vector3d normal)
 121    {
 64122        _count = 0;
 64123        AddContact(pointA, pointB, depth, normal);
 64124    }
 125
 126    /// <summary>
 127    /// Replaces the manifold with one rigid-frame contact.
 128    /// </summary>
 129    public void SetContact(
 130        ContactAnchor anchorA,
 131        ContactAnchor anchorB,
 132        Fixed64 depth,
 133        Vector3d normal,
 134        bool depthIsClamped = false)
 135    {
 6189136        _count = 0;
 6189137        AddContact(anchorA, anchorB, depth, normal, depthIsClamped);
 6189138    }
 139
 140    /// <summary>
 141    /// Adds a contact, keeping the deepest four contacts and exposing them by stable contact identity.
 142    /// </summary>
 143    public void AddContact(Vector3d pointA, Vector3d pointB, Fixed64 depth, Vector3d normal)
 144    {
 1091145        AddContact(
 1091146            ContactAnchor.FromWorldPoint(pointA),
 1091147            ContactAnchor.FromWorldPoint(pointB),
 1091148            depth,
 1091149            normal);
 1091150    }
 151
 152    /// <summary>
 153    /// Adds a rigid-frame contact, keeping the deepest four contacts and
 154    /// exposing them by stable anchor identity.
 155    /// </summary>
 156    public void AddContact(
 157        ContactAnchor anchorA,
 158        ContactAnchor anchorB,
 159        Fixed64 depth,
 160        Vector3d normal,
 161        bool depthIsClamped = false)
 162    {
 30360163        AddContactCore(
 30360164            anchorA,
 30360165            anchorB,
 30360166            depth,
 30360167            normal,
 30360168            hasMaterialOverride: false,
 30360169            default,
 30360170            default,
 30360171            depthIsClamped,
 30360172            featureNamespaceA: 0,
 30360173            featureNamespaceB: 0);
 30360174    }
 175
 176    internal void AddContact(
 177        Vector3d pointA,
 178        Vector3d pointB,
 179        Fixed64 depth,
 180        Vector3d normal,
 181        PhysicsMaterial materialA,
 182        PhysicsMaterial materialB,
 183        bool depthIsClamped = false)
 184    {
 1185        AddContactCore(
 1186            ContactAnchor.FromWorldPoint(pointA),
 1187            ContactAnchor.FromWorldPoint(pointB),
 1188            depth,
 1189            normal,
 1190            hasMaterialOverride: true,
 1191            materialA,
 1192            materialB,
 1193            depthIsClamped,
 1194            featureNamespaceA: 0,
 1195            featureNamespaceB: 0);
 1196    }
 197
 198    internal void AddContact(
 199        ContactAnchor anchorA,
 200        ContactAnchor anchorB,
 201        Fixed64 depth,
 202        Vector3d normal,
 203        PhysicsMaterial materialA,
 204        PhysicsMaterial materialB,
 205        bool depthIsClamped = false,
 206        int featureNamespaceA = 0,
 207        int featureNamespaceB = 0)
 208    {
 19209        AddContactCore(
 19210            anchorA,
 19211            anchorB,
 19212            depth,
 19213            normal,
 19214            hasMaterialOverride: true,
 19215            materialA,
 19216            materialB,
 19217            depthIsClamped,
 19218            featureNamespaceA,
 19219            featureNamespaceB);
 19220    }
 221
 222    private void AddContactCore(
 223        ContactAnchor anchorA,
 224        ContactAnchor anchorB,
 225        Fixed64 depth,
 226        Vector3d normal,
 227        bool hasMaterialOverride,
 228        PhysicsMaterial materialA,
 229        PhysicsMaterial materialB,
 230        bool depthIsClamped,
 231        int featureNamespaceA,
 232        int featureNamespaceB)
 233    {
 30380234        ulong contactId = CreateContactId(
 30380235            anchorA,
 30380236            featureNamespaceA,
 30380237            anchorB,
 30380238            featureNamespaceB);
 30380239        var contact = new ManifoldContact(
 30380240            contactId,
 30380241            anchorA,
 30380242            anchorB,
 30380243            depth,
 30380244            normal,
 30380245            hasMaterialOverride,
 30380246            materialA,
 30380247            materialB,
 30380248            depthIsClamped,
 30380249            featureNamespaceA,
 30380250            featureNamespaceB);
 251
 212098252        for (int i = 0; i < _count; i++)
 253        {
 76959254            ManifoldContact existing = GetContactUnchecked(i);
 76959255            if (existing.ContactId != contactId)
 256                continue;
 257
 1290258            if (IsDeeper(contact, existing))
 3259                SetContactUnchecked(i, contact);
 1290260            SortContactsById();
 1290261            return;
 262        }
 263
 29090264        if (_count < MaxContactCount)
 265        {
 12682266            SetContactUnchecked(_count, contact);
 12682267            _count++;
 12682268            SortContactsById();
 12682269            return;
 270        }
 271
 16408272        int replaceIndex = FindShallowestReplacementIndex(contact);
 16408273        if (replaceIndex < 0)
 12382274            return;
 275
 4026276        SetContactUnchecked(replaceIndex, contact);
 4026277        SortContactsById();
 4026278    }
 279
 280    /// <summary>Returns an allocation-free enumerator over the active contacts.</summary>
 51281    public Enumerator GetEnumerator() => new(this);
 282
 50283    IEnumerator<ManifoldContact> IEnumerable<ManifoldContact>.GetEnumerator() => GetEnumerator();
 284
 1285    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
 286
 287    private int FindShallowestReplacementIndex(ManifoldContact candidate)
 288    {
 16408289        int replaceIndex = 0;
 16408290        ManifoldContact shallowest = _contact0;
 291
 131264292        for (int i = 1; i < _count; i++)
 293        {
 49224294            ManifoldContact contact = GetContactUnchecked(i);
 49224295            if (contact.Depth <= shallowest.Depth)
 296            {
 49211297                shallowest = contact;
 49211298                replaceIndex = i;
 299            }
 300        }
 301
 16408302        if (IsDeeper(candidate, shallowest))
 6303            return replaceIndex;
 304
 16402305        if (HasEqualDepth(candidate, shallowest) && candidate.ContactId < shallowest.ContactId)
 4020306            return replaceIndex;
 307
 12382308        return -1;
 309    }
 310
 311    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 312    private static bool IsDeeper(ManifoldContact candidate, ManifoldContact existing) =>
 17698313        candidate.Depth > existing.Depth
 17698314        || candidate.Depth == existing.Depth
 17698315        && candidate.DepthIsClamped
 17698316        && !existing.DepthIsClamped;
 317
 318    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 319    private static bool HasEqualDepth(ManifoldContact left, ManifoldContact right) =>
 16402320        left.Depth == right.Depth
 16402321        && left.DepthIsClamped == right.DepthIsClamped;
 322
 323    private void SortContactsById()
 324    {
 84484325        for (int i = 1; i < _count; i++)
 326        {
 24244327            ManifoldContact contact = GetContactUnchecked(i);
 24244328            int j = i - 1;
 34363329            while (j >= 0 && GetContactUnchecked(j).ContactId > contact.ContactId)
 330            {
 10119331                SetContactUnchecked(j + 1, GetContactUnchecked(j));
 10119332                j--;
 333            }
 334
 24244335            SetContactUnchecked(j + 1, contact);
 336        }
 17998337    }
 338
 339    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 340    private ManifoldContact GetContactUnchecked(int index) =>
 205530341        index switch
 205530342        {
 49972343            0 => _contact0,
 62320344            1 => _contact1,
 53808345            2 => _contact2,
 39430346            _ => _contact3
 205530347        };
 348
 349    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 350    private void SetContactUnchecked(int index, ManifoldContact contact)
 351    {
 352        switch (index)
 353        {
 354            case 0:
 10482355                _contact0 = contact;
 10482356                break;
 357            case 1:
 14478358                _contact1 = contact;
 14478359                break;
 360            case 2:
 14163361                _contact2 = contact;
 14163362                break;
 363            default:
 11951364                _contact3 = contact;
 365                break;
 366        }
 11951367    }
 368
 369    private static ulong CreateContactId(
 370        ContactAnchor anchorA,
 371        int featureNamespaceA,
 372        ContactAnchor anchorB,
 373        int featureNamespaceB)
 374    {
 30380375        if (CompareLocalFeature(
 30380376                featureNamespaceB,
 30380377                anchorB,
 30380378                featureNamespaceA,
 30380379                anchorA) < 0)
 380        {
 27632381            (anchorA, anchorB) = (anchorB, anchorA);
 27632382            (featureNamespaceA, featureNamespaceB) =
 27632383                (featureNamespaceB, featureNamespaceA);
 384        }
 385
 30380386        ulong hash = 14695981039346656037UL;
 30380387        Mix(ref hash, featureNamespaceA);
 30380388        MixLocalFeature(ref hash, anchorA);
 30380389        Mix(ref hash, featureNamespaceB);
 30380390        MixLocalFeature(ref hash, anchorB);
 30380391        return hash;
 392    }
 393
 394    private static int CompareLocalFeature(
 395        int leftNamespace,
 396        ContactAnchor left,
 397        int rightNamespace,
 398        ContactAnchor right)
 399    {
 30380400        int comparison = leftNamespace.CompareTo(rightNamespace);
 30380401        return comparison != 0
 30380402            ? comparison
 30380403            : left.CompareLocalFeature(right);
 404    }
 405
 406    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 407    private static void Mix(ref ulong hash, long value)
 408    {
 409        unchecked
 410        {
 121520411            hash ^= (ulong)value;
 121520412            hash *= 1099511628211UL;
 413        }
 121520414    }
 415
 416    private static void MixLocalFeature(
 417        ref ulong hash,
 418        ContactAnchor anchor)
 419    {
 60760420        Mix(
 60760421            ref hash,
 60760422            unchecked((long)anchor.GetLocalFeatureHash64()));
 60760423    }
 424
 425    /// <summary>Enumerates the active contacts in deterministic order.</summary>
 426    public struct Enumerator : IEnumerator<ManifoldContact>
 427    {
 428        private readonly ContactManifold _manifold;
 429        private int _index;
 430
 431        internal Enumerator(ContactManifold manifold)
 432        {
 51433            _manifold = manifold;
 51434            _index = -1;
 51435        }
 436
 437        /// <summary>Gets the current contact.</summary>
 177438        public ManifoldContact Current => _manifold[_index];
 439
 3440        object IEnumerator.Current => Current;
 441
 442        /// <summary>Advances to the next active contact.</summary>
 443        public bool MoveNext()
 444        {
 227445            int next = _index + 1;
 227446            if (next >= _manifold._count)
 50447                return false;
 448
 177449            _index = next;
 177450            return true;
 451        }
 452
 453        /// <summary>Resets the enumerator to its initial position.</summary>
 1454        public void Reset() => _index = -1;
 455
 456        /// <summary>Releases enumerator resources.</summary>
 50457        public void Dispose() { }
 458    }
 459}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Contacts/3D/ContactWarmStartCache.cs

#LineLine coverage
 1//=======================================================================
 2// ContactWarmStartCache.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.CollisionHandling;
 12
 13/// <summary>
 14/// Fixed-size 3D warm-start cache keyed by stable manifold contact identity.
 15/// </summary>
 16internal struct ContactWarmStartCache
 17{
 18    private ulong _contactId0;
 19    private ulong _contactId1;
 20    private ulong _contactId2;
 21    private ulong _contactId3;
 22    private ContactWarmStartImpulse _impulse0;
 23    private ContactWarmStartImpulse _impulse1;
 24    private ContactWarmStartImpulse _impulse2;
 25    private ContactWarmStartImpulse _impulse3;
 26
 27    public int Count { get; private set; }
 28
 29    public void Clear()
 30    {
 251031        Count = 0;
 251032        _contactId0 = 0UL;
 251033        _contactId1 = 0UL;
 251034        _contactId2 = 0UL;
 251035        _contactId3 = 0UL;
 251036        _impulse0 = default;
 251037        _impulse1 = default;
 251038        _impulse2 = default;
 251039        _impulse3 = default;
 251040    }
 41
 42    public void Set(
 43        ulong contactId,
 44        Vector3d normal,
 45        Fixed64 normalImpulse,
 46        Fixed64 tangentImpulse,
 47        Fixed64 secondaryTangentImpulse = default)
 48    {
 912149        ContactWarmStartImpulse impulse = new(normal, normalImpulse, tangentImpulse, secondaryTangentImpulse);
 6630450        for (int i = 0; i < Count; i++)
 51        {
 3204652            if (GetContactId(i) != contactId)
 53                continue;
 54
 801555            SetImpulseUnchecked(i, impulse);
 801556            return;
 57        }
 58
 110659        if (Count < ContactManifold.MaxContactCount)
 60        {
 38061            SetContactIdUnchecked(Count, contactId);
 38062            SetImpulseUnchecked(Count, impulse);
 38063            Count++;
 38064            return;
 65        }
 66
 67        // Contact manifolds are already reduced to four stable contacts; replacement preserves bounded state if an olde
 72668        SetContactIdUnchecked(ContactManifold.MaxContactCount - 1, contactId);
 72669        SetImpulseUnchecked(ContactManifold.MaxContactCount - 1, impulse);
 72670    }
 71
 72    public bool TryGet(ulong contactId, out ContactWarmStartImpulse impulse)
 73    {
 6612474        for (int i = 0; i < Count; i++)
 75        {
 3202276            if (GetContactId(i) != contactId)
 77                continue;
 78
 809379            impulse = GetImpulseUnchecked(i);
 809380            return true;
 81        }
 82
 104083        impulse = default;
 104084        return false;
 85    }
 86
 87    public bool Remove(ulong contactId)
 88    {
 3289        for (int index = 0; index < Count; index++)
 90        {
 1291            if (GetContactId(index) != contactId)
 92                continue;
 93
 994            Count--;
 2095            for (int shift = index; shift < Count; shift++)
 96            {
 197                SetContactIdUnchecked(
 198                    shift,
 199                    GetContactId(shift + 1));
 1100                SetImpulseUnchecked(
 1101                    shift,
 1102                    GetImpulseUnchecked(shift + 1));
 103            }
 104
 9105            SetContactIdUnchecked(Count, 0UL);
 9106            SetImpulseUnchecked(Count, default);
 9107            return true;
 108        }
 109
 4110        return false;
 111    }
 112
 113    internal ulong GetContactIdForReplayHash(int index)
 114    {
 3115        SwiftThrowHelper.ThrowIfArrayIndexInvalid(index, Count, nameof(index));
 3116        return GetContactId(index);
 117    }
 118
 119    internal ContactWarmStartImpulse GetImpulseForReplayHash(int index)
 120    {
 1121        SwiftThrowHelper.ThrowIfArrayIndexInvalid(index, Count, nameof(index));
 1122        return GetImpulseUnchecked(index);
 123    }
 124
 125    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 126    private ulong GetContactId(int index) =>
 64084127        index switch
 64084128        {
 17808129            0 => _contactId0,
 16377130            1 => _contactId1,
 15387131            2 => _contactId2,
 14512132            _ => _contactId3
 64084133        };
 134
 135    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 136    private ContactWarmStartImpulse GetImpulseUnchecked(int index) =>
 8095137        index switch
 8095138        {
 681139            0 => _impulse0,
 450140            1 => _impulse1,
 404141            2 => _impulse2,
 6560142            _ => _impulse3
 8095143        };
 144
 145    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 146    private void SetContactIdUnchecked(int index, ulong contactId)
 147    {
 148        switch (index)
 149        {
 150            case 0:
 237151                _contactId0 = contactId;
 237152                break;
 153            case 1:
 62154                _contactId1 = contactId;
 62155                break;
 156            case 2:
 51157                _contactId2 = contactId;
 51158                break;
 159            default:
 766160                _contactId3 = contactId;
 161                break;
 162        }
 766163    }
 164
 165    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 166    private void SetImpulseUnchecked(int index, ContactWarmStartImpulse impulse)
 167    {
 168        switch (index)
 169        {
 170            case 0:
 869171                _impulse0 = impulse;
 869172                break;
 173            case 1:
 512174                _impulse1 = impulse;
 512175                break;
 176            case 2:
 454177                _impulse2 = impulse;
 454178                break;
 179            default:
 7296180                _impulse3 = impulse;
 181                break;
 182        }
 7296183    }
 184}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Contacts/3D/ManifoldContact.cs

#LineLine coverage
 1//=======================================================================
 2// ManifoldContact.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.Materials;
 10using System;
 11using System.Runtime.CompilerServices;
 12
 13namespace Gravitas.CollisionHandling;
 14
 15/// <summary>
 16/// A deterministic narrow-phase contact point for a collision manifold.
 17/// </summary>
 18public readonly struct ManifoldContact
 19{
 20    /// <summary>Creates a deterministic contact from world-space witness points.</summary>
 21    public ManifoldContact(
 22        ulong contactId,
 23        Vector3d pointA,
 24        Vector3d pointB,
 25        Fixed64 depth,
 26        Vector3d normal,
 27        bool hasMaterialOverride = false,
 28        PhysicsMaterial materialA = default,
 29        PhysicsMaterial materialB = default,
 30        bool depthIsClamped = false)
 331        : this(
 332            contactId,
 333            ContactAnchor.FromWorldPoint(pointA),
 334            ContactAnchor.FromWorldPoint(pointB),
 335            depth,
 336            normal,
 337            hasMaterialOverride,
 338            materialA,
 339            materialB,
 340            depthIsClamped)
 341    { }
 42
 43    /// <summary>
 44    /// Creates a deterministic contact from authoritative rigid-frame anchors.
 45    /// </summary>
 46    public ManifoldContact(
 47        ulong contactId,
 48        ContactAnchor anchorA,
 49        ContactAnchor anchorB,
 50        Fixed64 depth,
 51        Vector3d normal,
 52        bool hasMaterialOverride = false,
 53        PhysicsMaterial materialA = default,
 54        PhysicsMaterial materialB = default,
 55        bool depthIsClamped = false)
 956        : this(
 957            contactId,
 958            anchorA,
 959            anchorB,
 960            depth,
 961            normal,
 962            hasMaterialOverride,
 963            materialA,
 964            materialB,
 965            depthIsClamped,
 966            featureNamespaceA: 0,
 967            featureNamespaceB: 0)
 768    { }
 69
 70    internal ManifoldContact(
 71        ulong contactId,
 72        ContactAnchor anchorA,
 73        ContactAnchor anchorB,
 74        Fixed64 depth,
 75        Vector3d normal,
 76        bool hasMaterialOverride,
 77        PhysicsMaterial materialA,
 78        PhysicsMaterial materialB,
 79        bool depthIsClamped,
 80        int featureNamespaceA,
 81        int featureNamespaceB)
 82    {
 3038983        if (!anchorA.IsValid)
 184            throw new ArgumentException("Contact anchor A must be valid.", nameof(anchorA));
 3038885        if (!anchorB.IsValid)
 186            throw new ArgumentException("Contact anchor B must be valid.", nameof(anchorB));
 87
 3038788        ContactId = contactId;
 3038789        AnchorA = anchorA;
 3038790        AnchorB = anchorB;
 3038791        FeatureNamespaceA = featureNamespaceA;
 3038792        FeatureNamespaceB = featureNamespaceB;
 3038793        Depth = depth.Abs();
 3038794        DepthIsClamped = depthIsClamped;
 3038795        Normal = normal.MagnitudeSquared > Fixed64.Epsilon
 3038796            ? normal.Normalized
 3038797            : Vector3d.Zero;
 3038798        HasMaterialOverride = hasMaterialOverride;
 3038799        MaterialA = hasMaterialOverride ? materialA : PhysicsMaterial.Default;
 30387100        MaterialB = hasMaterialOverride ? materialB : PhysicsMaterial.Default;
 30387101    }
 102
 103    /// <summary>
 104    /// Stable identity derived from the unordered pair of pose-invariant
 105    /// canonical local feature terms.
 106    /// </summary>
 107    public ulong ContactId { get; }
 108
 109    /// <summary>
 110    /// Authoritative canonical contact anchor on collider A.
 111    /// </summary>
 112    public ContactAnchor AnchorA { get; }
 113
 114    /// <summary>
 115    /// Authoritative canonical contact anchor on collider B.
 116    /// </summary>
 117    public ContactAnchor AnchorB { get; }
 118
 119    internal int FeatureNamespaceA { get; }
 120
 121    internal int FeatureNamespaceB { get; }
 122
 123    /// <summary>
 124    /// Gets the world-space contact point on collider A.
 125    /// </summary>
 126    /// <exception cref="InvalidOperationException">
 127    /// The conceptual world point is outside the scalar range.
 128    /// </exception>
 103129    public Vector3d PointA => GetRequiredWorldPoint(AnchorA, nameof(PointA));
 130
 131    /// <summary>
 132    /// Gets the world-space contact point on collider B.
 133    /// </summary>
 134    /// <exception cref="InvalidOperationException">
 135    /// The conceptual world point is outside the scalar range.
 136    /// </exception>
 74137    public Vector3d PointB => GetRequiredWorldPoint(AnchorB, nameof(PointB));
 138
 139    /// <summary>
 140    /// Attempts to materialize the world-space contact point on collider A.
 141    /// </summary>
 142    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 40143    public bool TryGetPointA(out Vector3d point) => AnchorA.TryGetWorldPoint(out point);
 144
 145    /// <summary>
 146    /// Attempts to materialize the world-space contact point on collider B.
 147    /// </summary>
 148    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 38149    public bool TryGetPointB(out Vector3d point) => AnchorB.TryGetWorldPoint(out point);
 150
 151    /// <summary>
 152    /// Penetration depth along <see cref="Normal"/>.
 153    /// </summary>
 154    public Fixed64 Depth { get; }
 155
 156    /// <summary>
 157    /// Gets whether the conceptual penetration depth exceeded the scalar range.
 158    /// </summary>
 159    public bool DepthIsClamped { get; }
 160
 161    /// <summary>
 162    /// Unit normal pointing from collider A toward collider B.
 163    /// </summary>
 164    public Vector3d Normal { get; }
 165
 166    /// <summary>
 167    /// Gets whether this contact carries materials from private compound parts.
 168    /// </summary>
 169    public bool HasMaterialOverride { get; }
 170
 171    /// <summary>
 172    /// Material for collider A when <see cref="HasMaterialOverride"/> is true.
 173    /// </summary>
 174    public PhysicsMaterial MaterialA { get; }
 175
 176    /// <summary>
 177    /// Material for collider B when <see cref="HasMaterialOverride"/> is true.
 178    /// </summary>
 179    public PhysicsMaterial MaterialB { get; }
 180
 181    private static Vector3d GetRequiredWorldPoint(ContactAnchor anchor, string propertyName)
 182    {
 177183        if (anchor.TryGetWorldPoint(out Vector3d point))
 176184            return point;
 185
 1186        throw new InvalidOperationException(
 1187            $"{propertyName} is outside the representable coordinate range. Use the corresponding TryGet method.");
 188    }
 189}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Contacts/ContactSelectionPolicy.cs

#LineLine coverage
 1//=======================================================================
 2// ContactSelectionPolicy.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 System.Runtime.CompilerServices;
 9
 10namespace Gravitas.CollisionHandling;
 11
 12internal static class ContactSelectionPolicy
 13{
 14    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 15    internal static bool ShouldReplaceWithDeeper(Contact2D candidate, bool found, Contact2D current) =>
 1416        !found
 1417        || candidate.Depth > current.Depth
 1418        || (candidate.Depth == current.Depth
 1419            && candidate.DepthIsClamped
 1420            && !current.DepthIsClamped);
 21
 22    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 23    internal static bool ShouldReplaceWithShallower(MixedContact candidate, bool found, MixedContact current) =>
 4724        !found
 4725        || candidate.Depth < current.Depth
 4726        || (candidate.Depth == current.Depth
 4727            && current.DepthIsClamped
 4728            && !candidate.DepthIsClamped);
 29}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Contacts/Mixed/MixedContact.cs

#LineLine coverage
 1//=======================================================================
 2// MixedContact.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.Materials;
 11using System;
 12using System.Runtime.CompilerServices;
 13
 14namespace Gravitas.CollisionHandling;
 15
 16/// <summary>
 17/// Contact generated between one 3D collider and one embedded 2D collider.
 18/// </summary>
 19public readonly struct MixedContact
 20{
 21    /// <summary>Creates a mixed contact from world-space witness points.</summary>
 22    public MixedContact(
 23        Vector3d point3D,
 24        Vector3d point2D,
 25        Vector3d normal3DTo2D,
 26        Fixed64 depth,
 27        bool depthIsClamped = false)
 4728        : this(
 4729            ContactAnchor.FromWorldPoint(point3D),
 4730            ContactAnchor.FromWorldPoint(point2D),
 4731            normal3DTo2D,
 4732            depth,
 4733            depthIsClamped,
 4734            hasMaterialOverride: false,
 4735            default,
 4736            default)
 4737    { }
 38
 39    /// <summary>
 40    /// Creates a mixed contact with authoritative rigid-frame witnesses.
 41    /// </summary>
 42    /// <exception cref="ArgumentException">
 43    /// Either rigid-frame anchor is invalid.
 44    /// </exception>
 45    public MixedContact(
 46        ContactAnchor anchor3D,
 47        ContactAnchor anchor2D,
 48        Vector3d normal3DTo2D,
 49        Fixed64 depth,
 50        bool depthIsClamped = false)
 347651        : this(
 347652            anchor3D,
 347653            anchor2D,
 347654            normal3DTo2D,
 347655            depth,
 347656            depthIsClamped,
 347657            hasMaterialOverride: false,
 347658            default,
 347659            default)
 347460    { }
 61
 62    internal MixedContact(
 63        Vector3d point3D,
 64        Vector3d point2D,
 65        Vector3d normal3DTo2D,
 66        Fixed64 depth,
 67        PhysicsMaterial material3D,
 68        PhysicsMaterial material2D,
 69        bool depthIsClamped = false)
 170        : this(
 171            ContactAnchor.FromWorldPoint(point3D),
 172            ContactAnchor.FromWorldPoint(point2D),
 173            normal3DTo2D,
 174            depth,
 175            depthIsClamped,
 176            hasMaterialOverride: true,
 177            material3D,
 178            material2D)
 179    { }
 80
 81    internal MixedContact(
 82        ContactAnchor anchor3D,
 83        ContactAnchor anchor2D,
 84        Vector3d normal3DTo2D,
 85        Fixed64 depth,
 86        PhysicsMaterial material3D,
 87        PhysicsMaterial material2D,
 88        bool depthIsClamped = false)
 1489        : this(
 1490            anchor3D,
 1491            anchor2D,
 1492            normal3DTo2D,
 1493            depth,
 1494            depthIsClamped,
 1495            hasMaterialOverride: true,
 1496            material3D,
 1497            material2D)
 1498    { }
 99
 100    private MixedContact(
 101        ContactAnchor anchor3D,
 102        ContactAnchor anchor2D,
 103        Vector3d normal3DTo2D,
 104        Fixed64 depth,
 105        bool depthIsClamped,
 106        bool hasMaterialOverride,
 107        PhysicsMaterial material3D,
 108        PhysicsMaterial material2D)
 109    {
 3538110        if (!anchor3D.IsValid)
 1111            throw new ArgumentException("The 3D contact anchor must be valid.", nameof(anchor3D));
 3537112        if (!anchor2D.IsValid)
 1113            throw new ArgumentException("The 2D contact anchor must be valid.", nameof(anchor2D));
 114
 3536115        Anchor3D = anchor3D;
 3536116        Anchor2D = anchor2D;
 3536117        Normal3DTo2D = normal3DTo2D;
 3536118        Depth = depth;
 3536119        DepthIsClamped = depthIsClamped;
 3536120        HasMaterialOverride = hasMaterialOverride;
 3536121        Material3D = hasMaterialOverride ? material3D : PhysicsMaterial.Default;
 3536122        Material2D = hasMaterialOverride ? material2D : PhysicsMaterial.Default;
 3536123        HasContact = true;
 3536124    }
 125
 126    /// <summary>Gets whether this value contains a valid mixed contact.</summary>
 127    public bool HasContact { get; }
 128
 129    /// <summary>Gets the authoritative contact anchor on the 3D collider.</summary>
 130    public ContactAnchor Anchor3D { get; }
 131
 132    /// <summary>Gets the authoritative contact anchor on the embedded 2D collider.</summary>
 133    public ContactAnchor Anchor2D { get; }
 134
 135    /// <summary>Gets the world-space witness point on the 3D collider.</summary>
 20136    public Vector3d Point3D => GetRequiredWorldPoint(Anchor3D, nameof(Point3D));
 137
 138    /// <summary>Gets the world-space witness point on the embedded 2D collider.</summary>
 25139    public Vector3d Point2D => GetRequiredWorldPoint(Anchor2D, nameof(Point2D));
 140
 141    /// <summary>Attempts to resolve the 3D anchor to a world-space point.</summary>
 142    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 170143    public bool TryGetPoint3D(out Vector3d point) => Anchor3D.TryGetWorldPoint(out point);
 144
 145    /// <summary>Attempts to resolve the embedded 2D anchor to a world-space point.</summary>
 146    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 173147    public bool TryGetPoint2D(out Vector3d point) => Anchor2D.TryGetWorldPoint(out point);
 148
 149    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 150    internal bool TryGetPlanarOffset2DFrom(
 151        Vector2d origin,
 152        out Vector2d offset) =>
 1153        TryGetPlanarOffset2DFrom(
 1154            origin,
 1155            Fixed64.Zero,
 1156            Vector2d.Zero,
 1157            out offset);
 158
 159    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 160    internal bool TryGetPlanarOffset2DFrom(
 161        Vector2d origin,
 162        Fixed64 rotation,
 163        Vector2d localPoint,
 164        out Vector2d offset)
 165    {
 379166        ContactAnchor reference =
 379167            CreatePlanarReference(origin, rotation, localPoint);
 379168        if (!Anchor2D.TryGetOffsetFrom(
 379169                reference,
 379170                out Vector3d offset3D))
 171        {
 2172            offset = default;
 2173            return false;
 174        }
 175
 377176        offset = new Vector2d(offset3D.X, offset3D.Z);
 377177        return true;
 178    }
 179
 180    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 181    internal ExactLever3D GetPlanarXZLeverFrom(
 182        Vector2d origin,
 183        Fixed64 rotation,
 184        Vector2d localPoint) =>
 149185        Anchor2D.GetLeverFrom(
 149186            CreatePlanarReference(origin, rotation, localPoint));
 187
 188    /// <summary>
 189    /// Contact normal pointing from the 3D collider toward the embedded 2D collider volume.
 190    /// </summary>
 191    public Vector3d Normal3DTo2D { get; }
 192
 193    /// <summary>Gets the penetration depth along <see cref="Normal3DTo2D"/>.</summary>
 194    public Fixed64 Depth { get; }
 195
 196    /// <summary>
 197    /// Gets whether the exact penetration depth exceeded the scalar domain and
 198    /// <see cref="Depth"/> therefore contains <see cref="Fixed64.MaxValue"/>.
 199    /// </summary>
 200    public bool DepthIsClamped { get; }
 201
 202    /// <summary>Gets whether the contact carries per-feature material values.</summary>
 203    public bool HasMaterialOverride { get; }
 204
 205    /// <summary>Gets the material sampled from the 3D contact feature.</summary>
 206    public PhysicsMaterial Material3D { get; }
 207
 208    /// <summary>Gets the material sampled from the embedded 2D contact feature.</summary>
 209    public PhysicsMaterial Material2D { get; }
 210
 211    internal MixedContact WithMaterialOverride(PhysicsMaterial material3D, PhysicsMaterial material2D) =>
 14212        new(
 14213            Anchor3D,
 14214            Anchor2D,
 14215            Normal3DTo2D,
 14216            Depth,
 14217            material3D,
 14218            material2D,
 14219            DepthIsClamped);
 220
 221    internal MixedContact WithFallbackMaterials(PhysicsMaterial material3D, PhysicsMaterial material2D) =>
 13222        HasMaterialOverride ? this : WithMaterialOverride(material3D, material2D);
 223
 224    private ContactAnchor CreatePlanarReference(
 225        Vector2d origin,
 226        Fixed64 rotation,
 227        Vector2d localPoint) =>
 528228        new(
 528229            new Vector3d(
 528230                origin.X,
 528231                Anchor2D.Origin.Y,
 528232                origin.Y),
 528233            FixedQuaternion.FromAxisAngle(Vector3d.Up, -rotation),
 528234            new Vector3d(
 528235                localPoint.X,
 528236                Fixed64.Zero,
 528237                localPoint.Y));
 238
 239    private static Vector3d GetRequiredWorldPoint(ContactAnchor anchor, string propertyName)
 240    {
 45241        if (anchor.TryGetWorldPoint(out Vector3d point))
 43242            return point;
 243
 2244        throw new InvalidOperationException(
 2245            $"{propertyName} is outside the representable coordinate range. Use the corresponding TryGet method.");
 246    }
 247}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Contacts/Shared/ContactWarmStartImpulse.cs

#LineLine coverage
 1//=======================================================================
 2// ContactWarmStartImpulse.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;
 9
 10namespace Gravitas.CollisionHandling;
 11
 12/// <summary>
 13/// Stores deterministic impulse scalars from the previous solve for one stable manifold contact.
 14/// </summary>
 15internal readonly struct ContactWarmStartImpulse
 16{
 17    public ContactWarmStartImpulse(Fixed64 normalImpulse, Fixed64 tangentImpulse)
 207418        : this(Vector3d.Zero, normalImpulse, tangentImpulse, Fixed64.Zero) { }
 19
 20    public ContactWarmStartImpulse(
 21        Vector3d normal,
 22        Fixed64 normalImpulse,
 23        Fixed64 tangentImpulse,
 24        Fixed64 secondaryTangentImpulse)
 25    {
 1015826        Normal = normal;
 1015827        NormalImpulse = normalImpulse;
 1015828        TangentImpulse = tangentImpulse;
 1015829        SecondaryTangentImpulse = secondaryTangentImpulse;
 1015830    }
 31
 32    public Vector3d Normal { get; }
 33
 34    public Fixed64 NormalImpulse { get; }
 35
 36    public Fixed64 TangentImpulse { get; }
 37
 38    /// <summary>
 39    /// Secondary tangent impulse for 3D contacts. Pure 2D contacts leave this value at zero.
 40    /// </summary>
 41    public Fixed64 SecondaryTangentImpulse { get; }
 42}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Continuous/ContinuousCollisionCandidateOrdering.cs

#LineLine coverage
 1//=======================================================================
 2// ContinuousCollisionCandidateOrdering.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.Queries;
 11using System.Runtime.CompilerServices;
 12
 13namespace Gravitas.CollisionHandling;
 14
 15internal static class ContinuousCollisionCandidateOrdering
 16{
 17    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 18    public static bool Is2DHitFirst(Fixed64 distance2D, Fixed64 distance3D) =>
 919        distance2D <= distance3D;
 20
 21    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 22    public static bool ShouldReplaceHit(
 23        Physics3DHit candidate,
 24        Fixed64 candidateClosingSpeed,
 25        bool hasCandidate,
 26        bool hasCurrent,
 27        Physics3DHit current,
 28        Fixed64 currentClosingSpeed)
 29    {
 49830        if (!hasCandidate)
 38531            return false;
 11332        if (!hasCurrent)
 9533            return true;
 34
 1835        int distance = candidate.Distance.CompareTo(current.Distance);
 1836        if (distance != 0)
 1337            return distance < 0;
 38
 539        int closing = candidateClosingSpeed.CompareTo(currentClosingSpeed);
 540        if (closing != 0)
 141            return closing > 0;
 42
 443        int candidateId = candidate.Collider?.Id ?? -1;
 444        int currentId = current.Collider?.Id ?? -1;
 445        return candidateId < currentId;
 46    }
 47
 48    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 49    public static bool ShouldReplaceHit(
 50        Physics2DHit candidate,
 51        Fixed64 candidateClosingSpeed,
 52        bool hasCandidate,
 53        bool hasCurrent,
 54        Physics2DHit current,
 55        Fixed64 currentClosingSpeed)
 56    {
 37557        if (!hasCandidate)
 28658            return false;
 8959        if (!hasCurrent)
 7360            return true;
 61
 1662        int distance = candidate.Distance.CompareTo(current.Distance);
 1663        if (distance != 0)
 1364            return distance < 0;
 65
 366        int closing = candidateClosingSpeed.CompareTo(currentClosingSpeed);
 367        if (closing != 0)
 168            return closing > 0;
 69
 270        return candidate.Collider.Id < current.Collider.Id;
 71    }
 72
 73    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 74    public static bool ShouldReplaceMixedHit(
 75        PhysicsMixedHit candidate,
 76        Fixed64 candidateClosingSpeed,
 77        bool hasCandidate,
 78        bool hasCurrent,
 79        PhysicsMixedHit current,
 80        Fixed64 currentClosingSpeed)
 81    {
 1582        if (!hasCandidate)
 183            return false;
 1484        if (!hasCurrent)
 185            return true;
 86
 1387        int distance = candidate.Distance.CompareTo(current.Distance);
 1388        if (distance != 0)
 489            return distance < 0;
 90
 991        int closing = candidateClosingSpeed.CompareTo(currentClosingSpeed);
 992        if (closing != 0)
 193            return closing > 0;
 94
 895        int reducer = candidate.ReducerKind.CompareTo(current.ReducerKind);
 896        if (reducer != 0)
 197            return reducer < 0;
 98
 799        int candidate3D = candidate.Collider3D?.Id ?? -1;
 7100        int current3D = current.Collider3D?.Id ?? -1;
 7101        int collider3D = candidate3D.CompareTo(current3D);
 7102        if (collider3D != 0)
 3103            return collider3D < 0;
 104
 4105        int candidate2D = candidate.Collider2D?.Id ?? -1;
 4106        int current2D = current.Collider2D?.Id ?? -1;
 4107        return candidate2D < current2D;
 108    }
 109
 110    public static bool IsIgnoredTarget(LSCollider hitCollider, LSCollider? ignored)
 111    {
 13661112        if (ignored == null)
 13406113            return false;
 114
 255115        if (ReferenceEquals(hitCollider, ignored))
 124116            return true;
 117
 131118        SolidBody? ignoredBody = ignored.Body;
 131119        if (ignoredBody != null && ReferenceEquals(hitCollider.Body, ignoredBody))
 1120            return true;
 121
 130122        LSCollider? hitTopParent = hitCollider.TopParent3D;
 130123        LSCollider? ignoredTopParent = ignored.TopParent3D;
 130124        return (hitTopParent != null && ReferenceEquals(hitTopParent, ignored))
 130125            || (ignoredTopParent != null && ReferenceEquals(hitCollider, ignoredTopParent))
 130126            || (hitTopParent != null && ignoredTopParent != null && ReferenceEquals(hitTopParent, ignoredTopParent));
 127    }
 128
 129    public static bool IsIgnoredTarget(LSCollider2D hitCollider, LSCollider2D? ignored)
 130    {
 1523131        if (ignored == null)
 1280132            return false;
 133
 243134        if (ReferenceEquals(hitCollider, ignored))
 149135            return true;
 136
 94137        SolidBody2D? ignoredBody = ignored.Body;
 94138        if (ignoredBody != null && ReferenceEquals(hitCollider.Body, ignoredBody))
 1139            return true;
 140
 93141        LSCollider2D? hitTopParent = hitCollider.TopParent2D;
 93142        LSCollider2D? ignoredTopParent = ignored.TopParent2D;
 93143        return (hitTopParent != null && ReferenceEquals(hitTopParent, ignored))
 93144            || (ignoredTopParent != null && ReferenceEquals(hitCollider, ignoredTopParent))
 93145            || (hitTopParent != null && ignoredTopParent != null && ReferenceEquals(hitTopParent, ignoredTopParent));
 146    }
 147}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Continuous/ContinuousCollisionContactPolicy.cs

#LineLine coverage
 1//=======================================================================
 2// ContinuousCollisionContactPolicy.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;
 11
 12namespace Gravitas.CollisionHandling;
 13
 14internal static class ContinuousCollisionContactPolicy
 15{
 16    internal static bool TryResolveSweptSphereContact(
 17        LSCollider target,
 18        Vector3d sphereCenterAtImpact,
 19        Vector3d direction,
 20        out ContactAnchor targetAnchor,
 21        out Vector3d normal)
 22    {
 720623        Vector3d fallbackNormal = direction.MagnitudeSquared > Fixed64.Epsilon
 720624            ? -direction.Normalized
 720625            : Vector3d.Right;
 26
 720627        if (target is LSCapsuleCollider capsule)
 28        {
 2529            normal = capsule.GetNormalAtPoint(sphereCenterAtImpact);
 2530            Vector3d localNormal =
 2531                capsule.Rotation.Inverse().Rotate(normal).Normalized;
 2532            targetAnchor = new ContactAnchor(
 2533                FixedSegment.GetSurfaceAnchorOnCenteredCapsule(
 2534                    sphereCenterAtImpact,
 2535                    capsule.Center,
 2536                    capsule.Rotation,
 2537                    Vector3d.Up,
 2538                    capsule.AxisLength,
 2539                    capsule.ScaledRadius,
 2540                    localNormal));
 2541            return true;
 42        }
 43
 718144        if (target is LSSphereCollider sphere)
 45        {
 664846            normal = Vector3d.GetDirection(sphere.Center, sphereCenterAtImpact);
 664847            if (normal == Vector3d.Zero)
 348                normal = fallbackNormal;
 664849            targetAnchor = new ContactAnchor(
 664850                FixedSegment.GetCenteredCapsuleSupportAnchor(
 664851                    sphere.Center,
 664852                    sphere.Rotation,
 664853                    Fixed64.Zero,
 664854                    sphere.ScaledRadius,
 664855                    normal));
 664856            return true;
 57        }
 58
 53359        if (target is LSCylinderCollider cylinder)
 60        {
 2361            if (!FixedSegment.TryGetClosestCenteredFiniteCylinderSurfaceAnchor(
 2362                    sphereCenterAtImpact,
 2363                    cylinder.Center,
 2364                    cylinder.Rotation,
 2365                    Vector3d.Up,
 2366                    cylinder.Height,
 2367                    cylinder.ScaledRadius,
 2368                    Vector3d.Right,
 2369                    out FixedPointAnchor targetPoint,
 2370                    out Vector3d outwardNormal,
 2371                    out Fixed64 signedDistance))
 72            {
 173                targetAnchor = default;
 174                normal = default;
 175                return false;
 76            }
 77
 2278            targetAnchor = new ContactAnchor(targetPoint);
 2279            normal = signedDistance < Fixed64.Zero ? -outwardNormal : outwardNormal;
 2280            return true;
 81        }
 82
 51083        if (target is LSConeCollider cone)
 84        {
 1585            if (!FixedSegment.TryGetClosestCenteredFiniteConeSurfaceAnchor(
 1586                    sphereCenterAtImpact,
 1587                    cone.Center,
 1588                    cone.Rotation,
 1589                    Vector3d.Up,
 1590                    cone.Height,
 1591                    cone.ScaledRadius,
 1592                    Vector3d.Right,
 1593                    out FixedPointAnchor targetPoint,
 1594                    out Vector3d outwardNormal,
 1595                    out Fixed64 signedDistance))
 96            {
 197                targetAnchor = default;
 198                normal = default;
 199                return false;
 100            }
 101
 14102            targetAnchor = new ContactAnchor(targetPoint);
 14103            normal = signedDistance < Fixed64.Zero ? -outwardNormal : outwardNormal;
 14104            return true;
 105        }
 106
 495107        if (target is LSCuboidCollider cuboid)
 108        {
 430109            FixedPointAnchor targetPoint =
 430110                cuboid.OrientedBox.GetClosestPointAnchor(sphereCenterAtImpact);
 430111            var sphereCenter = new FixedPointAnchor(
 430112                sphereCenterAtImpact,
 430113                FixedQuaternion.Identity,
 430114                Vector3d.Zero);
 430115            normal = sphereCenter.TryGetOffsetFrom(
 430116                    targetPoint,
 430117                    out Vector3d fromSurface)
 430118                && fromSurface.MagnitudeSquared > Fixed64.Epsilon
 430119                    ? fromSurface.Normalized
 430120                    : cuboid.GetNormalAtPoint(sphereCenterAtImpact);
 430121            targetAnchor = new ContactAnchor(targetPoint);
 430122            return true;
 123        }
 124
 65125        if (target is LSMeshCollider mesh)
 126        {
 49127            mesh.FindClosestPointAnchor(
 49128                sphereCenterAtImpact,
 49129                out FixedPointAnchor meshPoint,
 49130                out normal);
 49131            if (Vector3d.Dot(normal, direction) > Fixed64.Zero)
 4132                normal = -normal;
 49133            targetAnchor = new ContactAnchor(meshPoint);
 49134            return true;
 135        }
 136
 16137        Vector3d point = target.ClosestPointOnSurface(sphereCenterAtImpact);
 16138        normal = ResolveLegacyNormal(target, point, sphereCenterAtImpact, direction);
 16139        var worldPoint = new FixedPointAnchor(
 16140            point,
 16141            FixedQuaternion.Identity,
 16142            Vector3d.Zero);
 16143        if (worldPoint.TryGetLocalPointIn(
 16144                target.Center,
 16145                target.Rotation,
 16146                out Vector3d localPoint))
 147        {
 13148            targetAnchor = new ContactAnchor(
 13149                target.Center,
 13150                target.Rotation,
 13151                localPoint);
 13152            return true;
 153        }
 154
 3155        targetAnchor = default;
 3156        normal = default;
 3157        return false;
 158    }
 159
 160    private static Vector3d ResolveLegacyNormal(
 161        LSCollider target,
 162        Vector3d point,
 163        Vector3d sphereCenterAtImpact,
 164        Vector3d direction)
 165    {
 16166        Vector3d fromPointToSphereCenter = sphereCenterAtImpact - point;
 16167        Vector3d normal = target.GetNormalAtPoint(point);
 16168        if (normal.MagnitudeSquared > Fixed64.Epsilon)
 12169            return normal.Normalized;
 170
 4171        if (fromPointToSphereCenter.MagnitudeSquared > Fixed64.Epsilon)
 1172            return fromPointToSphereCenter.Normalized;
 173
 3174        return direction.MagnitudeSquared > Fixed64.Epsilon
 3175            ? -direction.Normalized
 3176            : Vector3d.Zero;
 177    }
 178}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Continuous/ContinuousCollisionImpulsePolicy.cs

#LineLine coverage
 1//=======================================================================
 2// ContinuousCollisionImpulsePolicy.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;
 9
 10namespace Gravitas.CollisionHandling;
 11
 12internal static class ContinuousCollisionImpulsePolicy
 13{
 14    internal static bool TryResolveVelocityDelta(
 15        Vector3d signedNormal,
 16        Fixed64 normalVelocity,
 17        Fixed64 responseFactor,
 18        Fixed64 bodyInverseMass,
 19        Fixed64 constrainedInverseMass,
 20        out Vector3d velocityDelta)
 21    {
 3222        if (bodyInverseMass == Fixed64.Zero)
 23        {
 824            velocityDelta = default;
 825            return true;
 26        }
 27
 2428        bool resolved = Fixed64.TryMultiplyDivide(
 2429            normalVelocity,
 2430            responseFactor,
 2431            bodyInverseMass,
 2432            constrainedInverseMass,
 2433            out Fixed64 speedDelta);
 2434        velocityDelta = signedNormal * speedDelta;
 2435        return resolved;
 36    }
 37
 38    internal static bool TryResolveVelocityDelta(
 39        Vector2d signedNormal,
 40        Fixed64 normalVelocity,
 41        Fixed64 responseFactor,
 42        Fixed64 bodyInverseMass,
 43        Fixed64 constrainedInverseMass,
 44        out Vector2d velocityDelta)
 45    {
 9246        if (bodyInverseMass == Fixed64.Zero)
 47        {
 2748            velocityDelta = default;
 2749            return true;
 50        }
 51
 6552        bool resolved = Fixed64.TryMultiplyDivide(
 6553            normalVelocity,
 6554            responseFactor,
 6555            bodyInverseMass,
 6556            constrainedInverseMass,
 6557            out Fixed64 speedDelta);
 6558        velocityDelta = signedNormal * speedDelta;
 6559        return resolved;
 60    }
 61
 62    internal static bool TryResolveVelocityDelta(
 63        Vector3d normal,
 64        Fixed64 responseSpeed,
 65        Fixed64 bodyInverseMass,
 66        Fixed64 constrainedInverseMass,
 67        out Vector3d velocityDelta)
 68    {
 1255569        if (bodyInverseMass == Fixed64.Zero)
 70        {
 771            velocityDelta = default;
 772            return true;
 73        }
 74
 1254875        if (!Fixed64.TryMultiplyDivide(normal.X, responseSpeed, bodyInverseMass, constrainedInverseMass, out Fixed64 x)
 1254876            || !Fixed64.TryMultiplyDivide(normal.Y, responseSpeed, bodyInverseMass, constrainedInverseMass, out Fixed64 
 1254877            || !Fixed64.TryMultiplyDivide(normal.Z, responseSpeed, bodyInverseMass, constrainedInverseMass, out Fixed64 
 78        {
 1579            velocityDelta = default;
 1580            return false;
 81        }
 82
 1253383        velocityDelta = new Vector3d(x, y, z);
 1253384        return true;
 85    }
 86
 87    internal static bool TryResolveVelocityDelta(
 88        Vector2d normal,
 89        Fixed64 responseSpeed,
 90        Fixed64 bodyInverseMass,
 91        Fixed64 constrainedInverseMass,
 92        out Vector2d velocityDelta)
 93    {
 37594        if (bodyInverseMass == Fixed64.Zero)
 95        {
 6196            velocityDelta = default;
 6197            return true;
 98        }
 99
 314100        if (!Fixed64.TryMultiplyDivide(normal.X, responseSpeed, bodyInverseMass, constrainedInverseMass, out Fixed64 x)
 314101            || !Fixed64.TryMultiplyDivide(normal.Y, responseSpeed, bodyInverseMass, constrainedInverseMass, out Fixed64 
 102        {
 14103            velocityDelta = default;
 14104            return false;
 105        }
 106
 300107        velocityDelta = new Vector2d(x, y);
 300108        return true;
 109    }
 110
 111    internal static bool TryResolveSourceNormal(
 112        Vector3d normalForSource,
 113        Vector3d sourceDisplacement,
 114        out Vector3d normal)
 115    {
 14116        if (normalForSource.MagnitudeSquared > Fixed64.Epsilon)
 117        {
 12118            normal = normalForSource.Normalized;
 12119            return true;
 120        }
 121
 2122        if (sourceDisplacement.MagnitudeSquared > Fixed64.Epsilon)
 123        {
 1124            normal = -sourceDisplacement.Normalized;
 1125            return true;
 126        }
 127
 1128        normal = Vector3d.Zero;
 1129        return false;
 130    }
 131
 132    internal static bool TryResolveSourceNormal(
 133        Vector2d normalForSource,
 134        Vector2d sourceDisplacement,
 135        out Vector2d normal)
 136    {
 12137        if (normalForSource.MagnitudeSquared > Fixed64.Epsilon)
 138        {
 10139            normal = normalForSource.Normalized;
 10140            return true;
 141        }
 142
 2143        if (sourceDisplacement.MagnitudeSquared > Fixed64.Epsilon)
 144        {
 1145            normal = -sourceDisplacement.Normalized;
 1146            return true;
 147        }
 148
 1149        normal = Vector2d.Zero;
 1150        return false;
 151    }
 152
 153    internal static bool TryResolveImpactNormal(Vector3d normalForSource, out Vector3d normal)
 154    {
 76155        if (normalForSource.MagnitudeSquared > Fixed64.Epsilon)
 156        {
 72157            normal = normalForSource.Normalized;
 72158            return true;
 159        }
 160
 4161        normal = Vector3d.Zero;
 4162        return false;
 163    }
 164
 165    internal static bool TryResolveImpactNormal(Vector2d normalForSource, out Vector2d normal)
 166    {
 81167        if (normalForSource.MagnitudeSquared > Fixed64.Epsilon)
 168        {
 67169            normal = normalForSource.Normalized;
 67170            return true;
 171        }
 172
 14173        normal = Vector2d.Zero;
 14174        return false;
 175    }
 176}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Continuous/ContinuousCollisionMath.cs

#LineLine coverage
 1//=======================================================================
 2// ContinuousCollisionMath.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 System.Runtime.CompilerServices;
 11
 12namespace Gravitas.CollisionHandling;
 13
 14internal static class ContinuousCollisionMath
 15{
 16    public const int RotationalIntervalMaxDepth = 12;
 17    public const int RotationalIntervalNodeBudget = 64;
 18
 19    public enum IntervalSearchStatus : byte
 20    {
 21        CertifiedNoHit,
 22        ExactHit,
 23        Unresolved,
 24    }
 25
 26    // Pose reconstruction uses normalized fixed-point rotations. Cover both
 27    // absolute operation rounding and its radius-scaled positional effect;
 28    // failure to represent either term keeps the interval unresolved.
 129    private static readonly Fixed64 RotationalMotionUncertainty = Fixed64.Epsilon * 64;
 130    private static readonly Fixed64 RotationalRelativeUncertainty =
 131        FixedMath.CanonicalSinCosErrorBound * 16;
 132    private static readonly Fixed64 ClosestFeatureRelativeUncertainty =
 133        Fixed64.MinIncrement * 128;
 34
 35    public readonly struct RotationalInterval
 36    {
 37        public RotationalInterval(Fixed64 lowerTime, Fixed64 upperTime, int depth)
 38        {
 1042439            LowerTime = lowerTime;
 1042440            UpperTime = upperTime;
 1042441            Depth = depth;
 1042442        }
 43
 44        public Fixed64 LowerTime { get; }
 45
 46        public Fixed64 UpperTime { get; }
 47
 48        public int Depth { get; }
 49    }
 50
 51    public static bool TryResolveRotationalSearchLimit(
 52        RotationalInterval interval,
 53        int processedNodeCount,
 54        bool hasWitness,
 55        Fixed64 witnessTime,
 56        out Fixed64 safeTime,
 57        out Fixed64 contactTime,
 58        out bool retainsWitness)
 59    {
 697360        bool nodeBudgetExhausted = processedNodeCount >= RotationalIntervalNodeBudget;
 697361        if (!nodeBudgetExhausted && interval.Depth < RotationalIntervalMaxDepth)
 62        {
 649163            safeTime = default;
 649164            contactTime = default;
 649165            retainsWitness = false;
 649166            return false;
 67        }
 68
 48269        retainsWitness = hasWitness;
 48270        contactTime = hasWitness ? witnessTime : interval.LowerTime;
 71        // Max depth defines the accepted temporal resolution, so its same-target
 72        // witness may resolve this leaf only when it brackets the leaf itself. A
 73        // global witness from a later interval cannot certify the unresolved gap.
 74        // Hard node-budget exhaustion has no convergence guarantee either.
 48275        bool witnessIsBracketed = hasWitness
 48276            && witnessTime >= interval.LowerTime
 48277            && witnessTime <= interval.UpperTime;
 48278        safeTime = witnessIsBracketed && !nodeBudgetExhausted
 48279            ? witnessTime
 48280            : interval.LowerTime;
 48281        return true;
 82    }
 83
 84    public static bool ShouldContinueRotationalArbiter(
 85        Vector2d displacement,
 86        Fixed64 angularDistance) =>
 387        angularDistance > Fixed64.Epsilon
 388        || displacement.MagnitudeSquared > Fixed64.Epsilon;
 89
 90    public static bool TryResolveRotationalIntervalMotionBound(
 91        Vector2d displacement,
 92        Fixed64 angularDistance,
 93        Fixed64 pivotRadius,
 94        Fixed64 intervalSpan,
 95        out Fixed64 motionBound)
 96    {
 712497        Vector2d halfIntervalDisplacement = displacement * (intervalSpan * Fixed64.Half);
 712498        bool linearResolved = Vector2d.TryGetMagnitude(
 712499            halfIntervalDisplacement,
 7124100            out Fixed64 linearMotion);
 7124101        bool angularResolved = Fixed64.TryMultiplyDivide(
 7124102            angularDistance,
 7124103            pivotRadius,
 7124104            intervalSpan,
 7124105            Fixed64.Two,
 7124106            out Fixed64 angularMotion);
 7124107        bool poseResolved = Fixed64.TryMultiplyDivide(
 7124108            pivotRadius,
 7124109            RotationalRelativeUncertainty,
 7124110            Fixed64.One,
 7124111            out Fixed64 poseUncertainty);
 7124112        bool combined = Fixed64.TryAdd(linearMotion, angularMotion, out motionBound)
 7124113            & Fixed64.TryAdd(motionBound, poseUncertainty, out motionBound)
 7124114            & Fixed64.TryAdd(motionBound, RotationalMotionUncertainty, out motionBound);
 7124115        if (!(linearResolved & angularResolved & poseResolved & combined))
 116        {
 2117            motionBound = default;
 2118            return false;
 119        }
 120
 7122121        return true;
 122    }
 123
 124    public static bool TryResolveRotationalIntervalMotionBound(
 125        Vector3d displacement,
 126        Fixed64 angularDistance,
 127        Fixed64 pivotRadius,
 128        Fixed64 intervalSpan,
 129        out Fixed64 motionBound)
 130    {
 9567131        Vector3d halfIntervalDisplacement = displacement * (intervalSpan * Fixed64.Half);
 9567132        bool linearResolved = Vector3d.TryGetMagnitude(
 9567133            halfIntervalDisplacement,
 9567134            out Fixed64 linearMotion);
 9567135        bool angularResolved = Fixed64.TryMultiplyDivide(
 9567136            angularDistance,
 9567137            pivotRadius,
 9567138            intervalSpan,
 9567139            Fixed64.Two,
 9567140            out Fixed64 angularMotion);
 9567141        bool poseResolved = Fixed64.TryMultiplyDivide(
 9567142            pivotRadius,
 9567143            RotationalRelativeUncertainty,
 9567144            Fixed64.One,
 9567145            out Fixed64 poseUncertainty);
 9567146        bool combined = Fixed64.TryAdd(linearMotion, angularMotion, out motionBound)
 9567147            & Fixed64.TryAdd(motionBound, poseUncertainty, out motionBound)
 9567148            & Fixed64.TryAdd(motionBound, RotationalMotionUncertainty, out motionBound);
 9567149        if (!(linearResolved & angularResolved & poseResolved & combined))
 150        {
 2151            motionBound = default;
 2152            return false;
 153        }
 154
 9565155        return true;
 156    }
 157
 158    public static bool AreBoundsSeparatedByMoreThan(
 159        FixedBoundArea source,
 160        FixedBoundArea target,
 161        Fixed64 motionBound) =>
 312162        IsAxisGapGreaterThan(source.Min.X, source.Max.X, target.Min.X, target.Max.X, motionBound)
 312163        | IsAxisGapGreaterThan(source.Min.Y, source.Max.Y, target.Min.Y, target.Max.Y, motionBound);
 164
 165    public static bool AreBoundsSeparatedByMoreThan(
 166        FixedBoundBox source,
 167        FixedBoundBox target,
 168        Fixed64 motionBound) =>
 922169        IsAxisGapGreaterThan(source.Min.X, source.Max.X, target.Min.X, target.Max.X, motionBound)
 922170        | IsAxisGapGreaterThan(source.Min.Y, source.Max.Y, target.Min.Y, target.Max.Y, motionBound)
 922171        | IsAxisGapGreaterThan(source.Min.Z, source.Max.Z, target.Min.Z, target.Max.Z, motionBound);
 172
 173    public static bool TrySubtractClosestFeatureUncertainty(
 174        Fixed64 separationGap,
 175        Fixed64 characteristicScale,
 176        out Fixed64 conservativeGap)
 177    {
 6759178        bool inputValid = separationGap > Fixed64.Zero
 6759179            & characteristicScale >= Fixed64.Zero;
 6759180        bool scaled = Fixed64.TryMultiplyDivide(
 6759181            characteristicScale,
 6759182            ClosestFeatureRelativeUncertainty,
 6759183            Fixed64.One,
 6759184            out Fixed64 scaledUncertainty);
 6759185        bool combined = Fixed64.TryAdd(
 6759186            scaledUncertainty,
 6759187            RotationalMotionUncertainty,
 6759188            out Fixed64 uncertainty);
 6759189        bool subtracted = Fixed64.TrySubtract(
 6759190            separationGap,
 6759191            uncertainty,
 6759192            out conservativeGap);
 6759193        if (!(inputValid & scaled & combined & subtracted))
 194        {
 1500195            conservativeGap = default;
 1500196            return false;
 197        }
 198
 5259199        return conservativeGap > Fixed64.Zero;
 200    }
 201
 202    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 203    private static bool IsAxisGapGreaterThan(
 204        Fixed64 sourceMin,
 205        Fixed64 sourceMax,
 206        Fixed64 targetMin,
 207        Fixed64 targetMax,
 208        Fixed64 motionBound)
 209    {
 3390210        if (sourceMax < targetMin)
 573211            return !Fixed64.TrySubtract(targetMin, sourceMax, out Fixed64 gap)
 573212                | gap > motionBound;
 213
 2817214        return targetMax < sourceMin
 2817215            & (!Fixed64.TrySubtract(sourceMin, targetMax, out Fixed64 reverseGap)
 2817216                | reverseGap > motionBound);
 217    }
 218
 219    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 220    public static bool IsWithinProxyRadius(
 221        Vector3d displacement,
 222        Fixed64 displacementMagnitudeSquared,
 223        Fixed64 proxyRadius)
 224    {
 16225        Fixed64 proxyRadiusSquared = proxyRadius * proxyRadius;
 16226        if (displacementMagnitudeSquared != Fixed64.MaxValue || proxyRadiusSquared != Fixed64.MaxValue)
 13227            return displacementMagnitudeSquared <= proxyRadiusSquared;
 228
 3229        return Vector3d.TryGetMagnitude(displacement, out Fixed64 displacementMagnitude)
 3230            && displacementMagnitude <= proxyRadius;
 231    }
 232
 233    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 234    public static bool IsWithinProxyRadius(
 235        Vector2d displacement,
 236        Fixed64 displacementMagnitudeSquared,
 237        Fixed64 proxyRadius)
 238    {
 13239        Fixed64 proxyRadiusSquared = proxyRadius * proxyRadius;
 13240        if (displacementMagnitudeSquared != Fixed64.MaxValue || proxyRadiusSquared != Fixed64.MaxValue)
 9241            return displacementMagnitudeSquared <= proxyRadiusSquared;
 242
 4243        return Vector2d.TryGetMagnitude(displacement, out Fixed64 displacementMagnitude)
 4244            && displacementMagnitude <= proxyRadius;
 245    }
 246
 247    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 248    public static Vector3d ResolveContactPointOnTarget(
 249        Vector3d sourceCenter,
 250        Vector3d targetCenter,
 251        Vector3d normalForSource,
 252        Fixed64 targetRadius)
 253    {
 5254        if (normalForSource.MagnitudeSquared > Fixed64.Epsilon)
 3255            return targetCenter + normalForSource * targetRadius;
 256
 2257        Vector3d fallback = sourceCenter - targetCenter;
 2258        return fallback.MagnitudeSquared > Fixed64.Epsilon
 2259            ? targetCenter + fallback.Normalized * targetRadius
 2260            : targetCenter;
 261    }
 262
 263    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 264    public static bool ShouldReplaceContinuousCollisionHit(
 265        Fixed64 candidateSafeTime,
 266        int candidateTargetId,
 267        bool hasCurrent,
 268        Fixed64 currentSafeTime,
 269        int currentTargetId)
 270    {
 455271        if (!hasCurrent)
 439272            return true;
 273
 16274        int timeCompare = candidateSafeTime.CompareTo(currentSafeTime);
 16275        if (timeCompare != 0)
 9276            return timeCompare < 0;
 277
 7278        return candidateTargetId < currentTargetId;
 279    }
 280
 281    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 282    public static void ClipTranslationalTrajectoryInterval(
 283        Fixed64 queryStart,
 284        Fixed64 segmentStart,
 285        Fixed64 segmentEnd,
 286        out Fixed64 overlapStart,
 287        out Fixed64 overlapEnd,
 288        out Fixed64 sourceStartTime,
 289        out Fixed64 sourceEndTime)
 290    {
 522291        overlapStart = FixedMath.Max(queryStart, segmentStart);
 522292        overlapEnd = segmentEnd;
 522293        Fixed64 querySpan = Fixed64.One - queryStart;
 522294        sourceStartTime = FixedMath.Clamp01((overlapStart - queryStart) / querySpan);
 522295        sourceEndTime = FixedMath.Clamp01((overlapEnd - queryStart) / querySpan);
 522296    }
 297
 298    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 299    public static bool IsSupersededTranslationalBoundaryHit(
 300        bool hitAtSegmentEnd,
 301        Fixed64 overlapEnd,
 302        int segmentIndex,
 303        int segmentCount,
 304        Fixed64 successorStart) =>
 524305        hitAtSegmentEnd
 524306        && overlapEnd < Fixed64.One
 524307        && segmentIndex + 1 < segmentCount
 524308        && successorStart == overlapEnd;
 309
 310    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 311    public static bool TryNormalizeTranslationalClosingSpeed(
 312        Fixed64 localClosingSpeed,
 313        Fixed64 sourceIntervalSpan,
 314        out Fixed64 closingSpeed)
 315    {
 409316        if (localClosingSpeed <= Fixed64.Epsilon
 409317            || sourceIntervalSpan <= Fixed64.Zero)
 318        {
 5319            closingSpeed = Fixed64.Zero;
 5320            return false;
 321        }
 322
 404323        closingSpeed = localClosingSpeed / sourceIntervalSpan;
 404324        return true;
 325    }
 326
 327    public static bool TryGetRelativeSphereOverlapDistanceInterval(
 328        Vector3d sourceStart,
 329        Vector3d sourceDisplacement,
 330        Fixed64 sourceRadius,
 331        Vector3d targetStart,
 332        Vector3d targetDisplacement,
 333        Fixed64 targetRadius,
 334        out Fixed64 entryDistance,
 335        out Fixed64 exitDistance,
 336        out Vector3d relativeDisplacement,
 337        out Fixed64 relativeLength,
 338        out Vector3d normalForSource,
 339        out Fixed64 closingSpeed)
 340    {
 249341        entryDistance = Fixed64.Zero;
 249342        exitDistance = Fixed64.Zero;
 249343        relativeDisplacement = Vector3d.Zero;
 249344        normalForSource = Vector3d.Zero;
 249345        closingSpeed = Fixed64.Zero;
 346
 249347        relativeDisplacement = ContinuousCollisionSweepRange.ValidateRelativeDisplacement(
 249348            sourceDisplacement,
 249349            targetDisplacement,
 249350            out relativeLength);
 248351        Vector3d sourceEnd = GetSweepEnd(sourceStart, sourceDisplacement);
 246352        Vector3d targetEnd = GetSweepEnd(targetStart, targetDisplacement);
 246353        if (relativeDisplacement.MagnitudeSquared <= Fixed64.Epsilon
 246354            || !HasUsableCombinedRadius(sourceRadius, targetRadius))
 355        {
 9356            return false;
 357        }
 358
 237359        if (!WideFiniteAxisIntersection.TryGetSphereDirectionDistanceInterval(
 237360                sourceStart,
 237361                relativeDisplacement,
 237362                new FixedBoundSphere(targetStart, targetRadius),
 237363                sourceRadius,
 237364                relativeLength,
 237365                out entryDistance,
 237366                out exitDistance))
 367        {
 13368            return false;
 369        }
 370
 224371        Vector3d sourceImpact = new FixedSegment(sourceStart, sourceEnd)
 224372            .GetPointAtDistance(entryDistance, relativeLength);
 224373        Vector3d targetImpact = new FixedSegment(targetStart, targetEnd)
 224374            .GetPointAtDistance(entryDistance, relativeLength);
 224375        normalForSource = ResolveNormal(targetImpact, sourceImpact, relativeDisplacement);
 224376        closingSpeed = -Vector3d.Dot(relativeDisplacement, normalForSource);
 224377        return true;
 378    }
 379
 380    public static bool TryGetRelativeCircleOverlapDistanceInterval(
 381        Vector2d sourceStart,
 382        Vector2d sourceDisplacement,
 383        Fixed64 sourceRadius,
 384        Vector2d targetStart,
 385        Vector2d targetDisplacement,
 386        Fixed64 targetRadius,
 387        out Fixed64 entryDistance,
 388        out Fixed64 exitDistance,
 389        out Vector2d relativeDisplacement,
 390        out Fixed64 relativeLength,
 391        out Vector2d normalForSource,
 392        out Fixed64 closingSpeed)
 393    {
 310394        entryDistance = Fixed64.Zero;
 310395        exitDistance = Fixed64.Zero;
 310396        relativeDisplacement = Vector2d.Zero;
 310397        normalForSource = Vector2d.Zero;
 310398        closingSpeed = Fixed64.Zero;
 399
 310400        relativeDisplacement = ContinuousCollisionSweepRange.ValidateRelativeDisplacement(
 310401            sourceDisplacement,
 310402            targetDisplacement,
 310403            out relativeLength);
 309404        Vector2d sourceEnd = GetSweepEnd(sourceStart, sourceDisplacement);
 307405        Vector2d targetEnd = GetSweepEnd(targetStart, targetDisplacement);
 307406        if (relativeDisplacement.MagnitudeSquared <= Fixed64.Epsilon
 307407            || !HasUsableCombinedRadius(sourceRadius, targetRadius))
 408        {
 5409            return false;
 410        }
 411
 302412        if (!WideFiniteAxisIntersection.TryGetCircleDirectionDistanceInterval(
 302413                sourceStart,
 302414                relativeDisplacement,
 302415                new FixedBoundCircle(targetStart, targetRadius),
 302416                sourceRadius,
 302417                relativeLength,
 302418                out entryDistance,
 302419                out exitDistance))
 420        {
 17421            return false;
 422        }
 423
 285424        Vector2d sourceImpact = new FixedSegment2d(sourceStart, sourceEnd)
 285425            .GetPointAtDistance(entryDistance, relativeLength);
 285426        Vector2d targetImpact = new FixedSegment2d(targetStart, targetEnd)
 285427            .GetPointAtDistance(entryDistance, relativeLength);
 285428        normalForSource = ResolveNormal(targetImpact, sourceImpact, relativeDisplacement);
 285429        closingSpeed = -Vector2d.Dot(relativeDisplacement, normalForSource);
 285430        return true;
 431    }
 432
 433    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 434    private static bool HasUsableCombinedRadius(Fixed64 sourceRadius, Fixed64 targetRadius)
 435    {
 540436        if (sourceRadius > Fixed64.Epsilon || targetRadius > Fixed64.Epsilon)
 539437            return true;
 438
 1439        return sourceRadius + targetRadius > Fixed64.Epsilon;
 440    }
 441
 442    private static Vector3d GetSweepEnd(
 443        Vector3d start,
 444        Vector3d displacement)
 445    {
 494446        if (!Vector3d.TryAdd(start, displacement, out Vector3d end))
 2447            throw new System.ArgumentOutOfRangeException(nameof(displacement), "Continuous collision endpoint is outside
 448
 492449        return end;
 450    }
 451
 452    private static Vector2d GetSweepEnd(
 453        Vector2d start,
 454        Vector2d displacement)
 455    {
 616456        if (!Vector2d.TryAdd(start, displacement, out Vector2d end))
 2457            throw new System.ArgumentOutOfRangeException(nameof(displacement), "Continuous collision endpoint is outside
 458
 614459        return end;
 460    }
 461
 462    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 463    private static Vector3d ResolveNormal(
 464        Vector3d targetPosition,
 465        Vector3d sourcePosition,
 466        Vector3d relativeDisplacement)
 467    {
 224468        Vector3d normal = Vector3d.GetDirection(targetPosition, sourcePosition);
 224469        return normal != Vector3d.Zero ? normal : -relativeDisplacement.Normalized;
 470    }
 471
 472    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 473    private static Vector2d ResolveNormal(
 474        Vector2d targetPosition,
 475        Vector2d sourcePosition,
 476        Vector2d relativeDisplacement)
 477    {
 285478        Vector2d normal = Vector2d.GetDirection(targetPosition, sourcePosition);
 285479        return normal != Vector2d.Zero ? normal : -relativeDisplacement.Normalized;
 480    }
 481}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Continuous/ContinuousCollisionMotionSegment2D.cs

#LineLine coverage
 1//=======================================================================
 2// ContinuousCollisionMotionSegment2D.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;
 9
 10namespace Gravitas.CollisionHandling;
 11
 12/// <summary>
 13/// One immutable, normalized-time piece of a 2D body's prepared CCD trajectory.
 14/// </summary>
 15internal readonly struct ContinuousCollisionMotionSegment2D
 16{
 17    public ContinuousCollisionMotionSegment2D(
 18        Fixed64 startFraction,
 19        Fixed64 endFraction,
 20        Vector2d startPosition,
 21        Vector2d endPosition,
 22        Vector2d displacement,
 23        Fixed64 startRotation,
 24        Fixed64 angularDelta,
 25        Fixed64 angularVelocity)
 26    {
 320127        StartFraction = startFraction;
 320128        EndFraction = endFraction;
 320129        StartPosition = startPosition;
 320130        EndPosition = endPosition;
 320131        Displacement = displacement;
 320132        StartRotation = startRotation;
 320133        AngularDelta = angularDelta;
 320134        AngularVelocity = angularVelocity;
 320135    }
 36
 37    public Fixed64 StartFraction { get; }
 38    public Fixed64 EndFraction { get; }
 39    public Vector2d StartPosition { get; }
 40    public Vector2d EndPosition { get; }
 41    public Vector2d Displacement { get; }
 42    public Fixed64 StartRotation { get; }
 43    public Fixed64 AngularDelta { get; }
 44    public Fixed64 AngularVelocity { get; }
 451745    public Fixed64 AngularDistance => AngularDelta.Abs();
 46
 47    public Vector2d SamplePosition(Fixed64 frameFraction) =>
 398148        Vector2d.Lerp(StartPosition, EndPosition, ResolveLocalFraction(frameFraction));
 49
 50    public Fixed64 SampleRotation(Fixed64 frameFraction) =>
 387051        StartRotation + AngularDelta * ResolveLocalFraction(frameFraction);
 52
 53    public bool TryResolveMotionBound(
 54        Fixed64 lowerFraction,
 55        Fixed64 upperFraction,
 56        Fixed64 pivotRadius,
 57        out Fixed64 motionBound)
 58    {
 313759        Fixed64 segmentSpan = EndFraction - StartFraction;
 313760        Fixed64 localSpan = (upperFraction - lowerFraction) / segmentSpan;
 313761        if (!Vector2d.TrySubtract(EndPosition, StartPosition, out Vector2d exactDisplacement))
 62        {
 163            motionBound = default;
 164            return false;
 65        }
 66
 313667        return ContinuousCollisionMath.TryResolveRotationalIntervalMotionBound(
 313668            exactDisplacement,
 313669            AngularDistance,
 313670            pivotRadius,
 313671            localSpan,
 313672            out motionBound);
 73    }
 74
 75    private Fixed64 ResolveLocalFraction(Fixed64 frameFraction)
 76    {
 785177        Fixed64 span = EndFraction - StartFraction;
 785178        return FixedMath.Clamp01((frameFraction - StartFraction) / span);
 79    }
 80}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Continuous/ContinuousCollisionMotionSegment3D.cs

#LineLine coverage
 1//=======================================================================
 2// ContinuousCollisionMotionSegment3D.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;
 9
 10namespace Gravitas.CollisionHandling;
 11
 12internal enum ContinuousCollisionRotationPath3D : byte
 13{
 14    IntegratedAngularVelocity,
 15    KinematicSlerp
 16}
 17
 18/// <summary>
 19/// One immutable, normalized-time piece of a 3D body's prepared CCD trajectory.
 20/// </summary>
 21internal readonly struct ContinuousCollisionMotionSegment3D
 22{
 23    public ContinuousCollisionMotionSegment3D(
 24        Fixed64 startFraction,
 25        Fixed64 endFraction,
 26        Vector3d startPosition,
 27        Vector3d endPosition,
 28        Vector3d displacement,
 29        FixedQuaternion startRotation,
 30        Vector3d angularVelocity,
 31        FixedQuaternion targetRotation,
 32        Fixed64 angularDistance,
 33        ContinuousCollisionRotationPath3D rotationPath)
 34    {
 1456435        StartFraction = startFraction;
 1456436        EndFraction = endFraction;
 1456437        StartPosition = startPosition;
 1456438        EndPosition = endPosition;
 1456439        Displacement = displacement;
 1456440        StartRotation = startRotation;
 1456441        AngularVelocity = angularVelocity;
 1456442        TargetRotation = targetRotation;
 1456443        AngularDistance = angularDistance;
 1456444        RotationPath = rotationPath;
 1456445    }
 46
 47    public Fixed64 StartFraction { get; }
 48    public Fixed64 EndFraction { get; }
 49    public Vector3d StartPosition { get; }
 50    public Vector3d EndPosition { get; }
 51    public Vector3d Displacement { get; }
 52    public FixedQuaternion StartRotation { get; }
 53    public Vector3d AngularVelocity { get; }
 54    public FixedQuaternion TargetRotation { get; }
 55    public Fixed64 AngularDistance { get; }
 56    public ContinuousCollisionRotationPath3D RotationPath { get; }
 57
 58    public Vector3d SamplePosition(Fixed64 frameFraction)
 59    {
 451960        Fixed64 localFraction = ResolveLocalFraction(frameFraction);
 451961        return Vector3d.Lerp(StartPosition, EndPosition, localFraction);
 62    }
 63
 64    public FixedQuaternion SampleRotation(Fixed64 frameFraction, Fixed64 frameDeltaTime)
 65    {
 451266        Fixed64 localFraction = ResolveLocalFraction(frameFraction);
 451267        if (RotationPath == ContinuousCollisionRotationPath3D.KinematicSlerp)
 37168            return FixedQuaternion.Slerp(StartRotation, TargetRotation, localFraction).Normalized;
 69
 414170        Fixed64 elapsedTime = frameDeltaTime * (frameFraction - StartFraction);
 414171        FixedQuaternion angularVelocityQuaternion = new(
 414172            AngularVelocity.X,
 414173            AngularVelocity.Y,
 414174            AngularVelocity.Z,
 414175            Fixed64.Zero);
 414176        FixedQuaternion spin = angularVelocityQuaternion * StartRotation * Fixed64.Half * elapsedTime;
 414177        return (StartRotation + spin).Normalized;
 78    }
 79
 80    public bool TryResolveMotionBound(
 81        Fixed64 lowerFraction,
 82        Fixed64 upperFraction,
 83        Fixed64 pivotRadius,
 84        out Fixed64 motionBound)
 85    {
 396086        Fixed64 segmentSpan = EndFraction - StartFraction;
 396087        Fixed64 localSpan = (upperFraction - lowerFraction) / segmentSpan;
 396088        if (!Vector3d.TrySubtract(EndPosition, StartPosition, out Vector3d exactDisplacement))
 89        {
 190            motionBound = default;
 191            return false;
 92        }
 93
 395994        return ContinuousCollisionMath.TryResolveRotationalIntervalMotionBound(
 395995            exactDisplacement,
 395996            AngularDistance,
 395997            pivotRadius,
 395998            localSpan,
 395999            out motionBound);
 100    }
 101
 102    private Fixed64 ResolveLocalFraction(Fixed64 frameFraction)
 103    {
 9031104        Fixed64 span = EndFraction - StartFraction;
 9031105        return FixedMath.Clamp01((frameFraction - StartFraction) / span);
 106    }
 107}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Continuous/ContinuousCollisionSweepRange.cs

#LineLine coverage
 1//=======================================================================
 2// ContinuousCollisionSweepRange.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;
 10using System.Runtime.CompilerServices;
 11
 12namespace Gravitas.CollisionHandling;
 13
 14/// <summary>
 15/// Validates the scalar-distance contract required by continuous sweep queries.
 16/// </summary>
 17internal static class ContinuousCollisionSweepRange
 18{
 19    private const string RangeMessage =
 20        "Continuous collision motion must have a component-exact difference and a representable Euclidean length.";
 21
 22    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 23    public static Vector2d ValidateEndpoint(Vector2d start, Vector2d end, out Fixed64 length)
 24    {
 143225        if (!Vector2d.TrySubtract(end, start, out Vector2d displacement)
 143226            || !Vector2d.TryGetMagnitude(displacement, out length))
 27        {
 428            throw new ArgumentOutOfRangeException(nameof(end), RangeMessage);
 29        }
 30
 142831        return displacement;
 32    }
 33
 34    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 35    public static Vector3d ValidateEndpoint(Vector3d start, Vector3d end, out Fixed64 length)
 36    {
 2006637        if (!Vector3d.TrySubtract(end, start, out Vector3d displacement)
 2006638            || !Vector3d.TryGetMagnitude(displacement, out length))
 39        {
 440            throw new ArgumentOutOfRangeException(nameof(end), RangeMessage);
 41        }
 42
 2006243        return displacement;
 44    }
 45
 46    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 47    public static Vector2d ValidateEndpoint(
 48        Vector2d start,
 49        Vector2d end,
 50        Vector2d requestedDisplacement,
 51        out Fixed64 length)
 52    {
 58153        Vector2d displacement = ValidateEndpoint(start, end, out length);
 57954        if (displacement != requestedDisplacement)
 155            throw new ArgumentOutOfRangeException(nameof(end), RangeMessage);
 56
 57857        return displacement;
 58    }
 59
 60    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 61    public static Vector3d ValidateEndpoint(
 62        Vector3d start,
 63        Vector3d end,
 64        Vector3d requestedDisplacement,
 65        out Fixed64 length)
 66    {
 80167        Vector3d displacement = ValidateEndpoint(start, end, out length);
 80068        if (displacement != requestedDisplacement)
 169            throw new ArgumentOutOfRangeException(nameof(end), RangeMessage);
 70
 79971        return displacement;
 72    }
 73
 74    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 75    public static Vector2d ValidateRelativeDisplacement(
 76        Vector2d sourceDisplacement,
 77        Vector2d targetDisplacement,
 78        out Fixed64 length)
 79    {
 31380        if (!Vector2d.TrySubtract(sourceDisplacement, targetDisplacement, out Vector2d relativeDisplacement)
 31381            || !Vector2d.TryGetMagnitude(relativeDisplacement, out length))
 82        {
 383            throw new ArgumentOutOfRangeException(nameof(sourceDisplacement), RangeMessage);
 84        }
 85
 31086        return relativeDisplacement;
 87    }
 88
 89    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 90    public static Vector3d ValidateRelativeDisplacement(
 91        Vector3d sourceDisplacement,
 92        Vector3d targetDisplacement,
 93        out Fixed64 length)
 94    {
 32895        if (!Vector3d.TrySubtract(sourceDisplacement, targetDisplacement, out Vector3d relativeDisplacement)
 32896            || !Vector3d.TryGetMagnitude(relativeDisplacement, out length))
 97        {
 398            throw new ArgumentOutOfRangeException(nameof(sourceDisplacement), RangeMessage);
 99        }
 100
 325101        return relativeDisplacement;
 102    }
 103}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Continuous/ContinuousCollisionTargetPolicy.cs

#LineLine coverage
 1//=======================================================================
 2// ContinuousCollisionTargetPolicy.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
 8namespace Gravitas.CollisionHandling;
 9
 10internal static class ContinuousCollisionTargetPolicy
 11{
 12    internal static bool AllowsIndexed3DTarget(
 13        bool isSelf,
 14        bool active,
 15        bool dynamicBody,
 16        bool kinematic,
 17        bool movingKinematic,
 18        bool trigger,
 19        bool physicalPairRequired) =>
 674620        !isSelf
 674621        && active
 674622        && (kinematic ? movingKinematic : dynamicBody)
 674623        && !trigger
 674624        && physicalPairRequired;
 25
 26    internal static bool AllowsIndexed2DTarget(
 27        bool isSelf,
 28        bool active,
 29        bool dynamicBody,
 30        bool kinematic,
 31        bool movingKinematic,
 32        bool trigger,
 33        bool physicalPairRequired) =>
 45234        !isSelf
 45235        && active
 45236        && (kinematic ? movingKinematic : dynamicBody)
 45237        && !trigger
 45238        && physicalPairRequired;
 39
 40    internal static bool AllowsMixedIndexedTarget(
 41        bool active,
 42        bool dynamicBody,
 43        bool kinematic,
 44        bool movingKinematic,
 45        bool trigger,
 46        bool mixedPairRequired) =>
 9247        active
 9248        && (kinematic ? movingKinematic : dynamicBody)
 9249        && !trigger
 9250        && mixedPairRequired;
 51
 52    internal static bool AllowsStaticOrKinematic3DTarget(
 53        bool isSelf,
 54        bool ignored,
 55        bool trigger,
 56        bool physicalPairRequired,
 57        bool isStatic,
 58        bool bodyKinematic) =>
 639559        !isSelf
 639560        && !ignored
 639561        && !trigger
 639562        && physicalPairRequired
 639563        && (isStatic || bodyKinematic);
 64
 65    internal static bool AllowsStaticOrKinematic2DTarget(
 66        bool isSelf,
 67        bool ignored,
 68        bool trigger,
 69        bool physicalPairRequired,
 70        bool isStatic,
 71        bool bodyKinematic) =>
 28472        !isSelf
 28473        && !ignored
 28474        && !trigger
 28475        && physicalPairRequired
 28476        && (isStatic || bodyKinematic);
 77
 78    internal static bool AllowsMixedStaticOrKinematicTarget(
 79        bool ignored,
 80        bool trigger,
 81        bool mixedPairRequired,
 82        bool isStatic,
 83        bool bodyKinematic) =>
 1484        !ignored
 1485        && !trigger
 1486        && mixedPairRequired
 1487        && (isStatic || bodyKinematic);
 88}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Continuous/DynamicCcdCandidateIndex.cs

#LineLine coverage
 1//=======================================================================
 2// DynamicCcdCandidateIndex.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 SwiftCollections;
 10using SwiftCollections.Query;
 11using System.Runtime.CompilerServices;
 12
 13namespace Gravitas.CollisionHandling;
 14
 15internal sealed class DynamicCcdCandidateIndex
 16{
 17    private readonly SwiftList<Entry> _entries;
 18    private readonly SwiftDictionary<int, int>? _entryIndices;
 19    private Fixed64 _maxExtentX;
 20    private int _maxExtentXCount;
 21    private int _unrepresentableExtentXCount;
 2034022    private bool _isSorted = true;
 23
 2034024    public DynamicCcdCandidateIndex(int capacity = 0, bool supportsUpdates = false)
 25    {
 2034026        _entries = capacity > 0 ? new SwiftList<Entry>(capacity) : new SwiftList<Entry>();
 2034027        _entryIndices = !supportsUpdates
 2034028            ? null
 2034029            : capacity > 0
 2034030                ? new SwiftDictionary<int, int>(capacity)
 2034031                : new SwiftDictionary<int, int>();
 2034032    }
 33
 89734    public int Count => _entries.Count;
 35
 36    public void Clear()
 37    {
 697638        _entries.FastClear();
 697639        _entryIndices?.Clear();
 697640        _maxExtentX = Fixed64.Zero;
 697641        _maxExtentXCount = 0;
 697642        _unrepresentableExtentXCount = 0;
 697643        _isSorted = true;
 697644    }
 45
 46    public void Add(int dynamicId, FixedBoundVolume bounds)
 47    {
 1450248        _entryIndices?.Add(dynamicId, _entries.Count);
 1450249        _entries.Add(new Entry(dynamicId, bounds));
 1450250        IncludeExtent(bounds.Min.X, bounds.Max.X);
 51
 1450252        _isSorted = false;
 1450253    }
 54
 55    public void AddOrUpdate(int dynamicId, FixedBoundVolume bounds)
 56    {
 70457        SwiftDictionary<int, int>? entryIndices = _entryIndices;
 70458        SwiftThrowHelper.ThrowIfTrue(
 70459            entryIndices == null,
 70460            nameof(DynamicCcdCandidateIndex),
 70461            "Candidate index was not configured for updates.");
 70462        if (entryIndices.TryGetValue(dynamicId, out int index))
 63        {
 28164            Entry previous = _entries[index];
 28165            _entries[index] = new Entry(dynamicId, bounds);
 28166            bool remainsSorted = _isSorted && IsOrderedAt(index);
 28167            if (!DynamicCcdExtentMetadata.IsEquivalent(
 28168                    previous.MinX,
 28169                    previous.MaxX,
 28170                    bounds.Min.X,
 28171                    bounds.Max.X))
 72            {
 6573                if (RemoveExtent(previous.MinX, previous.MaxX))
 4774                    RebuildExtents();
 75                else
 1876                    IncludeExtent(bounds.Min.X, bounds.Max.X);
 77            }
 78
 28179            _isSorted = remainsSorted;
 28180            return;
 81        }
 82
 42383        Add(dynamicId, bounds);
 42384    }
 85
 86    public bool Remove(int dynamicId)
 87    {
 1188        SwiftDictionary<int, int>? entryIndices = _entryIndices;
 1189        if (entryIndices == null || !entryIndices.TryGetValue(dynamicId, out int index))
 590            return false;
 91
 692        Entry removed = _entries[index];
 693        int lastIndex = _entries.Count - 1;
 694        if (index != lastIndex)
 95        {
 396            Entry moved = _entries[lastIndex];
 397            _entries[index] = moved;
 398            entryIndices[moved.DynamicId] = index;
 99        }
 100
 6101        _entries.RemoveAt(lastIndex);
 6102        entryIndices.Remove(dynamicId);
 6103        if (RemoveExtent(removed.MinX, removed.MaxX))
 3104            RebuildExtents();
 6105        _isSorted = false;
 6106        return true;
 107    }
 108
 109    public void Sort()
 110    {
 17819111        if (!_isSorted && _entries.Count > 1)
 1488112            HeapSort();
 113
 17819114        _isSorted = true;
 17819115    }
 116
 117    public void Query(FixedBoundVolume queryBounds, SwiftList<int> results)
 118    {
 28749119        results.FastClear();
 28749120        if (_entries.Count == 0)
 13643121            return;
 122
 15106123        Sort();
 15106124        Fixed64 scanMinX = _unrepresentableExtentXCount > 0
 15106125            || !Fixed64.TrySubtract(queryBounds.Min.X, _maxExtentX, out Fixed64 representableScanMinX)
 15106126                ? Fixed64.MinValue
 15106127                : representableScanMinX;
 15106128        int index = FindFirstCandidateIndex(scanMinX);
 415544129        for (; index < _entries.Count; index++)
 130        {
 211780131            Entry entry = _entries[index];
 211780132            if (entry.MinX > queryBounds.Max.X)
 133                break;
 134
 200219135            if (entry.Intersects(queryBounds))
 15789136                results.Add(entry.DynamicId);
 137        }
 15106138    }
 139
 140    public static FixedBoundVolume CreateSweptSphereBounds(Vector3d start, Vector3d displacement, Fixed64 radius)
 13998141        => CreateBoundsBetween(start, start + displacement, Vector3d.One * radius);
 142
 143    public static FixedBoundVolume CreateSweptBounds(
 144        Vector3d start,
 145        Vector3d displacement,
 146        Vector3d extents) =>
 1147        CreateBoundsBetween(start, start + displacement, extents);
 148
 149    public static FixedBoundVolume CreateBoundsBetween(
 150        Vector3d start,
 151        Vector3d end,
 152        Vector3d extents)
 153    {
 27832154        return new FixedBoundVolume(Vector3d.Min(start, end) - extents, Vector3d.Max(start, end) + extents);
 155    }
 156
 157    private void IncludeExtent(Fixed64 minX, Fixed64 maxX)
 158    {
 14601159        if (!Fixed64.TrySubtract(maxX, minX, out Fixed64 extentX))
 160        {
 22161            _unrepresentableExtentXCount++;
 22162            return;
 163        }
 164
 14579165        if (extentX > _maxExtentX)
 166        {
 5332167            _maxExtentX = extentX;
 5332168            _maxExtentXCount = 1;
 169        }
 9247170        else if (extentX == _maxExtentX)
 171        {
 7249172            _maxExtentXCount++;
 173        }
 9247174    }
 175
 176    private bool RemoveExtent(Fixed64 minX, Fixed64 maxX)
 177    {
 71178        if (!Fixed64.TrySubtract(maxX, minX, out Fixed64 extentX))
 179        {
 2180            _unrepresentableExtentXCount--;
 2181            return false;
 182        }
 183
 69184        return extentX == _maxExtentX && --_maxExtentXCount == 0;
 185    }
 186
 187    private void RebuildExtents()
 188    {
 50189        _maxExtentX = Fixed64.Zero;
 50190        _maxExtentXCount = 0;
 50191        _unrepresentableExtentXCount = 0;
 262192        for (int i = 0; i < _entries.Count; i++)
 81193            IncludeExtent(_entries[i].MinX, _entries[i].MaxX);
 50194    }
 195
 196    private int FindFirstCandidateIndex(Fixed64 minX)
 197    {
 15106198        int low = 0;
 15106199        int high = _entries.Count;
 117733200        while (low < high)
 201        {
 102627202            int middle = low + ((high - low) >> 1);
 102627203            if (_entries[middle].MinX < minX)
 67607204                low = middle + 1;
 205            else
 35020206                high = middle;
 207        }
 208
 15106209        return low;
 210    }
 211
 212    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 213    private bool IsOrderedAt(int index)
 214    {
 251215        Entry entry = _entries[index];
 251216        return (index == 0 || Compare(_entries[index - 1], entry) <= 0)
 251217            && (index == _entries.Count - 1 || Compare(entry, _entries[index + 1]) <= 0);
 218    }
 219
 220    private void HeapSort()
 221    {
 1488222        int count = _entries.Count;
 15620223        for (int start = (count >> 1) - 1; start >= 0; start--)
 6322224            SiftDown(start, count);
 225
 26078226        for (int end = count - 1; end > 0; end--)
 227        {
 11551228            Swap(0, end);
 11551229            SiftDown(0, end);
 230        }
 1488231    }
 232
 233    private void SiftDown(int root, int count)
 234    {
 48295235        while (true)
 236        {
 66168237            int child = (root << 1) + 1;
 66168238            if (child >= count)
 15426239                return;
 240
 50742241            int swapIndex = root;
 50742242            if (Compare(_entries[swapIndex], _entries[child]) < 0)
 46572243                swapIndex = child;
 244
 50742245            int right = child + 1;
 50742246            if (right < count && Compare(_entries[swapIndex], _entries[right]) < 0)
 22306247                swapIndex = right;
 248
 50742249            if (swapIndex == root)
 2447250                return;
 251
 48295252            Swap(root, swapIndex);
 48295253            root = swapIndex;
 254        }
 255    }
 256
 257    private void Swap(int first, int second)
 258    {
 59846259        Entry firstEntry = _entries[first];
 59846260        Entry secondEntry = _entries[second];
 59846261        _entries[first] = secondEntry;
 59846262        _entries[second] = firstEntry;
 59846263        if (_entryIndices != null)
 264        {
 122265            _entryIndices[secondEntry.DynamicId] = first;
 122266            _entryIndices[firstEntry.DynamicId] = second;
 267        }
 59846268    }
 269
 270    private static int Compare(Entry x, Entry y)
 271    {
 98570272        int result = x.MinX.CompareTo(y.MinX);
 98570273        if (result != 0)
 76847274            return result;
 275
 21723276        result = x.MinY.CompareTo(y.MinY);
 21723277        if (result != 0)
 82278            return result;
 279
 21641280        result = x.MinZ.CompareTo(y.MinZ);
 21641281        if (result != 0)
 21613282            return result;
 283
 28284        result = x.MaxX.CompareTo(y.MaxX);
 28285        if (result != 0)
 5286            return result;
 287
 23288        result = x.MaxY.CompareTo(y.MaxY);
 23289        if (result != 0)
 2290            return result;
 291
 21292        result = x.MaxZ.CompareTo(y.MaxZ);
 21293        if (result != 0)
 3294            return result;
 295
 18296        return x.DynamicId.CompareTo(y.DynamicId);
 297    }
 298
 299    private readonly struct Entry
 300    {
 301        public Entry(int dynamicId, FixedBoundVolume bounds)
 302        {
 14783303            DynamicId = dynamicId;
 14783304            MinX = bounds.Min.X;
 14783305            MinY = bounds.Min.Y;
 14783306            MinZ = bounds.Min.Z;
 14783307            MaxX = bounds.Max.X;
 14783308            MaxY = bounds.Max.Y;
 14783309            MaxZ = bounds.Max.Z;
 14783310        }
 311
 312        public int DynamicId { get; }
 313        public Fixed64 MinX { get; }
 314        public Fixed64 MinY { get; }
 315        public Fixed64 MinZ { get; }
 316        public Fixed64 MaxX { get; }
 317        public Fixed64 MaxY { get; }
 318        public Fixed64 MaxZ { get; }
 319
 320        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 321        public bool Intersects(FixedBoundVolume queryBounds)
 322        {
 200219323            return !(MinX > queryBounds.Max.X || MaxX < queryBounds.Min.X ||
 200219324                     MinY > queryBounds.Max.Y || MaxY < queryBounds.Min.Y ||
 200219325                     MinZ > queryBounds.Max.Z || MaxZ < queryBounds.Min.Z);
 326        }
 327    }
 328}
 329
 330internal readonly struct DynamicCcdPlanarBounds
 331{
 332    public DynamicCcdPlanarBounds(Fixed64 minX, Fixed64 minZ, Fixed64 maxX, Fixed64 maxZ)
 333    {
 27153334        MinX = minX;
 27153335        MinZ = minZ;
 27153336        MaxX = maxX;
 27153337        MaxZ = maxZ;
 27153338    }
 339
 340    public Fixed64 MinX { get; }
 341    public Fixed64 MinZ { get; }
 342    public Fixed64 MaxX { get; }
 343    public Fixed64 MaxZ { get; }
 344}
 345
 346internal sealed class DynamicCcdCandidateIndex2D
 347{
 348    private readonly SwiftList<Entry> _entries;
 349    private readonly SwiftDictionary<int, int>? _entryIndices;
 350    private Fixed64 _maxExtentX;
 351    private int _maxExtentXCount;
 352    private int _unrepresentableExtentXCount;
 15257353    private bool _isSorted = true;
 354
 15257355    public DynamicCcdCandidateIndex2D(int capacity = 0, bool supportsUpdates = false)
 356    {
 15257357        _entries = capacity > 0 ? new SwiftList<Entry>(capacity) : new SwiftList<Entry>();
 15257358        _entryIndices = !supportsUpdates
 15257359            ? null
 15257360            : capacity > 0
 15257361                ? new SwiftDictionary<int, int>(capacity)
 15257362                : new SwiftDictionary<int, int>();
 15257363    }
 364
 279365    public int Count => _entries.Count;
 366
 367    public void Clear()
 368    {
 2369369        _entries.FastClear();
 2369370        _entryIndices?.Clear();
 2369371        _maxExtentX = Fixed64.Zero;
 2369372        _maxExtentXCount = 0;
 2369373        _unrepresentableExtentXCount = 0;
 2369374        _isSorted = true;
 2369375    }
 376
 377    public void Add(int dynamicId, DynamicCcdPlanarBounds bounds)
 378    {
 25455379        _entryIndices?.Add(dynamicId, _entries.Count);
 25455380        _entries.Add(new Entry(dynamicId, bounds));
 25455381        IncludeExtent(bounds.MinX, bounds.MaxX);
 382
 25455383        _isSorted = false;
 25455384    }
 385
 386    public void AddOrUpdate(int dynamicId, DynamicCcdPlanarBounds bounds)
 387    {
 23057388        SwiftDictionary<int, int>? entryIndices = _entryIndices;
 23057389        SwiftThrowHelper.ThrowIfTrue(
 23057390            entryIndices == null,
 23057391            nameof(DynamicCcdCandidateIndex2D),
 23057392            "Candidate index was not configured for updates.");
 23057393        if (entryIndices.TryGetValue(dynamicId, out int index))
 394        {
 63395            Entry previous = _entries[index];
 63396            _entries[index] = new Entry(dynamicId, bounds);
 63397            bool remainsSorted = _isSorted && IsOrderedAt(index);
 63398            if (!DynamicCcdExtentMetadata.IsEquivalent(
 63399                    previous.MinX,
 63400                    previous.MaxX,
 63401                    bounds.MinX,
 63402                    bounds.MaxX))
 403            {
 52404                if (RemoveExtent(previous.MinX, previous.MaxX))
 36405                    RebuildExtents();
 406                else
 16407                    IncludeExtent(bounds.MinX, bounds.MaxX);
 408            }
 409
 63410            _isSorted = remainsSorted;
 63411            return;
 412        }
 413
 22994414        Add(dynamicId, bounds);
 22994415    }
 416
 417    public bool Remove(int dynamicId)
 418    {
 19235419        SwiftDictionary<int, int>? entryIndices = _entryIndices;
 19235420        if (entryIndices == null || !entryIndices.TryGetValue(dynamicId, out int index))
 1678421            return false;
 422
 17557423        Entry removed = _entries[index];
 17557424        int lastIndex = _entries.Count - 1;
 17557425        if (index != lastIndex)
 426        {
 16359427            Entry moved = _entries[lastIndex];
 16359428            _entries[index] = moved;
 16359429            entryIndices[moved.DynamicId] = index;
 430        }
 431
 17557432        _entries.RemoveAt(lastIndex);
 17557433        entryIndices.Remove(dynamicId);
 17557434        if (RemoveExtent(removed.MinX, removed.MaxX))
 959435            RebuildExtents();
 17557436        _isSorted = false;
 17557437        return true;
 438    }
 439
 440    public void Sort()
 441    {
 2549442        if (!_isSorted && _entries.Count > 1)
 354443            HeapSort();
 444
 2549445        _isSorted = true;
 2549446    }
 447
 448    public void Query(DynamicCcdPlanarBounds queryBounds, SwiftList<int> results)
 449    {
 2217450        results.FastClear();
 2217451        if (_entries.Count == 0)
 793452            return;
 453
 1424454        Sort();
 1424455        Fixed64 scanMinX = _unrepresentableExtentXCount > 0
 1424456            || !Fixed64.TrySubtract(queryBounds.MinX, _maxExtentX, out Fixed64 representableScanMinX)
 1424457                ? Fixed64.MinValue
 1424458                : representableScanMinX;
 1424459        int index = FindFirstCandidateIndex(scanMinX);
 5210460        for (; index < _entries.Count; index++)
 461        {
 1934462            Entry entry = _entries[index];
 1934463            if (entry.MinX > queryBounds.MaxX)
 464                break;
 465
 1893466            if (entry.Intersects(queryBounds))
 1857467                results.Add(entry.DynamicId);
 468        }
 1424469    }
 470
 471    public static DynamicCcdPlanarBounds CreateSweptCircleBounds(Vector2d start, Vector2d displacement, Fixed64 radius)
 1078472        => CreateBoundsBetween(start, start + displacement, radius);
 473
 474    public static DynamicCcdPlanarBounds CreateBoundsBetween(
 475        Vector2d start,
 476        Vector2d end,
 477        Fixed64 radius)
 478    {
 3568479        Fixed64 minX = FixedMath.Min(start.X, end.X) - radius;
 3568480        Fixed64 maxX = FixedMath.Max(start.X, end.X) + radius;
 3568481        Fixed64 minZ = FixedMath.Min(start.Y, end.Y) - radius;
 3568482        Fixed64 maxZ = FixedMath.Max(start.Y, end.Y) + radius;
 3568483        return new DynamicCcdPlanarBounds(minX, minZ, maxX, maxZ);
 484    }
 485
 486    private void IncludeExtent(Fixed64 minX, Fixed64 maxX)
 487    {
 25883488        if (!Fixed64.TrySubtract(maxX, minX, out Fixed64 extentX))
 489        {
 20490            _unrepresentableExtentXCount++;
 20491            return;
 492        }
 493
 25863494        if (extentX > _maxExtentX)
 495        {
 6200496            _maxExtentX = extentX;
 6200497            _maxExtentXCount = 1;
 498        }
 19663499        else if (extentX == _maxExtentX)
 500        {
 16810501            _maxExtentXCount++;
 502        }
 19663503    }
 504
 505    private bool RemoveExtent(Fixed64 minX, Fixed64 maxX)
 506    {
 17609507        if (!Fixed64.TrySubtract(maxX, minX, out Fixed64 extentX))
 508        {
 2509            _unrepresentableExtentXCount--;
 2510            return false;
 511        }
 512
 17607513        return extentX == _maxExtentX && --_maxExtentXCount == 0;
 514    }
 515
 516    private void RebuildExtents()
 517    {
 995518        _maxExtentX = Fixed64.Zero;
 995519        _maxExtentXCount = 0;
 995520        _unrepresentableExtentXCount = 0;
 2814521        for (int i = 0; i < _entries.Count; i++)
 412522            IncludeExtent(_entries[i].MinX, _entries[i].MaxX);
 995523    }
 524
 525    private int FindFirstCandidateIndex(Fixed64 minX)
 526    {
 1424527        int low = 0;
 1424528        int high = _entries.Count;
 3258529        while (low < high)
 530        {
 1834531            int middle = low + ((high - low) >> 1);
 1834532            if (_entries[middle].MinX < minX)
 18533                low = middle + 1;
 534            else
 1816535                high = middle;
 536        }
 537
 1424538        return low;
 539    }
 540
 541    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 542    private bool IsOrderedAt(int index)
 543    {
 35544        Entry entry = _entries[index];
 35545        return (index == 0 || Compare(_entries[index - 1], entry) <= 0)
 35546            && (index == _entries.Count - 1 || Compare(entry, _entries[index + 1]) <= 0);
 547    }
 548
 549    private void HeapSort()
 550    {
 354551        int count = _entries.Count;
 2478552        for (int start = (count >> 1) - 1; start >= 0; start--)
 885553            SiftDown(start, count);
 554
 3672555        for (int end = count - 1; end > 0; end--)
 556        {
 1482557            Swap(0, end);
 1482558            SiftDown(0, end);
 559        }
 354560    }
 561
 562    private void SiftDown(int root, int count)
 563    {
 5012564        while (true)
 565        {
 7379566            int child = (root << 1) + 1;
 7379567            if (child >= count)
 2171568                return;
 569
 5208570            int swapIndex = root;
 5208571            if (Compare(_entries[swapIndex], _entries[child]) < 0)
 4890572                swapIndex = child;
 573
 5208574            int right = child + 1;
 5208575            if (right < count && Compare(_entries[swapIndex], _entries[right]) < 0)
 2263576                swapIndex = right;
 577
 5208578            if (swapIndex == root)
 196579                return;
 580
 5012581            Swap(root, swapIndex);
 5012582            root = swapIndex;
 583        }
 584    }
 585
 586    private void Swap(int first, int second)
 587    {
 6494588        Entry firstEntry = _entries[first];
 6494589        Entry secondEntry = _entries[second];
 6494590        _entries[first] = secondEntry;
 6494591        _entries[second] = firstEntry;
 6494592        if (_entryIndices != null)
 593        {
 141594            _entryIndices[secondEntry.DynamicId] = first;
 141595            _entryIndices[firstEntry.DynamicId] = second;
 596        }
 6494597    }
 598
 599    private static int Compare(Entry x, Entry y)
 600    {
 9907601        int result = x.MinX.CompareTo(y.MinX);
 9907602        if (result != 0)
 9193603            return result;
 604
 714605        result = x.MinZ.CompareTo(y.MinZ);
 714606        if (result != 0)
 671607            return result;
 608
 43609        result = x.MaxX.CompareTo(y.MaxX);
 43610        if (result != 0)
 5611            return result;
 612
 38613        result = x.MaxZ.CompareTo(y.MaxZ);
 38614        if (result != 0)
 2615            return result;
 616
 36617        return x.DynamicId.CompareTo(y.DynamicId);
 618    }
 619
 620    private readonly struct Entry
 621    {
 622        public Entry(int dynamicId, DynamicCcdPlanarBounds bounds)
 623        {
 25518624            DynamicId = dynamicId;
 25518625            MinX = bounds.MinX;
 25518626            MinZ = bounds.MinZ;
 25518627            MaxX = bounds.MaxX;
 25518628            MaxZ = bounds.MaxZ;
 25518629        }
 630
 631        public int DynamicId { get; }
 632        public Fixed64 MinX { get; }
 633        public Fixed64 MinZ { get; }
 634        public Fixed64 MaxX { get; }
 635        public Fixed64 MaxZ { get; }
 636
 637        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 638        public bool Intersects(DynamicCcdPlanarBounds queryBounds)
 639        {
 1893640            return !(MinX > queryBounds.MaxX || MaxX < queryBounds.MinX ||
 1893641                     MinZ > queryBounds.MaxZ || MaxZ < queryBounds.MinZ);
 642        }
 643    }
 644}
 645
 646file static class DynamicCcdExtentMetadata
 647{
 648    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 649    public static bool IsEquivalent(
 650        Fixed64 previousMinX,
 651        Fixed64 previousMaxX,
 652        Fixed64 currentMinX,
 653        Fixed64 currentMaxX)
 654    {
 344655        bool previousRepresentable = Fixed64.TrySubtract(previousMaxX, previousMinX, out Fixed64 previousExtentX);
 344656        bool currentRepresentable = Fixed64.TrySubtract(currentMaxX, currentMinX, out Fixed64 currentExtentX);
 344657        return previousRepresentable == currentRepresentable
 344658            && (!previousRepresentable || previousExtentX == currentExtentX);
 659    }
 660}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/2D/CollisionDetection2D.cs

#LineLine coverage
 1//=======================================================================
 2// CollisionDetection2D.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 System;
 12using System.Runtime.CompilerServices;
 13
 14namespace Gravitas.CollisionHandling;
 15
 16/// <summary>
 17/// Deterministic pure 2D narrow-phase collision checks.
 18/// </summary>
 19internal static class CollisionDetection2D
 20{
 21    internal static bool TryCollide(LSCollider2D colliderA, LSCollider2D colliderB, out Contact2D contact)
 22    {
 237323        SwiftThrowHelper.ThrowIfNull(colliderA, nameof(colliderA));
 237324        SwiftThrowHelper.ThrowIfNull(colliderB, nameof(colliderB));
 25
 237326        CollisionType2D collisionType = ColliderSettings2D.GetCollisionType(colliderA.Shape, colliderB.Shape);
 237327        return TryCollide(new CollisionWorkItem2D(colliderA, colliderB, collisionType), out contact);
 28    }
 29
 30    internal static bool TryCollide(CollisionPair2D pair, ContactManifold2D manifold, int frame)
 31    {
 54632        SwiftThrowHelper.ThrowIfNull(pair, nameof(pair));
 54633        return TryCollide(CollisionWorkItem2D.Create(pair), manifold, frame);
 34    }
 35
 36    internal static bool TryCollide(CollisionWorkItem2D item, ContactManifold2D manifold, int frame)
 37    {
 62138        SwiftThrowHelper.ThrowIfNull(manifold, nameof(manifold));
 62139        manifold.BeginUpdate(frame);
 62140        return TryCollide(item, manifold);
 41    }
 42
 43    internal static bool TryCollide(CollisionWorkItem2D item, out Contact2D contact)
 44    {
 237545        LSCollider2D colliderA = item.ColliderA;
 237546        LSCollider2D colliderB = item.ColliderB;
 237547        if (!BoundsOverlap(colliderA, colliderB))
 48        {
 139949            contact = default;
 139950            return false;
 51        }
 52
 97653        switch (item.CollisionType)
 54        {
 55            case CollisionType2D.Circle_Circle:
 5856                return TryCircleCircle((LSCircleCollider2D)colliderA, (LSCircleCollider2D)colliderB, out contact);
 57            case CollisionType2D.Circle_Convex:
 3858                return TryCircleConvex((LSCircleCollider2D)colliderA, colliderB, out contact);
 59            case CollisionType2D.Convex_Circle:
 78560                bool result = TryCircleConvex((LSCircleCollider2D)colliderB, colliderA, out Contact2D reversed);
 78561                contact = result
 78562                    ? new Contact2D(
 78563                        reversed.AnchorB,
 78564                        reversed.AnchorA,
 78565                        -reversed.Normal,
 78566                        reversed.Depth,
 78567                        reversed.DepthIsClamped)
 78568                    : default;
 78569                return result;
 70            case CollisionType2D.Convex_Convex:
 5371                return TryConvexConvex(colliderA, colliderB, out contact);
 72            case CollisionType2D.Capsule_Circle:
 773                return TryCapsuleCircle((LSCapsuleCollider2D)colliderA, (LSCircleCollider2D)colliderB, out contact);
 74            case CollisionType2D.Circle_Capsule:
 475                bool circleCapsule = TryCapsuleCircle((LSCapsuleCollider2D)colliderB, (LSCircleCollider2D)colliderA, out
 476                contact = circleCapsule
 477                    ? new Contact2D(
 478                        circleCapsuleReversed.AnchorB,
 479                        circleCapsuleReversed.AnchorA,
 480                        -circleCapsuleReversed.Normal,
 481                        circleCapsuleReversed.Depth,
 482                        circleCapsuleReversed.DepthIsClamped)
 483                    : default;
 484                return circleCapsule;
 85            case CollisionType2D.Capsule_Convex:
 686                return TryCapsuleConvex((LSCapsuleCollider2D)colliderA, colliderB, out contact);
 87            case CollisionType2D.Convex_Capsule:
 488                bool convexCapsule = TryCapsuleConvex((LSCapsuleCollider2D)colliderB, colliderA, out Contact2D convexCap
 489                contact = convexCapsule
 490                    ? new Contact2D(
 491                        convexCapsuleReversed.AnchorB,
 492                        convexCapsuleReversed.AnchorA,
 493                        -convexCapsuleReversed.Normal,
 494                        convexCapsuleReversed.Depth,
 495                        convexCapsuleReversed.DepthIsClamped)
 496                    : default;
 497                return convexCapsule;
 98            case CollisionType2D.Capsule_Capsule:
 1099                return TryCapsuleCapsule((LSCapsuleCollider2D)colliderA, (LSCapsuleCollider2D)colliderB, out contact);
 100            case CollisionType2D.Compound:
 10101                return TryCompound(colliderA, colliderB, out contact);
 102            default:
 1103                contact = default;
 1104                return false;
 105        }
 106    }
 107
 108    private static bool TryCollide(CollisionWorkItem2D item, ContactManifold2D manifold)
 109    {
 649110        LSCollider2D colliderA = item.ColliderA;
 649111        LSCollider2D colliderB = item.ColliderB;
 649112        if (!BoundsOverlap(colliderA, colliderB))
 17113            return false;
 114
 632115        return item.CollisionType switch
 632116        {
 455117            CollisionType2D.Circle_Circle => TryCircleCircle((LSCircleCollider2D)colliderA, (LSCircleCollider2D)collider
 4118            CollisionType2D.Circle_Convex => TryCircleConvex((LSCircleCollider2D)colliderA, colliderB, manifold),
 89119            CollisionType2D.Convex_Circle => TryCircleConvexReversed((LSCircleCollider2D)colliderB, colliderA, manifold)
 25120            CollisionType2D.Convex_Convex => TryConvexConvex(colliderA, colliderB, manifold),
 11121            CollisionType2D.Capsule_Circle => TryCapsuleCircle((LSCapsuleCollider2D)colliderA, (LSCircleCollider2D)colli
 4122            CollisionType2D.Circle_Capsule => TryCapsuleCircleReversed((LSCapsuleCollider2D)colliderB, (LSCircleCollider
 10123            CollisionType2D.Capsule_Convex => TryCapsuleConvex((LSCapsuleCollider2D)colliderA, colliderB, manifold),
 9124            CollisionType2D.Convex_Capsule => TryCapsuleConvexReversed((LSCapsuleCollider2D)colliderB, colliderA, manifo
 7125            CollisionType2D.Capsule_Capsule => TryCapsuleCapsule((LSCapsuleCollider2D)colliderA, (LSCapsuleCollider2D)co
 17126            CollisionType2D.Compound => TryCompound(colliderA, colliderB, manifold),
 1127            _ => false
 632128        };
 129    }
 130
 131    private static bool TryCircleCircle(LSCircleCollider2D colliderA, LSCircleCollider2D colliderB, out Contact2D contac
 132    {
 513133        Vector2d delta = colliderB.Center - colliderA.Center;
 513134        Fixed64 radius = colliderA.ScaledRadius + colliderB.ScaledRadius;
 513135        Fixed64 distanceSquared = delta.MagnitudeSquared;
 513136        if (distanceSquared > radius * radius)
 137        {
 34138            contact = default;
 34139            return false;
 140        }
 141
 479142        Fixed64 distance = distanceSquared > Fixed64.Zero ? FixedMath.Sqrt(distanceSquared) : Fixed64.Zero;
 479143        Vector2d normal = distance > Fixed64.Zero ? delta / distance : Vector2d.Right;
 479144        Fixed64 depth = radius - distance;
 479145        contact = new Contact2D(
 479146            new ContactAnchor2D(
 479147                colliderA.Center,
 479148                normal * colliderA.ScaledRadius),
 479149            new ContactAnchor2D(
 479150                colliderB.Center,
 479151                -normal * colliderB.ScaledRadius),
 479152            normal,
 479153            depth);
 479154        return true;
 155    }
 156
 157    private static bool TryCircleCircle(
 158        LSCircleCollider2D colliderA,
 159        LSCircleCollider2D colliderB,
 160        ContactManifold2D manifold)
 161    {
 455162        if (!TryCircleCircle(colliderA, colliderB, out Contact2D contact))
 10163            return false;
 164
 445165        AddContact(manifold, contact, colliderA, colliderB);
 445166        return true;
 167    }
 168
 169    private static bool TryCircleConvex(LSCircleCollider2D circle, LSCollider2D convex, out Contact2D contact)
 170    {
 916171        Span<Vector2d> convexOffsets = stackalloc Vector2d[4];
 916172        ReadOnlySpan<Vector2d> convexVertexOffsets =
 916173            GetConvexVertexOffsets(convex, convexOffsets);
 916174        if (!FixedConvex2dRelations.TryGetCircleContact(
 916175                circle.Center,
 916176                circle.Rotation,
 916177                circle.ScaledRadius,
 916178                convex.Center,
 916179                convex.ConvexRotation,
 916180                convexVertexOffsets,
 916181                out FixedPointAnchor2d circleAnchor,
 916182                out FixedPointAnchor2d convexAnchor,
 916183                out Vector2d normal,
 916184                out Fixed64 depth,
 916185                out bool depthIsClamped))
 186        {
 387187            contact = default;
 387188            return false;
 189        }
 190
 529191        contact = new Contact2D(
 529192            new ContactAnchor2D(circleAnchor),
 529193            new ContactAnchor2D(convexAnchor),
 529194            normal,
 529195            depth,
 529196            depthIsClamped);
 529197        return true;
 198    }
 199
 200    private static bool TryCircleConvex(
 201        LSCircleCollider2D circle,
 202        LSCollider2D convex,
 203        ContactManifold2D manifold)
 204    {
 4205        if (!TryCircleConvex(circle, convex, out Contact2D contact))
 1206            return false;
 207
 3208        AddContact(manifold, contact, circle, convex);
 3209        return true;
 210    }
 211
 212    private static bool TryCircleConvexReversed(
 213        LSCircleCollider2D circle,
 214        LSCollider2D convex,
 215        ContactManifold2D manifold)
 216    {
 89217        if (!TryCircleConvex(circle, convex, out Contact2D contact))
 1218            return false;
 219
 88220        manifold.AddContact(
 88221            contact.AnchorB,
 88222            contact.AnchorA,
 88223            contact.Depth,
 88224            -contact.Normal,
 88225            convex.Material,
 88226            circle.Material,
 88227            contact.DepthIsClamped);
 88228        return true;
 229    }
 230
 231    private static bool TryCapsuleCircle(LSCapsuleCollider2D capsule, LSCircleCollider2D circle, out Contact2D contact)
 232    {
 26233        Vector2d fallbackNormal = capsule.GetNormalFromCenteredAxis(circle.Center);
 26234        if (!FixedSegment2d.TryGetCenteredCapsulesContact(
 26235                capsule.Center,
 26236                capsule.Rotation,
 26237                capsule.AxisLength,
 26238                capsule.ScaledRadius,
 26239                circle.Center,
 26240                circle.Rotation,
 26241                Fixed64.Zero,
 26242                circle.ScaledRadius,
 26243                fallbackNormal,
 26244                out FixedContactAnchors2d fixedContact))
 245        {
 4246            contact = default;
 4247            return false;
 248        }
 249
 22250        contact = new Contact2D(
 22251            new ContactAnchor2D(fixedContact.FirstAnchor),
 22252            new ContactAnchor2D(fixedContact.SecondAnchor),
 22253            fixedContact.Normal,
 22254            fixedContact.Depth,
 22255            fixedContact.DepthIsClamped);
 22256        return true;
 257    }
 258
 259    private static bool TryCapsuleCircle(
 260        LSCapsuleCollider2D capsule,
 261        LSCircleCollider2D circle,
 262        ContactManifold2D manifold)
 263    {
 11264        if (!TryCapsuleCircle(capsule, circle, out Contact2D contact))
 1265            return false;
 266
 10267        AddContact(manifold, contact, capsule, circle);
 10268        return true;
 269    }
 270
 271    private static bool TryCapsuleCircleReversed(
 272        LSCapsuleCollider2D capsule,
 273        LSCircleCollider2D circle,
 274        ContactManifold2D manifold)
 275    {
 4276        if (!TryCapsuleCircle(capsule, circle, out Contact2D contact))
 1277            return false;
 278
 3279        manifold.AddContact(
 3280            contact.AnchorB,
 3281            contact.AnchorA,
 3282            contact.Depth,
 3283            -contact.Normal,
 3284            circle.Material,
 3285            capsule.Material,
 3286            contact.DepthIsClamped);
 3287        return true;
 288    }
 289
 290    private static bool TryCapsuleCapsule(LSCapsuleCollider2D colliderA, LSCapsuleCollider2D colliderB, out Contact2D co
 291    {
 17292        Vector2d fallbackNormal = colliderB.AxisLength <= Fixed64.Epsilon
 17293            ? colliderA.GetNormalFromCenteredAxis(colliderB.Center)
 17294            : colliderA.AxisLength <= Fixed64.Epsilon
 17295                ? -colliderB.GetNormalFromCenteredAxis(colliderA.Center)
 17296                : OrientCoincidentCapsuleNormal(colliderB.Center - colliderA.Center);
 17297        if (!FixedSegment2d.TryGetCenteredCapsulesContact(
 17298                colliderA.Center,
 17299                colliderA.Rotation,
 17300                colliderA.AxisLength,
 17301                colliderA.ScaledRadius,
 17302                colliderB.Center,
 17303                colliderB.Rotation,
 17304                colliderB.AxisLength,
 17305                colliderB.ScaledRadius,
 17306                fallbackNormal,
 17307                out FixedContactAnchors2d fixedContact))
 308        {
 2309            contact = default;
 2310            return false;
 311        }
 312
 15313        contact = new Contact2D(
 15314            new ContactAnchor2D(fixedContact.FirstAnchor),
 15315            new ContactAnchor2D(fixedContact.SecondAnchor),
 15316            fixedContact.Normal,
 15317            fixedContact.Depth,
 15318            fixedContact.DepthIsClamped);
 15319        return true;
 320    }
 321
 322    private static bool TryCapsuleCapsule(
 323        LSCapsuleCollider2D colliderA,
 324        LSCapsuleCollider2D colliderB,
 325        ContactManifold2D manifold)
 326    {
 7327        if (!TryCapsuleCapsule(colliderA, colliderB, out Contact2D contact))
 1328            return false;
 329
 6330        AddContact(manifold, contact, colliderA, colliderB);
 6331        return true;
 332    }
 333
 334    private static bool TryCapsuleConvex(LSCapsuleCollider2D capsule, LSCollider2D convex, out Contact2D contact)
 335    {
 10336        Span<FixedPointAnchor2d> capsuleAnchors =
 10337            stackalloc FixedPointAnchor2d[2];
 10338        Span<FixedPointAnchor2d> convexAnchors =
 10339            stackalloc FixedPointAnchor2d[2];
 10340        if (!TryGetCapsuleConvexContacts(
 10341                capsule,
 10342                convex,
 10343                capsuleAnchors,
 10344                convexAnchors,
 10345                out _,
 10346                out Vector2d normal,
 10347                out Fixed64 depth,
 10348                out bool depthIsClamped))
 349        {
 3350            contact = default;
 3351            return false;
 352        }
 353
 7354        contact = new Contact2D(
 7355            new ContactAnchor2D(capsuleAnchors[0]),
 7356            new ContactAnchor2D(convexAnchors[0]),
 7357            normal,
 7358            depth,
 7359            depthIsClamped);
 7360        return true;
 361    }
 362
 363    private static bool TryGetCapsuleConvexContacts(
 364        LSCapsuleCollider2D capsule,
 365        LSCollider2D convex,
 366        Span<FixedPointAnchor2d> capsuleAnchors,
 367        Span<FixedPointAnchor2d> convexAnchors,
 368        out int contactCount,
 369        out Vector2d normal,
 370        out Fixed64 depth,
 371        out bool depthIsClamped)
 372    {
 29373        Span<Vector2d> vertexOffsets = stackalloc Vector2d[4];
 29374        ReadOnlySpan<Vector2d> convexVertexOffsets =
 29375            GetConvexVertexOffsets(convex, vertexOffsets);
 29376        return FixedSegment2d.TryGetCenteredCapsuleConvexContacts(
 29377            capsule.Center,
 29378            capsule.Rotation,
 29379            Vector2d.Forward,
 29380            capsule.AxisLength,
 29381            capsule.ScaledRadius,
 29382            convex.Center,
 29383            convex.ConvexRotation,
 29384            convexVertexOffsets,
 29385            capsuleAnchors,
 29386            convexAnchors,
 29387            out contactCount,
 29388            out normal,
 29389            out depth,
 29390            out depthIsClamped);
 391    }
 392
 393    private static ReadOnlySpan<Vector2d> GetConvexVertexOffsets(
 394        LSCollider2D convex,
 395        Span<Vector2d> scratch)
 396    {
 1101397        if (convex is LSPolygonCollider2D polygon)
 899398            return polygon.ScaledLocalVertices;
 399
 202400        int vertexCount = convex.VertexCount;
 2020401        for (int i = 0; i < vertexCount; i++)
 808402            scratch[i] = convex.GetScaledLocalVertexUnchecked(i);
 202403        return scratch.Slice(0, vertexCount);
 404    }
 405
 406    private static bool TryCapsuleConvex(
 407        LSCapsuleCollider2D capsule,
 408        LSCollider2D convex,
 409        ContactManifold2D manifold)
 410    {
 10411        Span<FixedPointAnchor2d> capsuleAnchors =
 10412            stackalloc FixedPointAnchor2d[2];
 10413        Span<FixedPointAnchor2d> convexAnchors =
 10414            stackalloc FixedPointAnchor2d[2];
 10415        if (!TryGetCapsuleConvexContacts(
 10416                capsule,
 10417                convex,
 10418                capsuleAnchors,
 10419                convexAnchors,
 10420                out int contactCount,
 10421                out Vector2d normal,
 10422                out Fixed64 depth,
 10423                out bool depthIsClamped))
 424        {
 1425            return false;
 426        }
 427
 48428        for (int i = 0; i < contactCount; i++)
 429        {
 15430            manifold.AddContact(
 15431                new ContactAnchor2D(capsuleAnchors[i]),
 15432                new ContactAnchor2D(convexAnchors[i]),
 15433                depth,
 15434                normal,
 15435                capsule.Material,
 15436                convex.Material,
 15437                depthIsClamped);
 438        }
 9439        return true;
 440    }
 441
 442    private static bool TryCapsuleConvexReversed(
 443        LSCapsuleCollider2D capsule,
 444        LSCollider2D convex,
 445        ContactManifold2D manifold)
 446    {
 9447        Span<FixedPointAnchor2d> capsuleAnchors =
 9448            stackalloc FixedPointAnchor2d[2];
 9449        Span<FixedPointAnchor2d> convexAnchors =
 9450            stackalloc FixedPointAnchor2d[2];
 9451        if (!TryGetCapsuleConvexContacts(
 9452                capsule,
 9453                convex,
 9454                capsuleAnchors,
 9455                convexAnchors,
 9456                out int contactCount,
 9457                out Vector2d normal,
 9458                out Fixed64 depth,
 9459                out bool depthIsClamped))
 460        {
 1461            return false;
 462        }
 463
 42464        for (int i = 0; i < contactCount; i++)
 465        {
 13466            manifold.AddContact(
 13467                new ContactAnchor2D(convexAnchors[i]),
 13468                new ContactAnchor2D(capsuleAnchors[i]),
 13469                depth,
 13470                -normal,
 13471                convex.Material,
 13472                capsule.Material,
 13473                depthIsClamped);
 474        }
 8475        return true;
 476    }
 477
 478    private static bool TryConvexConvex(LSCollider2D colliderA, LSCollider2D colliderB, out Contact2D contact)
 479    {
 53480        Span<Vector2d> firstVertexScratch = stackalloc Vector2d[4];
 53481        Span<Vector2d> secondVertexScratch = stackalloc Vector2d[4];
 53482        ReadOnlySpan<Vector2d> firstVertexOffsets =
 53483            GetConvexVertexOffsets(colliderA, firstVertexScratch);
 53484        ReadOnlySpan<Vector2d> secondVertexOffsets =
 53485            GetConvexVertexOffsets(colliderB, secondVertexScratch);
 53486        Span<FixedPointAnchor2d> firstContactAnchors =
 53487            stackalloc FixedPointAnchor2d[2];
 53488        Span<FixedPointAnchor2d> secondContactAnchors =
 53489            stackalloc FixedPointAnchor2d[2];
 53490        if (!FixedConvex2dRelations.TryGetConvexContacts(
 53491                colliderA.Center,
 53492                colliderA.ConvexRotation,
 53493                firstVertexOffsets,
 53494                colliderB.Center,
 53495                colliderB.ConvexRotation,
 53496                secondVertexOffsets,
 53497                firstContactAnchors,
 53498                secondContactAnchors,
 53499                out _,
 53500                out Vector2d normal,
 53501                out Fixed64 depth,
 53502                out bool depthIsClamped))
 503        {
 3504            contact = default;
 3505            return false;
 506        }
 507
 50508        contact = new Contact2D(
 50509            new ContactAnchor2D(colliderA.GetConvexSupportAnchor(normal)),
 50510            new ContactAnchor2D(colliderB.GetConvexSupportAnchor(-normal)),
 50511            normal,
 50512            depth,
 50513            depthIsClamped);
 50514        return true;
 515    }
 516
 517    private static bool TryConvexConvex(
 518        LSCollider2D colliderA,
 519        LSCollider2D colliderB,
 520        ContactManifold2D manifold)
 521    {
 25522        Span<Vector2d> firstVertexScratch = stackalloc Vector2d[4];
 25523        Span<Vector2d> secondVertexScratch = stackalloc Vector2d[4];
 25524        ReadOnlySpan<Vector2d> firstVertexOffsets =
 25525            GetConvexVertexOffsets(colliderA, firstVertexScratch);
 25526        ReadOnlySpan<Vector2d> secondVertexOffsets =
 25527            GetConvexVertexOffsets(colliderB, secondVertexScratch);
 25528        Span<FixedPointAnchor2d> firstContactAnchors =
 25529            stackalloc FixedPointAnchor2d[2];
 25530        Span<FixedPointAnchor2d> secondContactAnchors =
 25531            stackalloc FixedPointAnchor2d[2];
 25532        if (!FixedConvex2dRelations.TryGetConvexContacts(
 25533                colliderA.Center,
 25534                colliderA.ConvexRotation,
 25535                firstVertexOffsets,
 25536                colliderB.Center,
 25537                colliderB.ConvexRotation,
 25538                secondVertexOffsets,
 25539                firstContactAnchors,
 25540                secondContactAnchors,
 25541                out int contactCount,
 25542                out Vector2d normal,
 25543                out Fixed64 depth,
 25544                out bool depthIsClamped))
 545        {
 1546            return false;
 547        }
 548
 136549        for (int i = 0; i < contactCount; i++)
 550        {
 44551            manifold.AddContact(
 44552                new ContactAnchor2D(firstContactAnchors[i]),
 44553                new ContactAnchor2D(secondContactAnchors[i]),
 44554                depth,
 44555                normal,
 44556                colliderA.Material,
 44557                colliderB.Material,
 44558                depthIsClamped);
 559        }
 560
 24561        return true;
 562    }
 563
 564    private static bool TryCompound(LSCollider2D colliderA, LSCollider2D colliderB, out Contact2D contact)
 565    {
 10566        if (colliderA is LSCompoundCollider2D compoundA)
 567        {
 6568            if (colliderB is LSCompoundCollider2D compoundB)
 3569                return TryCompoundCompound(compoundA, compoundB, out contact);
 570
 3571            return TryCompoundOther(compoundA, colliderB, compoundIsA: true, out contact);
 572        }
 573
 4574        if (colliderB is LSCompoundCollider2D compound)
 3575            return TryCompoundOther(compound, colliderA, compoundIsA: false, out contact);
 576
 1577        contact = default;
 1578        return false;
 579    }
 580
 581    private static bool TryCompoundCompound(
 582        LSCompoundCollider2D compoundA,
 583        LSCompoundCollider2D compoundB,
 584        out Contact2D contact)
 585    {
 3586        bool found = false;
 3587        Contact2D best = default;
 588
 16589        for (int i = 0; i < compoundA.PartCount; i++)
 590        {
 5591            LSCollider2D partA = compoundA.GetPartCollider(i);
 26592            for (int j = 0; j < compoundB.PartCount; j++)
 593            {
 8594                LSCollider2D partB = compoundB.GetPartCollider(j);
 8595                if (!TryCollide(partA, partB, out Contact2D candidate))
 596                    continue;
 597
 3598                if (ContactSelectionPolicy.ShouldReplaceWithDeeper(candidate, found, best))
 599                {
 2600                    best = candidate;
 2601                    found = true;
 602                }
 603            }
 604        }
 605
 3606        if (!found)
 607        {
 1608            contact = default;
 1609            return false;
 610        }
 611
 2612        contact = best;
 2613        return true;
 614    }
 615
 616    private static bool TryCompoundOther(
 617        LSCompoundCollider2D compound,
 618        LSCollider2D other,
 619        bool compoundIsA,
 620        out Contact2D contact)
 621    {
 6622        bool found = false;
 6623        Contact2D best = default;
 624
 32625        for (int i = 0; i < compound.PartCount; i++)
 626        {
 10627            LSCollider2D part = compound.GetPartCollider(i);
 628            Contact2D candidate;
 10629            bool collided = compoundIsA
 10630                ? TryCollide(part, other, out candidate)
 10631                : TryCollide(other, part, out candidate);
 632
 10633            if (!collided)
 634                continue;
 635
 5636            if (ContactSelectionPolicy.ShouldReplaceWithDeeper(candidate, found, best))
 637            {
 4638                best = candidate;
 4639                found = true;
 640            }
 641        }
 642
 6643        if (!found)
 644        {
 2645            contact = default;
 2646            return false;
 647        }
 648
 4649        contact = best;
 4650        return true;
 651    }
 652
 653    private static bool TryCompound(
 654        LSCollider2D colliderA,
 655        LSCollider2D colliderB,
 656        ContactManifold2D manifold)
 657    {
 17658        if (colliderA is LSCompoundCollider2D compoundA)
 659        {
 14660            if (colliderB is LSCompoundCollider2D compoundB)
 4661                return TryCompoundCompound(compoundA, compoundB, manifold);
 662
 10663            return TryCompoundOther(compoundA, colliderB, compoundIsA: true, manifold);
 664        }
 665
 3666        if (colliderB is LSCompoundCollider2D compound)
 2667            return TryCompoundOther(compound, colliderA, compoundIsA: false, manifold);
 668
 1669        return false;
 670    }
 671
 672    private static bool TryCompoundCompound(
 673        LSCompoundCollider2D compoundA,
 674        LSCompoundCollider2D compoundB,
 675        ContactManifold2D manifold)
 676    {
 4677        bool found = false;
 4678        ContactManifold2D scratch = compoundA.PartManifoldScratch;
 22679        for (int i = 0; i < compoundA.PartCount; i++)
 680        {
 7681            LSCollider2D partA = compoundA.GetPartCollider(i);
 32682            for (int j = 0; j < compoundB.PartCount; j++)
 683            {
 9684                LSCollider2D partB = compoundB.GetPartCollider(j);
 9685                CollisionType2D collisionType = ColliderSettings2D.GetCollisionType(partA.Shape, partB.Shape);
 9686                scratch.BeginUpdate(manifold.LastUpdatedFrame);
 9687                if (!TryCollide(new CollisionWorkItem2D(partA, partB, collisionType), scratch))
 688                    continue;
 689
 2690                found = true;
 2691                AddCompoundPartContacts(
 2692                    manifold,
 2693                    scratch,
 2694                    featureNamespaceA: i + 1,
 2695                    featureNamespaceB: -(j + 1));
 696            }
 697        }
 698
 4699        return found;
 700    }
 701
 702    private static bool TryCompoundOther(
 703        LSCompoundCollider2D compound,
 704        LSCollider2D other,
 705        bool compoundIsA,
 706        ContactManifold2D manifold)
 707    {
 12708        bool found = false;
 12709        ContactManifold2D scratch = compound.PartManifoldScratch;
 62710        for (int i = 0; i < compound.PartCount; i++)
 711        {
 19712            LSCollider2D part = compound.GetPartCollider(i);
 19713            LSCollider2D colliderA = compoundIsA ? part : other;
 19714            LSCollider2D colliderB = compoundIsA ? other : part;
 19715            CollisionType2D collisionType = ColliderSettings2D.GetCollisionType(colliderA.Shape, colliderB.Shape);
 19716            scratch.BeginUpdate(manifold.LastUpdatedFrame);
 19717            if (!TryCollide(new CollisionWorkItem2D(colliderA, colliderB, collisionType), scratch))
 718                continue;
 719
 16720            found = true;
 16721            AddCompoundPartContacts(
 16722                manifold,
 16723                scratch,
 16724                featureNamespaceA: compoundIsA ? i + 1 : 0,
 16725                featureNamespaceB: compoundIsA ? 0 : -(i + 1));
 726        }
 727
 12728        return found;
 729    }
 730
 731    private static void AddCompoundPartContacts(
 732        ContactManifold2D destination,
 733        ContactManifold2D source,
 734        int featureNamespaceA,
 735        int featureNamespaceB)
 736    {
 76737        for (int i = 0; i < source.Count; i++)
 738        {
 20739            ManifoldContact2D contact = source[i];
 20740            destination.AddContact(
 20741                contact.AnchorA,
 20742                contact.AnchorB,
 20743                contact.Depth,
 20744                contact.Normal,
 20745                contact.MaterialA,
 20746                contact.MaterialB,
 20747                contact.DepthIsClamped,
 20748                featureNamespaceA,
 20749                featureNamespaceB);
 750        }
 18751    }
 752
 753    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 754    private static void AddContact(
 755        ContactManifold2D manifold,
 756        Contact2D contact,
 757        LSCollider2D colliderA,
 758        LSCollider2D colliderB) =>
 464759        manifold.AddContact(
 464760            contact.AnchorA,
 464761            contact.AnchorB,
 464762            contact.Depth,
 464763            contact.Normal,
 464764            colliderA.Material,
 464765            colliderB.Material,
 464766            contact.DepthIsClamped);
 767
 768    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 769    internal static bool BoundsOverlap(LSCollider2D colliderA, LSCollider2D colliderB) =>
 10356770        colliderA.MinX <= colliderB.MaxX
 10356771        && colliderA.MaxX >= colliderB.MinX
 10356772        && colliderA.MinY <= colliderB.MaxY
 10356773        && colliderA.MaxY >= colliderB.MinY;
 774
 775    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 776    private static Vector2d OrientCoincidentCapsuleNormal(Vector2d direction) =>
 9777        direction.MagnitudeSquared > Fixed64.Epsilon && direction.X < Fixed64.Zero
 9778            ? -Vector2d.Right
 9779            : Vector2d.Right;
 780
 781}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/2D/CollisionWorkItem2D.cs

#LineLine coverage
 1//=======================================================================
 2// CollisionWorkItem2D.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 Gravitas.Colliders;
 9using System.Runtime.CompilerServices;
 10
 11namespace Gravitas.CollisionHandling;
 12
 13internal readonly struct CollisionWorkItem2D
 14{
 15    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 16    public CollisionWorkItem2D(
 17        LSCollider2D colliderA,
 18        LSCollider2D colliderB,
 19        CollisionType2D collisionType)
 20    {
 302421        ColliderA = colliderA;
 302422        ColliderB = colliderB;
 302423        CollisionType = collisionType;
 302424    }
 25
 26    public LSCollider2D ColliderA { get; }
 27
 28    public LSCollider2D ColliderB { get; }
 29
 30    public CollisionType2D CollisionType { get; }
 31
 32    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 33    public static CollisionWorkItem2D Create(CollisionPair2D pair) =>
 54634        new(pair.ColliderA, pair.ColliderB, pair.CollisionType);
 35}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/3D/AxisPenetration.cs

#LineLine coverage
 1//=======================================================================
 2// CollisionDetection.Support.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;
 9
 10namespace Gravitas.CollisionHandling;
 11
 12internal readonly struct AxisPenetration
 13{
 14    public AxisPenetration(Vector3d axis, Fixed64 depth, bool depthIsClamped = false)
 15    {
 140916        Axis = axis;
 140917        Depth = depth;
 140918        DepthIsClamped = depthIsClamped;
 140919        HasValue = true;
 140920    }
 21
 22    public Vector3d Axis { get; }
 23
 24    public Fixed64 Depth { get; }
 25
 26    public bool DepthIsClamped { get; }
 27
 28    public bool HasValue { get; }
 29}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/3D/CollisionDetection.Capsule.cs

#LineLine coverage
 1//=======================================================================
 2// CollisionDetection.Capsule.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;
 11
 12namespace Gravitas.CollisionHandling;
 13
 14public static partial class CollisionDetection
 15{
 16    #region Capsule
 17
 18    private static bool DoCapsuleSphereCheck(CollisionWorkItem pair)
 19    {
 21120        var capsule = (LSCapsuleCollider)pair.ColliderA;
 21121        var sphere = (LSSphereCollider)pair.ColliderB;
 22
 21123        if (!FixedSegment.TryGetCenteredCapsulesContact(
 21124                capsule.Center,
 21125                capsule.Rotation,
 21126                Vector3d.Up,
 21127                capsule.AxisLength,
 21128                capsule.ScaledRadius,
 21129                sphere.Center,
 21130                sphere.Rotation,
 21131                Vector3d.Up,
 21132                Fixed64.Zero,
 21133                sphere.ScaledRadius,
 21134                ResolveCenteredCapsuleFallback(capsule.Center, sphere.Center),
 21135                out FixedContactAnchors contact))
 36        {
 137            return false;
 38        }
 39
 21040        pair.Manifold.SetContact(
 21041            new ContactAnchor(contact.FirstAnchor),
 21042            new ContactAnchor(contact.SecondAnchor),
 21043            contact.Depth,
 21044            contact.Normal,
 21045            contact.DepthIsClamped);
 21046        return true;
 47    }
 48
 49    private static bool DoCapsulesCheck(CollisionWorkItem pair)
 50    {
 22151        var capsule1 = (LSCapsuleCollider)pair.ColliderA;
 22152        var capsule2 = (LSCapsuleCollider)pair.ColliderB;
 53
 22154        if (!FixedSegment.TryGetCenteredCapsulesContact(
 22155                capsule1.Center,
 22156                capsule1.Rotation,
 22157                Vector3d.Up,
 22158                capsule1.AxisLength,
 22159                capsule1.ScaledRadius,
 22160                capsule2.Center,
 22161                capsule2.Rotation,
 22162                Vector3d.Up,
 22163                capsule2.AxisLength,
 22164                capsule2.ScaledRadius,
 22165                ResolveCenteredCapsuleFallback(capsule1.Center, capsule2.Center),
 22166                out FixedContactAnchors contact))
 67        {
 168            return false;
 69        }
 70
 22071        pair.Manifold.SetContact(
 22072            new ContactAnchor(contact.FirstAnchor),
 22073            new ContactAnchor(contact.SecondAnchor),
 22074            contact.Depth,
 22075            contact.Normal,
 22076            contact.DepthIsClamped);
 22077        return true;
 78    }
 79
 80    private static Vector3d ResolveCenteredCapsuleFallback(
 81        Vector3d firstCenter,
 82        Vector3d secondCenter)
 83    {
 150484        if (secondCenter.X != firstCenter.X)
 147685            return secondCenter.X > firstCenter.X ? Vector3d.Right : Vector3d.Left;
 2886        if (secondCenter.Y != firstCenter.Y)
 387            return secondCenter.Y > firstCenter.Y ? Vector3d.Up : Vector3d.Down;
 2588        if (secondCenter.Z != firstCenter.Z)
 289            return secondCenter.Z > firstCenter.Z ? Vector3d.Forward : Vector3d.Backward;
 2390        return Vector3d.Right;
 91    }
 92
 93    #endregion
 94
 95}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/3D/CollisionDetection.Compound.cs

#LineLine coverage
 1//=======================================================================
 2// CollisionDetection.Compound.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 Gravitas.Colliders;
 9using System.Runtime.CompilerServices;
 10
 11namespace Gravitas.CollisionHandling;
 12
 13public static partial class CollisionDetection
 14{
 15    #region Compound
 16
 17    private static bool DoCompoundCheck(CollisionWorkItem pair)
 18    {
 1319        var compoundA = (LSCompoundCollider)pair.ColliderA;
 1320        if (pair.ColliderB is LSCompoundCollider compoundB)
 21        {
 322            return DoCompoundCompoundCheck(pair, compoundA, compoundB);
 23        }
 24
 1025        return DoCompoundOtherCheck(pair, compoundA, pair.ColliderB);
 26    }
 27
 28    private static bool DoCompoundOtherCheck(
 29        CollisionWorkItem pair,
 30        LSCompoundCollider compound,
 31        LSCollider other)
 32    {
 1033        bool collided = false;
 5034        for (int i = 0; i < compound.PartCount; i++)
 35        {
 1536            LSCollider part = compound.GetPartCollider(i);
 1537            collided |= TryBuildCompoundPartContact(
 1538                pair,
 1539                part,
 1540                featureNamespaceA: i + 1,
 1541                other,
 1542                featureNamespaceB: 0);
 43        }
 44
 1045        return collided;
 46    }
 47
 48    private static bool DoCompoundCompoundCheck(
 49        CollisionWorkItem pair,
 50        LSCompoundCollider compoundA,
 51        LSCompoundCollider compoundB)
 52    {
 353        bool collided = false;
 1654        for (int i = 0; i < compoundA.PartCount; i++)
 55        {
 556            LSCollider partA = compoundA.GetPartCollider(i);
 2857            for (int j = 0; j < compoundB.PartCount; j++)
 58            {
 959                collided |= TryBuildCompoundPartContact(
 960                    pair,
 961                    partA,
 962                    featureNamespaceA: i + 1,
 963                    compoundB.GetPartCollider(j),
 964                    featureNamespaceB: -(j + 1));
 65            }
 66        }
 67
 368        return collided;
 69    }
 70
 71    private static bool TryBuildCompoundPartContact(
 72        CollisionWorkItem ownerPair,
 73        LSCollider first,
 74        int featureNamespaceA,
 75        LSCollider second,
 76        int featureNamespaceB)
 77    {
 2478        if (!BoundsOverlapInclusive(first, second))
 1179            return false;
 80
 1381        OrderPartPairForDetection(
 1382            first,
 1383            featureNamespaceA,
 1384            second,
 1385            featureNamespaceB,
 1386            out LSCollider colliderA,
 1387            out int orderedNamespaceA,
 1388            out LSCollider colliderB,
 1389            out int orderedNamespaceB);
 1390        CollisionType collisionType = ColliderSettings.GetCollisionType(colliderA.Shape, colliderB.Shape);
 1391        ContactManifold scratch = ownerPair.Context.CollisionScratch.CompoundPartManifold;
 1392        scratch.BeginUpdate(ownerPair.Context.FrameCount);
 1393        var partPair = new CollisionWorkItem(ownerPair.Context, colliderA, colliderB, collisionType, scratch);
 1394        if (!DoCollisionCheck(partPair))
 195            return false;
 96
 1297        AddCompoundPartContactsInOwnerOrder(
 1298            ownerPair,
 1299            partPair,
 12100            orderedNamespaceA,
 12101            orderedNamespaceB);
 12102        return true;
 103    }
 104
 105    private static void AddCompoundPartContactsInOwnerOrder(
 106        CollisionWorkItem ownerPair,
 107        CollisionWorkItem partPair,
 108        int featureNamespaceA,
 109        int featureNamespaceB)
 110    {
 12111        bool addInPartOrder = ((LSCompoundCollider)ownerPair.ColliderA).ContainsPartCollider(partPair.ColliderA);
 112
 12113        ContactManifold scratch = partPair.Manifold;
 48114        for (int i = 0; i < scratch.Count; i++)
 115        {
 12116            ManifoldContact contact = scratch[i];
 12117            if (addInPartOrder)
 118            {
 11119                ownerPair.Manifold.AddContact(
 11120                    contact.AnchorA,
 11121                    contact.AnchorB,
 11122                    contact.Depth,
 11123                    contact.Normal,
 11124                    partPair.ColliderA.Material,
 11125                    partPair.ColliderB.Material,
 11126                    contact.DepthIsClamped,
 11127                    featureNamespaceA,
 11128                    featureNamespaceB);
 11129                continue;
 130            }
 131
 1132            ownerPair.Manifold.AddContact(
 1133                contact.AnchorB,
 1134                contact.AnchorA,
 1135                contact.Depth,
 1136                -contact.Normal,
 1137                partPair.ColliderB.Material,
 1138                partPair.ColliderA.Material,
 1139                contact.DepthIsClamped,
 1140                featureNamespaceB,
 1141                featureNamespaceA);
 142        }
 12143    }
 144
 145    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 146    private static void OrderPartPairForDetection(
 147        LSCollider first,
 148        int firstFeatureNamespace,
 149        LSCollider second,
 150        int secondFeatureNamespace,
 151        out LSCollider colliderA,
 152        out int featureNamespaceA,
 153        out LSCollider colliderB,
 154        out int featureNamespaceB)
 155    {
 13156        if (first.Priority >= second.Priority)
 157        {
 11158            colliderA = first;
 11159            featureNamespaceA = firstFeatureNamespace;
 11160            colliderB = second;
 11161            featureNamespaceB = secondFeatureNamespace;
 11162            return;
 163        }
 164
 2165        colliderA = second;
 2166        featureNamespaceA = secondFeatureNamespace;
 2167        colliderB = first;
 2168        featureNamespaceB = firstFeatureNamespace;
 2169    }
 170
 171    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 172    private static bool BoundsOverlapInclusive(LSCollider colliderA, LSCollider colliderB)
 173    {
 24174        return colliderA.BoundsMin.X <= colliderB.BoundsMax.X
 24175            && colliderA.BoundsMax.X >= colliderB.BoundsMin.X
 24176            && colliderA.BoundsMin.Y <= colliderB.BoundsMax.Y
 24177            && colliderA.BoundsMax.Y >= colliderB.BoundsMin.Y
 24178            && colliderA.BoundsMin.Z <= colliderB.BoundsMax.Z
 24179            && colliderA.BoundsMax.Z >= colliderB.BoundsMin.Z;
 180    }
 181
 182    #endregion
 183
 184}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/3D/CollisionDetection.Cone.cs

#LineLine coverage
 1//=======================================================================
 2// CollisionDetection.Cone.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 SwiftCollections.Query;
 12using System.Runtime.CompilerServices;
 13
 14namespace Gravitas.CollisionHandling;
 15
 16public static partial class CollisionDetection
 17{
 18    private static bool DoConeSphereCheck(CollisionWorkItem pair)
 19    {
 720        var cone = (LSConeCollider)pair.ColliderA;
 721        var sphere = (LSSphereCollider)pair.ColliderB;
 22
 723        if (!FixedSegment.TryGetClosestCenteredFiniteConeSurfaceAnchor(
 724                sphere.Center,
 725                cone.Center,
 726                cone.Rotation,
 727                Vector3d.Up,
 728                cone.Height,
 729                cone.ScaledRadius,
 730                Vector3d.Right,
 731                out FixedPointAnchor coneAnchor,
 732                out Vector3d outwardNormal,
 733                out Fixed64 signedDistance))
 34        {
 135            return false;
 36        }
 637        if (signedDistance > sphere.ScaledRadius)
 138            return false;
 39
 540        Vector3d normal = signedDistance < Fixed64.Zero
 541            ? -outwardNormal
 542            : outwardNormal;
 543        ResolvePenetrationDepth(
 544            sphere.ScaledRadius,
 545            signedDistance,
 546            out Fixed64 penetrationDepth,
 547            out bool depthIsClamped);
 548        pair.Manifold.SetContact(
 549            new ContactAnchor(coneAnchor),
 550            new ContactAnchor(
 551                FixedSegment.GetCenteredCapsuleSupportAnchor(
 552                    sphere.Center,
 553                    sphere.Rotation,
 554                    Fixed64.Zero,
 555                    sphere.ScaledRadius,
 556                    -normal)),
 557            penetrationDepth,
 558            normal,
 559            depthIsClamped);
 60
 561        return true;
 62    }
 63
 64    private static bool DoConeConvexCheck(CollisionWorkItem pair)
 65    {
 1166        GetConeConvexColliders(pair, out LSConeCollider cone, out LSCollider convex);
 67
 1168        if (!ConvexColliderSupport.Intersects(cone, convex))
 169            return false;
 70
 1071        Vector3d normalConeToConvex = ResolveNormal(convex.Center - cone.Center);
 1072        normalConeToConvex = normalConeToConvex.Normalized;
 1073        FixedPointAnchor pointOnCone = ConvexColliderSupport.GetSupportAnchor(
 1074            cone,
 1075            normalConeToConvex,
 1076            Vector3d.Zero);
 1077        FixedPointAnchor pointOnConvex = ConvexColliderSupport.GetSupportAnchor(
 1078            convex,
 1079            -normalConeToConvex,
 1080            Vector3d.Zero);
 1081        Fixed64 depth = pointOnCone.ProjectNonNegativeOffsetFrom(
 1082            pointOnConvex,
 1083            normalConeToConvex);
 84
 1085        SetContactInPairOrder(
 1086            pair,
 1087            cone,
 1088            new ContactAnchor(pointOnCone),
 1089            convex,
 1090            new ContactAnchor(pointOnConvex),
 1091            depth,
 1092            normalConeToConvex,
 1093            depthIsClamped: false);
 1094        return true;
 95    }
 96
 97    private static bool DoMeshConeCheck(CollisionWorkItem pair)
 98    {
 1399        var mesh = (LSMeshCollider)pair.ColliderA;
 13100        var cone = (LSConeCollider)pair.ColliderB;
 101
 13102        if (TryFindMeshConeTriangleContact(
 13103                mesh,
 13104                cone,
 13105                pair.Context.CollisionScratch.MeshTriangleCandidatesA,
 13106                out ContactAnchor meshAnchor,
 13107                out ContactAnchor coneAnchor,
 13108                out Vector3d normalMeshToCone,
 13109                out Fixed64 depth,
 13110                out bool depthIsClamped))
 111        {
 7112            pair.Manifold.SetContact(
 7113                meshAnchor,
 7114                coneAnchor,
 7115                depth,
 7116                normalMeshToCone,
 7117                depthIsClamped);
 7118            return true;
 119        }
 120
 6121        if (mesh.Mode == MeshColliderMode.Concave || !ConvexColliderSupport.Intersects(mesh, cone))
 4122            return false;
 123
 2124        normalMeshToCone = ResolveNormal(cone.Center - mesh.Center);
 2125        FixedPointAnchor pointOnMesh = ConvexColliderSupport.GetSupportAnchor(
 2126            mesh,
 2127            normalMeshToCone,
 2128            Vector3d.Zero);
 2129        FixedPointAnchor pointOnCone = ConvexColliderSupport.GetSupportAnchor(
 2130            cone,
 2131            -normalMeshToCone,
 2132            Vector3d.Zero);
 2133        depth = pointOnMesh.ProjectNonNegativeOffsetFrom(
 2134            pointOnCone,
 2135            normalMeshToCone);
 136
 2137        pair.Manifold.SetContact(
 2138            new ContactAnchor(pointOnMesh),
 2139            new ContactAnchor(pointOnCone),
 2140            depth,
 2141            normalMeshToCone);
 2142        return true;
 143    }
 144
 145    private static bool TryFindMeshConeTriangleContact(
 146        LSMeshCollider mesh,
 147        LSConeCollider cone,
 148        SwiftCollections.SwiftList<int> triangleBuffer,
 149        out ContactAnchor meshAnchor,
 150        out ContactAnchor coneAnchor,
 151        out Vector3d normalMeshToCone,
 152        out Fixed64 depth,
 153        out bool depthIsClamped)
 154    {
 13155        meshAnchor = default;
 13156        coneAnchor = default;
 13157        normalMeshToCone = Vector3d.Zero;
 13158        depth = Fixed64.Zero;
 13159        depthIsClamped = false;
 160
 13161        mesh.GetTrianglesInBounds(new FixedBoundVolume(cone.BoundsMin, cone.BoundsMax), triangleBuffer);
 13162        bool found = false;
 13163        Fixed64 bestDepth = Fixed64.MaxValue;
 164
 52165        for (int i = 0; i < triangleBuffer.Count; i++)
 166        {
 13167            int triangleIndex = triangleBuffer[i];
 13168            mesh.Mesh.GetLocalTriangleVertices(
 13169                triangleIndex,
 13170                out Vector3d first,
 13171                out Vector3d second,
 13172                out Vector3d third);
 13173            Vector3d windingNormal = mesh.Mesh.GetFaceNormalWorld(triangleIndex);
 13174            var triangle = new FixedTriangle(first, second, third);
 13175            var coneCenterAnchor = new FixedPointAnchor(
 13176                cone.Center,
 13177                FixedQuaternion.Identity,
 13178                Vector3d.Zero);
 13179            if (!coneCenterAnchor.TryGetLocalPointIn(
 13180                    mesh.Mesh.Origin,
 13181                    mesh.Mesh.Rotation,
 13182                    out Vector3d localConeCenter))
 183            {
 184                continue;
 185            }
 186
 12187            Vector3d candidatePointOnMesh =
 12188                triangle.ClosestPoint(localConeCenter);
 12189            FixedPointAnchor candidateMeshAnchor =
 12190                mesh.Mesh.CreatePointAnchor(candidatePointOnMesh);
 12191            Vector3d candidateNormalMeshToCone =
 12192                OrientNormal(
 12193                    candidateMeshAnchor,
 12194                    coneCenterAnchor,
 12195                    windingNormal).Normalized;
 196            FixedPointAnchor candidateConeAnchor;
 197            Fixed64 candidateDepth;
 198            bool candidateDepthIsClamped;
 199            // The closest point belongs to a triangle already admitted into
 200            // the cone's candidate bounds, so its cone-frame offset is finite.
 12201            _ = candidateMeshAnchor.TryGetLocalPointIn(
 12202                cone.Center,
 12203                cone.Rotation,
 12204                out Vector3d localPointInCone);
 12205            bool closestMeshPointInsideCone =
 12206                FixedSegment.ContainsPointInCenteredFiniteCone(
 12207                    localPointInCone,
 12208                    Vector3d.Zero,
 12209                    Vector3d.Up,
 12210                    cone.Height,
 12211                    cone.ScaledRadius);
 12212            if (closestMeshPointInsideCone)
 213            {
 214                // Exact containment proves the centered canonical surface
 215                // offset remains inside the admitted radius and height.
 6216                _ = FixedSegment.TryGetClosestCenteredFiniteConeSurfaceOffset(
 6217                    localPointInCone,
 6218                    Vector3d.Zero,
 6219                    Vector3d.Up,
 6220                    cone.Height,
 6221                    cone.ScaledRadius,
 6222                    Vector3d.Right,
 6223                    out Vector3d localConePoint,
 6224                    out _,
 6225                    out Fixed64 signedDistance);
 226
 6227                candidateDepth = -signedDistance;
 6228                candidateDepthIsClamped = false;
 6229                candidateConeAnchor = new FixedPointAnchor(
 6230                    cone.Center,
 6231                    cone.Rotation,
 6232                    localConePoint);
 233            }
 6234            else if (triangle.TryGetCenteredFiniteConeSupportContact(
 6235                         mesh.Mesh.Origin,
 6236                         mesh.Mesh.Rotation,
 6237                         cone.Center,
 6238                         cone.Rotation,
 6239                         cone.Height,
 6240                         cone.ScaledRadius,
 6241                         -candidateNormalMeshToCone,
 6242                         candidateNormalMeshToCone,
 6243                         out FixedContactAnchors contact))
 244            {
 4245                candidateMeshAnchor = contact.FirstAnchor;
 4246                candidateConeAnchor = contact.SecondAnchor;
 4247                candidateDepth = contact.Depth;
 4248                candidateDepthIsClamped = contact.DepthIsClamped;
 249            }
 250            else
 251            {
 252                continue;
 253            }
 254
 10255            if (found && candidateDepth >= bestDepth)
 256                continue;
 257
 7258            found = true;
 7259            bestDepth = candidateDepth;
 7260            meshAnchor = new ContactAnchor(candidateMeshAnchor);
 7261            coneAnchor = new ContactAnchor(candidateConeAnchor);
 7262            normalMeshToCone = candidateNormalMeshToCone;
 7263            depth = candidateDepth;
 7264            depthIsClamped = candidateDepthIsClamped;
 265        }
 266
 13267        return found;
 268    }
 269
 270    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 271    private static void GetConeConvexColliders(
 272        CollisionWorkItem pair,
 273        out LSConeCollider cone,
 274        out LSCollider convex)
 275    {
 11276        if (pair.ColliderA is LSConeCollider coneA && ConvexColliderSupport.IsSupported(pair.ColliderB))
 277        {
 5278            cone = coneA;
 5279            convex = pair.ColliderB;
 5280            return;
 281        }
 282
 6283        cone = (LSConeCollider)pair.ColliderB;
 6284        convex = pair.ColliderA;
 6285    }
 286
 287    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 288    private static Vector3d OrientNormal(
 289        in FixedPointAnchor source,
 290        in FixedPointAnchor target,
 291        Vector3d normal)
 292    {
 12293        Vector3d resolved = normal.Normalized;
 12294        return source.ProjectNonNegativeOffsetFrom(target, resolved)
 12295                > Fixed64.Zero
 12296            ? -resolved
 12297            : resolved;
 298    }
 299
 300}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/3D/CollisionDetection.cs

#LineLine coverage
 1//=======================================================================
 2// CollisionDetection.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 Gravitas.Colliders;
 9
 10namespace Gravitas.CollisionHandling;
 11
 12/// <summary>Provides deterministic 3D narrow-phase collision checks.</summary>
 13public static partial class CollisionDetection
 14{
 15    /// <summary>Rebuilds and tests the contact manifold for a collision pair.</summary>
 16    public static bool DoCollisionCheck(CollisionPair pair)
 17    {
 689918        pair.Manifold.BeginUpdate(pair.Context.FrameCount);
 689919        return DoCollisionCheck(CollisionWorkItem.Create(pair));
 20    }
 21
 22    internal static bool DoCollisionCheck(CollisionWorkItem pair)
 23    {
 1005224        return pair.CollisionType switch
 1005225        {
 107226            CollisionType.Sphere_Sphere => DoSpheresCheck(pair),
 21127            CollisionType.Capsule_Sphere => DoCapsuleSphereCheck(pair),
 22128            CollisionType.Capsule_Capsule => DoCapsulesCheck(pair),
 469029            CollisionType.Cuboid_Sphere => DoCuboidSphereCheck(pair),
 22530            CollisionType.AABox_Capsule => DoCuboidCapsuleCheck(pair),
 22031            CollisionType.OBBox_Capsule => DoCuboidCapsuleCheck(pair),
 70832            CollisionType.Cuboid_Cuboid => DoCuboidsCheck(pair),
 21733            CollisionType.Cylinder_Sphere => DoCylinderSphereCheck(pair),
 21434            CollisionType.Cylinder_Capsule => DoCylinderCapsuleCheck(pair),
 21635            CollisionType.Cylinder_Cylinder => DoCylindersCheck(pair),
 936            CollisionType.Cuboid_Cylinder => DoCuboidCylinderCheck(pair),
 737            CollisionType.Cone_Sphere => DoConeSphereCheck(pair),
 1138            CollisionType.Cone_Convex => DoConeConvexCheck(pair),
 1239            CollisionType.Mesh_Sphere => DoMeshSphereCheck(pair),
 22340            CollisionType.Mesh_Capsule => DoMeshCapsuleCheck(pair),
 68941            CollisionType.Mesh_Cuboid => DoMeshCuboidCheck(pair),
 42842            CollisionType.Mesh_Cylinder => DoMeshCylinderCheck(pair),
 1343            CollisionType.Mesh_Cone => DoMeshConeCheck(pair),
 65244            CollisionType.Mesh_Mesh => DoMeshesCheck(pair),
 1345            CollisionType.Compound => DoCompoundCheck(pair),
 146            _ => false,
 1005247        };
 48    }
 49
 50}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/3D/CollisionDetection.Cuboid.cs

#LineLine coverage
 1//=======================================================================
 2// CollisionDetection.Cuboid.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;
 11
 12namespace Gravitas.CollisionHandling;
 13
 14public static partial class CollisionDetection
 15{
 16    #region Cuboid
 17
 18    private static bool DoCuboidSphereCheck(CollisionWorkItem pair)
 19    {
 469020        var cuboid = (LSCuboidCollider)pair.ColliderA;
 469021        if (!cuboid.OrientedBox.TryGetSphereContact(
 469022                pair.ColliderB.Center,
 469023                pair.ColliderB.Rotation,
 469024                pair.ColliderB.ScaledRadius,
 469025                out FixedContactAnchors contact))
 26        {
 200727            return false;
 28        }
 29
 268330        pair.Manifold.SetContact(
 268331            new ContactAnchor(contact.FirstAnchor),
 268332            new ContactAnchor(contact.SecondAnchor),
 268333            contact.Depth,
 268334            contact.Normal,
 268335            contact.DepthIsClamped);
 268336        return true;
 37    }
 38
 39    private static bool DoCuboidCapsuleCheck(CollisionWorkItem pair)
 40    {
 44541        var cuboid = (LSCuboidCollider)pair.ColliderA;
 44542        var capsule = (LSCapsuleCollider)pair.ColliderB;
 43
 44544        if (!cuboid.OrientedBox.TryGetCenteredCapsuleContact(
 44545                capsule.Center,
 44546                capsule.Rotation,
 44547                Vector3d.Up,
 44548                capsule.AxisLength,
 44549                capsule.ScaledRadius,
 44550                out FixedContactAnchors contact))
 51        {
 952            return false;
 53        }
 54
 43655        pair.Manifold.SetContact(
 43656            new ContactAnchor(contact.FirstAnchor),
 43657            new ContactAnchor(contact.SecondAnchor),
 43658            contact.Depth,
 43659            contact.Normal,
 43660            contact.DepthIsClamped);
 43661        return true;
 62    }
 63
 64    /// <summary>
 65    /// Checks for collisions between two poly-poly colliders.
 66    /// </summary>
 67    /// <returns>true if a collision is detected, false otherwise.</returns>
 68    private static bool DoCuboidsCheck(CollisionWorkItem pair)
 69    {
 70870        var cuboidA = (LSCuboidCollider)pair.ColliderA;
 70871        var cuboidB = (LSCuboidCollider)pair.ColliderB;
 72
 70873        if (cuboidA.Shape == ColliderType.AABox
 70874            && cuboidB.Shape == ColliderType.AABox
 70875            && CanBuildAxisAlignedManifold(cuboidA)
 70876            && CanBuildAxisAlignedManifold(cuboidB))
 77        {
 24178            return TryBuildAxisAlignedCuboidManifold(pair, cuboidA, cuboidB);
 79        }
 80
 46781        if (!cuboidA.OrientedBox.TryGetContact(
 46782                cuboidB.OrientedBox,
 46783                out FixedContactAnchors contact))
 84        {
 585            return false;
 86        }
 87
 46288        pair.Manifold.SetContact(
 46289            new ContactAnchor(contact.FirstAnchor),
 46290            new ContactAnchor(contact.SecondAnchor),
 46291            contact.Depth,
 46292            contact.Normal,
 46293            contact.DepthIsClamped);
 46294        return true;
 95    }
 96
 97    private static bool CanBuildAxisAlignedManifold(LSCuboidCollider cuboid)
 98    {
 48399        Vector3d center = cuboid.Center;
 483100        Vector3d halfExtents = cuboid.OrientedBox.HalfExtents;
 101        // The four-point fast path materializes both bounds and subtracts their
 102        // overlap widths. Wider conceptual boxes use the exact relative path.
 483103        return Vector3d.TryAdd(center, halfExtents, out _)
 483104            && Vector3d.TrySubtract(center, halfExtents, out _)
 483105            && Vector3d.TryAdd(halfExtents, halfExtents, out _);
 106    }
 107
 108    private static bool TryBuildAxisAlignedCuboidManifold(
 109        CollisionWorkItem pair,
 110        LSCuboidCollider cuboidA,
 111        LSCuboidCollider cuboidB)
 112    {
 241113        Fixed64 overlapX = FixedMath.Min(cuboidA.BoundsMax.X, cuboidB.BoundsMax.X) - FixedMath.Max(cuboidA.BoundsMin.X, 
 241114        Fixed64 overlapY = FixedMath.Min(cuboidA.BoundsMax.Y, cuboidB.BoundsMax.Y) - FixedMath.Max(cuboidA.BoundsMin.Y, 
 241115        Fixed64 overlapZ = FixedMath.Min(cuboidA.BoundsMax.Z, cuboidB.BoundsMax.Z) - FixedMath.Max(cuboidA.BoundsMin.Z, 
 116
 241117        if (overlapX < Fixed64.Zero || overlapY < Fixed64.Zero || overlapZ < Fixed64.Zero)
 2118            return false;
 119
 239120        Vector3d centerDelta = cuboidB.Center - cuboidA.Center;
 239121        int axis = 0;
 239122        Fixed64 depth = overlapX;
 239123        if (overlapY < depth)
 124        {
 21125            axis = 1;
 21126            depth = overlapY;
 127        }
 128
 239129        if (overlapZ < depth)
 130        {
 2131            axis = 2;
 2132            depth = overlapZ;
 133        }
 134
 239135        Vector3d normal = axis switch
 239136        {
 216137            0 => new Vector3d(centerDelta.X < Fixed64.Zero ? -Fixed64.One : Fixed64.One, Fixed64.Zero, Fixed64.Zero),
 21138            1 => new Vector3d(Fixed64.Zero, centerDelta.Y < Fixed64.Zero ? -Fixed64.One : Fixed64.One, Fixed64.Zero),
 2139            _ => new Vector3d(Fixed64.Zero, Fixed64.Zero, centerDelta.Z < Fixed64.Zero ? -Fixed64.One : Fixed64.One)
 239140        };
 141
 239142        AddAxisAlignedCuboidContacts(pair.Manifold, cuboidA, cuboidB, axis, depth, normal);
 239143        return pair.Manifold.HasContact;
 144    }
 145
 146    private static void AddAxisAlignedCuboidContacts(
 147        ContactManifold manifold,
 148        LSCuboidCollider cuboidA,
 149        LSCuboidCollider cuboidB,
 150        int axis,
 151        Fixed64 depth,
 152        Vector3d normal)
 153    {
 239154        Fixed64 minX = FixedMath.Max(cuboidA.BoundsMin.X, cuboidB.BoundsMin.X);
 239155        Fixed64 maxX = FixedMath.Min(cuboidA.BoundsMax.X, cuboidB.BoundsMax.X);
 239156        Fixed64 minY = FixedMath.Max(cuboidA.BoundsMin.Y, cuboidB.BoundsMin.Y);
 239157        Fixed64 maxY = FixedMath.Min(cuboidA.BoundsMax.Y, cuboidB.BoundsMax.Y);
 239158        Fixed64 minZ = FixedMath.Max(cuboidA.BoundsMin.Z, cuboidB.BoundsMin.Z);
 239159        Fixed64 maxZ = FixedMath.Min(cuboidA.BoundsMax.Z, cuboidB.BoundsMax.Z);
 160
 161        switch (axis)
 162        {
 163            case 0:
 164                {
 216165                    Fixed64 x = normal.X > Fixed64.Zero ? cuboidA.BoundsMax.X : cuboidA.BoundsMin.X;
 216166                    AddCuboidContact(manifold, new Vector3d(x, minY, minZ), normal, depth);
 216167                    AddCuboidContact(manifold, new Vector3d(x, minY, maxZ), normal, depth);
 216168                    AddCuboidContact(manifold, new Vector3d(x, maxY, minZ), normal, depth);
 216169                    AddCuboidContact(manifold, new Vector3d(x, maxY, maxZ), normal, depth);
 216170                    break;
 171                }
 172            case 1:
 173                {
 21174                    Fixed64 y = normal.Y > Fixed64.Zero ? cuboidA.BoundsMax.Y : cuboidA.BoundsMin.Y;
 21175                    AddCuboidContact(manifold, new Vector3d(minX, y, minZ), normal, depth);
 21176                    AddCuboidContact(manifold, new Vector3d(minX, y, maxZ), normal, depth);
 21177                    AddCuboidContact(manifold, new Vector3d(maxX, y, minZ), normal, depth);
 21178                    AddCuboidContact(manifold, new Vector3d(maxX, y, maxZ), normal, depth);
 21179                    break;
 180                }
 181            default:
 182                {
 2183                    Fixed64 z = normal.Z > Fixed64.Zero ? cuboidA.BoundsMax.Z : cuboidA.BoundsMin.Z;
 2184                    AddCuboidContact(manifold, new Vector3d(minX, minY, z), normal, depth);
 2185                    AddCuboidContact(manifold, new Vector3d(minX, maxY, z), normal, depth);
 2186                    AddCuboidContact(manifold, new Vector3d(maxX, minY, z), normal, depth);
 2187                    AddCuboidContact(manifold, new Vector3d(maxX, maxY, z), normal, depth);
 188                    break;
 189                }
 190        }
 2191    }
 192
 193    private static void AddCuboidContact(ContactManifold manifold, Vector3d pointA, Vector3d normal, Fixed64 depth)
 194    {
 956195        Vector3d pointB = pointA - normal * depth;
 956196        manifold.AddContact(pointA, pointB, depth, normal);
 956197    }
 198
 199    #endregion
 200
 201}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/3D/CollisionDetection.Cylinder.cs

#LineLine coverage
 1//=======================================================================
 2// CollisionDetection.Cylinder.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 System;
 12using System.Runtime.CompilerServices;
 13
 14namespace Gravitas.CollisionHandling;
 15
 16public static partial class CollisionDetection
 17{
 18    #region Cylinder
 19
 20    private static bool DoCylinderSphereCheck(CollisionWorkItem pair)
 21    {
 21722        var cylinder = (LSCylinderCollider)pair.ColliderA;
 21723        var sphere = (LSSphereCollider)pair.ColliderB;
 24
 21725        if (!FixedSegment.TryGetClosestCenteredFiniteCylinderSurfaceAnchor(
 21726                sphere.Center,
 21727                cylinder.Center,
 21728                cylinder.Rotation,
 21729                Vector3d.Up,
 21730                cylinder.Height,
 21731                cylinder.ScaledRadius,
 21732                Vector3d.Right,
 21733                out FixedPointAnchor cylinderAnchor,
 21734                out Vector3d outwardNormal,
 21735                out Fixed64 signedDistance))
 36        {
 137            return false;
 38        }
 21639        if (signedDistance > sphere.ScaledRadius)
 140            return false;
 41
 21542        Vector3d normal = signedDistance < Fixed64.Zero
 21543            ? -outwardNormal
 21544            : outwardNormal;
 21545        ResolvePenetrationDepth(
 21546            sphere.ScaledRadius,
 21547            signedDistance,
 21548            out Fixed64 penetrationDepth,
 21549            out bool depthIsClamped);
 21550        pair.Manifold.SetContact(
 21551            new ContactAnchor(cylinderAnchor),
 21552            new ContactAnchor(
 21553                FixedSegment.GetCenteredCapsuleSupportAnchor(
 21554                    sphere.Center,
 21555                    sphere.Rotation,
 21556                    Fixed64.Zero,
 21557                    sphere.ScaledRadius,
 21558                    -normal)),
 21559            penetrationDepth,
 21560            normal,
 21561            depthIsClamped);
 62
 21563        return true;
 64    }
 65
 66    private static bool DoCylinderCapsuleCheck(CollisionWorkItem pair)
 67    {
 68        LSCylinderCollider cylinder;
 69        LSCapsuleCollider capsule;
 21470        if (pair.ColliderA is LSCylinderCollider cylinderA)
 71        {
 21372            cylinder = cylinderA;
 21373            capsule = (LSCapsuleCollider)pair.ColliderB;
 74        }
 75        else
 76        {
 177            cylinder = (LSCylinderCollider)pair.ColliderB;
 178            capsule = (LSCapsuleCollider)pair.ColliderA;
 79        }
 80
 21481        if (!FixedSegment.TryGetCenteredFiniteCylinderCapsuleContact(
 21482                cylinder.Center,
 21483                cylinder.Rotation,
 21484                Vector3d.Up,
 21485                cylinder.Height,
 21486                cylinder.ScaledRadius,
 21487                capsule.Center,
 21488                capsule.Rotation,
 21489                Vector3d.Up,
 21490                capsule.AxisLength,
 21491                capsule.ScaledRadius,
 21492                out FixedContactAnchors contact))
 93        {
 494            return false;
 95        }
 96
 21097        SetContactInPairOrder(
 21098            pair,
 21099            cylinder,
 210100            new ContactAnchor(contact.FirstAnchor),
 210101            capsule,
 210102            new ContactAnchor(contact.SecondAnchor),
 210103            contact.Depth,
 210104            contact.Normal,
 210105            contact.DepthIsClamped);
 106
 210107        return true;
 108    }
 109
 110    private static bool DoCylindersCheck(CollisionWorkItem pair)
 111    {
 216112        var cylinderA = (LSCylinderCollider)pair.ColliderA;
 216113        var cylinderB = (LSCylinderCollider)pair.ColliderB;
 114
 216115        if (!FixedSegment.TryGetCenteredFiniteCylindersContact(
 216116                cylinderA.Center,
 216117                cylinderA.Rotation,
 216118                Vector3d.Up,
 216119                cylinderA.Height,
 216120                cylinderA.ScaledRadius,
 216121                cylinderB.Center,
 216122                cylinderB.Rotation,
 216123                Vector3d.Up,
 216124                cylinderB.Height,
 216125                cylinderB.ScaledRadius,
 216126                out FixedContactAnchors contact))
 127        {
 4128            return false;
 129        }
 130
 212131        if (TryAddCylinderCylinderCapContacts(
 212132                pair,
 212133                cylinderA,
 212134                cylinderB,
 212135                contact))
 136        {
 3137            return true;
 138        }
 139
 209140        pair.Manifold.SetContact(
 209141            new ContactAnchor(contact.FirstAnchor),
 209142            new ContactAnchor(contact.SecondAnchor),
 209143            contact.Depth,
 209144            contact.Normal,
 209145            contact.DepthIsClamped);
 146
 209147        return true;
 148    }
 149
 150    private static bool DoCuboidCylinderCheck(CollisionWorkItem pair)
 151    {
 9152        var cuboid = (LSCuboidCollider)pair.ColliderA;
 9153        var cylinder = (LSCylinderCollider)pair.ColliderB;
 154
 9155        Span<FixedContactLocalPoints> capFaceContacts =
 9156            stackalloc FixedContactLocalPoints[ContactManifold.MaxContactCount];
 9157        if (!cuboid.OrientedBox.TryGetCenteredCylinderContact(
 9158                cylinder.Center,
 9159                cylinder.Rotation,
 9160                Vector3d.Up,
 9161                cylinder.Height,
 9162                cylinder.ScaledRadius,
 9163                capFaceContacts,
 9164                out FixedContactAnchors contact,
 9165                out int capFaceContactCount))
 166        {
 4167            return false;
 168        }
 169
 5170        if (capFaceContactCount > 0)
 171        {
 10172            for (int index = 0; index < capFaceContactCount; index++)
 173            {
 4174                FixedContactLocalPoints capContact = capFaceContacts[index];
 4175                pair.Manifold.AddContact(
 4176                    new ContactAnchor(
 4177                        contact.FirstAnchor.Origin,
 4178                        contact.FirstAnchor.Rotation,
 4179                        capContact.FirstLocalPoint),
 4180                    new ContactAnchor(
 4181                        contact.SecondAnchor.Origin,
 4182                        contact.SecondAnchor.Rotation,
 4183                        capContact.SecondLocalPoint),
 4184                    contact.Depth,
 4185                    contact.Normal,
 4186                    contact.DepthIsClamped);
 187            }
 1188            return true;
 189        }
 190
 4191        pair.Manifold.SetContact(
 4192            new ContactAnchor(contact.FirstAnchor),
 4193            new ContactAnchor(contact.SecondAnchor),
 4194            contact.Depth,
 4195            contact.Normal,
 4196            contact.DepthIsClamped);
 4197        return true;
 198    }
 199
 200    private static bool TryAddCylinderCylinderCapContacts(
 201        CollisionWorkItem pair,
 202        LSCylinderCollider cylinderA,
 203        LSCylinderCollider cylinderB,
 204        FixedContactAnchors contact)
 205    {
 212206        if (contact.FirstAnchor.LocalDisplacement != Vector3d.Zero
 212207            || contact.SecondAnchor.LocalDisplacement != Vector3d.Zero)
 208        {
 209209            return false;
 210        }
 211
 3212        CylinderContactGeometry.GetCapBasis(cylinderA, out Vector3d tangentA, out Vector3d tangentB);
 3213        int initialCount = pair.Manifold.Count;
 3214        TryAddCylinderCylinderCapContact(pair, cylinderA, cylinderB, tangentA, contact);
 3215        TryAddCylinderCylinderCapContact(pair, cylinderA, cylinderB, -tangentA, contact);
 3216        TryAddCylinderCylinderCapContact(pair, cylinderA, cylinderB, tangentB, contact);
 3217        TryAddCylinderCylinderCapContact(pair, cylinderA, cylinderB, -tangentB, contact);
 3218        return pair.Manifold.Count > initialCount;
 219    }
 220
 221    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 222    private static void TryAddCylinderCylinderCapContact(
 223        CollisionWorkItem pair,
 224        LSCylinderCollider cylinderA,
 225        LSCylinderCollider cylinderB,
 226        Vector3d radialDirection,
 227        FixedContactAnchors contact)
 228    {
 12229        pair.Manifold.AddContact(
 12230            new ContactAnchor(ConvexColliderSupport.GetSupportAnchor(
 12231                cylinderA,
 12232                contact.Normal + radialDirection,
 12233                Vector3d.Zero)),
 12234            new ContactAnchor(ConvexColliderSupport.GetSupportAnchor(
 12235                cylinderB,
 12236                -contact.Normal + radialDirection,
 12237                Vector3d.Zero)),
 12238            contact.Depth,
 12239            contact.Normal,
 12240            contact.DepthIsClamped);
 12241    }
 242
 243    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 244    private static void ResolvePenetrationDepth(
 245        Fixed64 radius,
 246        Fixed64 signedDistance,
 247        out Fixed64 depth,
 248        out bool depthIsClamped)
 249    {
 220250        if (Fixed64.TrySubtract(radius, signedDistance, out depth))
 251        {
 219252            depthIsClamped = false;
 219253            return;
 254        }
 255
 1256        depth = Fixed64.MaxValue;
 1257        depthIsClamped = true;
 1258    }
 259
 260    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 261    private static Vector3d ResolveNormal(Vector3d delta) =>
 12262        delta.MagnitudeSquared > Fixed64.Epsilon
 12263            ? delta.Normalized
 12264            : Vector3d.Right;
 265
 266    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 267    private static void SetContactInPairOrder(
 268        CollisionWorkItem pair,
 269        LSCollider first,
 270        ContactAnchor anchorOnFirst,
 271        LSCollider second,
 272        ContactAnchor anchorOnSecond,
 273        Fixed64 depth,
 274        Vector3d normalFirstToSecond,
 275        bool depthIsClamped)
 276    {
 220277        if (ReferenceEquals(pair.ColliderA, first))
 278        {
 214279            pair.Manifold.SetContact(
 214280                anchorOnFirst,
 214281                anchorOnSecond,
 214282                depth,
 214283                normalFirstToSecond,
 214284                depthIsClamped);
 214285            return;
 286        }
 287
 6288        pair.Manifold.SetContact(
 6289            anchorOnSecond,
 6290            anchorOnFirst,
 6291            depth,
 6292            -normalFirstToSecond,
 6293            depthIsClamped);
 6294    }
 295
 296    #endregion
 297
 298}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/3D/CollisionDetection.Mesh.cs

#LineLine coverage
 1//=======================================================================
 2// CollisionDetection.Mesh.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;
 11
 12namespace Gravitas.CollisionHandling;
 13
 14public static partial class CollisionDetection
 15{
 16    #region Mesh
 17
 18    private static bool DoMeshSphereCheck(CollisionWorkItem pair)
 19    {
 1220        var meshCollider = (LSMeshCollider)pair.ColliderA;
 1221        var sphere = (LSSphereCollider)pair.ColliderB;
 22
 1223        return MeshTriangleContactGenerator.TryBuildMeshSphereManifold(
 1224            pair,
 1225            meshCollider,
 1226            sphere,
 1227            pair.Context.CollisionScratch.MeshTriangleCandidatesA);
 28    }
 29
 30    private static bool DoMeshCapsuleCheck(CollisionWorkItem pair)
 31    {
 22332        var mesh = (LSMeshCollider)pair.ColliderA;
 22333        var capsule = (LSCapsuleCollider)pair.ColliderB;
 34
 22335        if (mesh.Mode == MeshColliderMode.Convex)
 36        {
 21837            PhysicsMesh physicsMesh = mesh.Mesh;
 21838            bool hasContact =
 21839                FixedConvexHullRelations
 21840                    .TryGetCenteredCapsuleContact(
 21841                        physicsMesh.Origin,
 21842                        physicsMesh.Rotation,
 21843                        physicsMesh.ScaledLocalVertices,
 21844                        physicsMesh.Triangles,
 21845                        physicsMesh.ConvexSatEdgeVertexPairs,
 21846                        capsule.Center,
 21847                        capsule.Rotation,
 21848                        Vector3d.Up,
 21849                        capsule.AxisLength,
 21850                        capsule.ScaledRadius,
 21851                        out FixedContactAnchors contact);
 21852            if (!hasContact)
 353                return false;
 21554            pair.Manifold.SetContact(
 21555                new ContactAnchor(contact.FirstAnchor),
 21556                new ContactAnchor(contact.SecondAnchor),
 21557                contact.Depth,
 21558                contact.Normal,
 21559                contact.DepthIsClamped);
 21560            return true;
 61        }
 62
 563        return MeshTriangleContactGenerator.TryBuildMeshCapsuleManifold(
 564            pair,
 565            mesh,
 566            capsule,
 567            pair.Context.CollisionScratch.MeshTriangleCandidatesA);
 68    }
 69
 70    private static bool DoMeshCuboidCheck(CollisionWorkItem pair)
 71    {
 68972        var mesh = (LSMeshCollider)pair.ColliderA;
 68973        var cuboid = (LSCuboidCollider)pair.ColliderB;
 74
 68975        if (MeshTriangleContactGenerator.TryBuildMeshCuboidManifold(
 68976            pair,
 68977            mesh,
 68978            cuboid,
 68979            pair.Context.CollisionScratch.MeshTriangleCandidatesA))
 80        {
 43481            return true;
 82        }
 83
 25584        if (mesh.Mode == MeshColliderMode.Concave)
 285            return false;
 86
 25387        PhysicsMesh physicsMesh = mesh.Mesh;
 25388        if (!cuboid.OrientedBox.TryGetConvexHullContact(
 25389                mesh.Center,
 25390                physicsMesh.Rotation,
 25391                physicsMesh.ScaledLocalVertices,
 25392                physicsMesh.Triangles,
 25393                physicsMesh.ConvexSatEdgeVertexPairs,
 25394                out FixedContactAnchors contact))
 95        {
 4296            return false;
 97        }
 98
 21199        pair.Manifold.SetContact(
 211100            new ContactAnchor(contact.SecondAnchor),
 211101            new ContactAnchor(contact.FirstAnchor),
 211102            contact.Depth,
 211103            -contact.Normal,
 211104            contact.DepthIsClamped);
 105
 211106        return true;
 107    }
 108
 109    private static bool DoMeshCylinderCheck(CollisionWorkItem pair)
 110    {
 428111        var mesh = (LSMeshCollider)pair.ColliderA;
 428112        var cylinder = (LSCylinderCollider)pair.ColliderB;
 113
 428114        return MeshTriangleContactGenerator.TryBuildMeshCylinderManifold(
 428115            pair,
 428116            mesh,
 428117            cylinder,
 428118            pair.Context.CollisionScratch.MeshTriangleCandidatesA);
 119    }
 120
 121    /// <summary>
 122    /// Checks if two mesh colliders intersect and sets the contact point if they do.
 123    /// </summary>
 124    /// <param name="pair">The pair of colliders to test for collision.</param>
 125    /// <returns>true if the colliders intersect; otherwise, false.</returns>
 126    private static bool DoMeshesCheck(CollisionWorkItem pair)
 127    {
 652128        var meshA = (LSMeshCollider)pair.ColliderA;
 652129        var meshB = (LSMeshCollider)pair.ColliderB;
 652130        if (meshA.Mode == MeshColliderMode.Concave
 652131            || meshB.Mode == MeshColliderMode.Concave)
 132        {
 645133            return MeshTriangleContactGenerator.TryBuildMeshMeshManifold(
 645134                pair,
 645135                meshA,
 645136                meshB,
 645137                pair.Context.CollisionScratch.MeshTriangleCandidatesA,
 645138                pair.Context.CollisionScratch.MeshTriangleCandidatesB);
 139        }
 140
 7141        PhysicsMesh physicsMeshA = meshA.Mesh;
 7142        PhysicsMesh physicsMeshB = meshB.Mesh;
 7143        bool hasContact = FixedConvexHullRelations.TryGetContact(
 7144            meshA.Center,
 7145            physicsMeshA.Rotation,
 7146            physicsMeshA.ScaledLocalVertices,
 7147            physicsMeshA.Triangles,
 7148            physicsMeshA.ConvexSatEdgeVertexPairs,
 7149            meshB.Center,
 7150            physicsMeshB.Rotation,
 7151            physicsMeshB.ScaledLocalVertices,
 7152            physicsMeshB.Triangles,
 7153            physicsMeshB.ConvexSatEdgeVertexPairs,
 7154            out FixedContactAnchors contact);
 7155        if (!hasContact)
 4156            return false;
 3157        if (contact.Depth <= Fixed64.Zero)
 1158            return false;
 159
 2160        pair.Manifold.SetContact(
 2161            new ContactAnchor(contact.FirstAnchor),
 2162            new ContactAnchor(contact.SecondAnchor),
 2163            contact.Depth,
 2164            contact.Normal,
 2165            contact.DepthIsClamped);
 166
 2167        return true;
 168    }
 169
 170    #endregion
 171}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/3D/CollisionDetection.Sphere.cs

#LineLine coverage
 1//=======================================================================
 2// CollisionDetection.Sphere.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;
 10
 11namespace Gravitas.CollisionHandling;
 12
 13public static partial class CollisionDetection
 14{
 15    #region Sphere
 16
 17    private static bool DoSpheresCheck(CollisionWorkItem pair)
 18    {
 107219        if (!FixedSegment.TryGetCenteredCapsulesContact(
 107220                pair.ColliderA.Center,
 107221                pair.ColliderA.Rotation,
 107222                Vector3d.Up,
 107223                Fixed64.Zero,
 107224                pair.ColliderA.ScaledRadius,
 107225                pair.ColliderB.Center,
 107226                pair.ColliderB.Rotation,
 107227                Vector3d.Up,
 107228                Fixed64.Zero,
 107229                pair.ColliderB.ScaledRadius,
 107230                ResolveCenteredCapsuleFallback(pair.ColliderA.Center, pair.ColliderB.Center),
 107231                out FixedContactAnchors contact))
 32        {
 1533            return false;
 34        }
 35
 105736        pair.Manifold.SetContact(
 105737            new ContactAnchor(contact.FirstAnchor),
 105738            new ContactAnchor(contact.SecondAnchor),
 105739            contact.Depth,
 105740            contact.Normal,
 105741            contact.DepthIsClamped);
 105742        return true;
 43    }
 44
 45    #endregion
 46
 47}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/3D/CollisionWorkItem.cs

#LineLine coverage
 1//=======================================================================
 2// CollisionWorkItem.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 Gravitas.Colliders;
 9using System.Runtime.CompilerServices;
 10
 11namespace Gravitas.CollisionHandling;
 12
 13/// <summary>
 14/// Allocation-free narrow-phase work item used by real pairs and compound
 15/// collider internal part checks.
 16/// </summary>
 17internal readonly struct CollisionWorkItem
 18{
 19    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 20    public CollisionWorkItem(
 21        GravitasWorldContext context,
 22        LSCollider colliderA,
 23        LSCollider colliderB,
 24        CollisionType collisionType,
 25        ContactManifold manifold)
 26    {
 1005227        Context = context;
 1005228        ColliderA = colliderA;
 1005229        ColliderB = colliderB;
 1005230        CollisionType = collisionType;
 1005231        Manifold = manifold;
 1005232    }
 33
 34    public GravitasWorldContext Context { get; }
 35
 36    public LSCollider ColliderA { get; }
 37
 38    public LSCollider ColliderB { get; }
 39
 40    public CollisionType CollisionType { get; }
 41
 42    public ContactManifold Manifold { get; }
 43
 44    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 45    public static CollisionWorkItem Create(CollisionPair pair) =>
 689946        new(pair.Context, pair.ColliderA, pair.ColliderB, pair.CollisionType, pair.Manifold);
 47}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/3D/ConvexColliderSupport.cs

#LineLine coverage
 1//=======================================================================
 2// ConvexColliderSupport.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 System;
 12using System.Runtime.CompilerServices;
 13
 14namespace Gravitas.CollisionHandling;
 15
 16internal static class ConvexColliderSupport
 17{
 18    public static FixedPointAnchor GetSupportAnchor(
 19        LSCollider collider,
 20        Vector3d direction,
 21        Vector3d originOffset)
 22    {
 478723        Vector3d normal = ResolveSupportDirection(direction);
 478724        Vector3d localTranslation =
 478725            GetLocalDisplacement(collider.Rotation, originOffset);
 478626        if (collider is LSSphereCollider sphere)
 27        {
 172228            return FixedSegment.GetCenteredCapsuleSupportAnchor(
 172229                    sphere.Center,
 172230                    sphere.Rotation,
 172231                    Fixed64.Zero,
 172232                    sphere.ScaledRadius,
 172233                    normal)
 172234                .WithLocalTranslation(localTranslation);
 35        }
 306436        if (collider is LSCapsuleCollider capsule)
 37        {
 28338            return FixedSegment.GetCenteredCapsuleSupportAnchor(
 28339                    capsule.Center,
 28340                    capsule.Rotation,
 28341                    capsule.AxisLength,
 28342                    capsule.ScaledRadius,
 28343                    normal)
 28344                .WithLocalTranslation(localTranslation);
 45        }
 278146        if (collider is LSCuboidCollider cuboid)
 47        {
 132748            return new FixedPointAnchor(
 132749                cuboid.Center,
 132750                cuboid.Rotation,
 132751                cuboid.OrientedBox.GetLocalSupportPoint(normal))
 132752                .WithLocalTranslation(localTranslation);
 53        }
 145454        if (collider is LSCylinderCollider cylinder)
 55        {
 57356            return FixedSegment.GetCenteredFiniteCylinderSupportAnchor(
 57357                    cylinder.Center,
 57358                    cylinder.Rotation,
 57359                    cylinder.Height,
 57360                    cylinder.ScaledRadius,
 57361                    normal)
 57362                .WithLocalTranslation(localTranslation);
 63        }
 88164        if (collider is LSConeCollider cone)
 65        {
 58566            return FixedSegment.GetCenteredFiniteConeSupportAnchor(
 58567                    cone.Center,
 58568                    cone.Rotation,
 58569                    cone.Height,
 58570                    cone.ScaledRadius,
 58571                    normal)
 58572                .WithLocalTranslation(localTranslation);
 73        }
 29674        if (collider is LSMeshCollider { Mode: MeshColliderMode.Convex } mesh)
 75        {
 29576            return new FixedPointAnchor(
 29577                mesh.Mesh.Origin,
 29578                mesh.Mesh.Rotation,
 29579                mesh.Mesh.GetSupportVertexLocal(normal))
 29580                .WithLocalTranslation(localTranslation);
 81        }
 82
 183        throw new NotSupportedException(
 184            $"Convex support mapping does not support {collider.GetType().Name}.");
 85    }
 86
 87    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 88    public static bool IsSupported(LSCollider collider) =>
 113589        collider is LSSphereCollider
 113590            or LSCapsuleCollider
 113591            or LSCuboidCollider
 113592            or LSCylinderCollider
 113593            or LSConeCollider
 113594            or LSMeshCollider { Mode: MeshColliderMode.Convex };
 95
 96    public static bool Intersects(LSCollider first, LSCollider second, int maxIterations = 32)
 97    {
 3498        if (!IsSupported(first) || !IsSupported(second))
 299            return false;
 100
 32101        Span<Vector3d> simplex = stackalloc Vector3d[4];
 32102        int count = 0;
 32103        Vector3d direction = second.Center - first.Center;
 32104        if (direction.MagnitudeSquared <= Fixed64.Epsilon)
 3105            direction = Vector3d.Right;
 106
 32107        if (!TrySupportMinkowski(first, second, direction, out simplex[count]))
 1108            return false;
 31109        count++;
 31110        direction = -simplex[0];
 31111        if (direction.MagnitudeSquared <= Fixed64.Epsilon)
 4112            return true;
 113
 210114        for (int i = 0; i < maxIterations; i++)
 115        {
 104116            if (!TrySupportMinkowski(first, second, direction, out Vector3d point))
 1117                return false;
 103118            if (Vector3d.Dot(point, direction) < -Fixed64.Epsilon)
 4119                return false;
 120
 99121            GjkSimplexPolicy.AddPoint(simplex, ref count, point);
 99122            if (GjkSimplexPolicy.Update(simplex, ref count, ref direction))
 19123                return true;
 124
 80125            if (direction.MagnitudeSquared <= Fixed64.Epsilon)
 2126                return true;
 127        }
 128
 129        // No separating support was found within the bounded search, so preserve touching/overlap.
 1130        return true;
 131    }
 132
 133    public static bool IntersectsConeVolume(
 134        LSCollider collider,
 135        Vector3d apex,
 136        Vector3d baseCenter,
 137        Vector3d axis,
 138        Fixed64 endRadius,
 139        int maxIterations = 32)
 140    {
 459141        if (!IsSupported(collider))
 1142            return false;
 143
 458144        Span<Vector3d> simplex = stackalloc Vector3d[4];
 458145        int count = 0;
 458146        Vector3d coneCenter = Vector3d.Midpoint(apex, baseCenter);
 458147        Vector3d direction = collider.Center - coneCenter;
 458148        if (direction.MagnitudeSquared <= Fixed64.Epsilon)
 11149            direction = Vector3d.Right;
 150
 458151        if (!TrySupportMinkowskiConeCollider(
 458152                collider,
 458153                apex,
 458154                baseCenter,
 458155                axis,
 458156                endRadius,
 458157                direction,
 458158                out simplex[count]))
 159        {
 1160            return false;
 161        }
 457162        count++;
 457163        direction = -simplex[0];
 457164        if (direction.MagnitudeSquared <= Fixed64.Epsilon)
 1165            return true;
 166
 2776167        for (int i = 0; i < maxIterations; i++)
 168        {
 1387169            if (!TrySupportMinkowskiConeCollider(
 1387170                    collider,
 1387171                    apex,
 1387172                    baseCenter,
 1387173                    axis,
 1387174                    endRadius,
 1387175                    direction,
 1387176                    out Vector3d point))
 177            {
 1178                return false;
 179            }
 1386180            if (Vector3d.Dot(point, direction) < -Fixed64.Epsilon)
 9181                return false;
 182
 1377183            GjkSimplexPolicy.AddPoint(simplex, ref count, point);
 1377184            if (GjkSimplexPolicy.Update(simplex, ref count, ref direction))
 444185                return true;
 186
 933187            if (direction.MagnitudeSquared <= Fixed64.Epsilon)
 1188                return true;
 189        }
 190
 191        // No separating support was found within the bounded search, so preserve touching/overlap.
 1192        return true;
 193    }
 194
 195    private static bool TrySupportMinkowski(
 196        LSCollider first,
 197        LSCollider second,
 198        Vector3d direction,
 199        out Vector3d difference)
 200    {
 136201        Vector3d normal = ResolveSupportDirection(direction);
 136202        if (first is LSCuboidCollider firstBox
 136203            && second is LSCuboidCollider secondBox)
 204        {
 1205            return firstBox.OrientedBox.TryGetSupportDifference(
 1206                secondBox.OrientedBox,
 1207                normal,
 1208                out difference);
 209        }
 210
 135211        FixedPointAnchor supportA =
 135212            GetSupportAnchor(first, normal, Vector3d.Zero);
 135213        FixedPointAnchor supportB =
 135214            GetSupportAnchor(second, -normal, Vector3d.Zero);
 135215        return supportA.TryGetOffsetFrom(supportB, out difference);
 216    }
 217
 218    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 219    private static bool TrySupportMinkowskiConeCollider(
 220        LSCollider collider,
 221        Vector3d apex,
 222        Vector3d baseCenter,
 223        Vector3d axis,
 224        Fixed64 endRadius,
 225        Vector3d direction,
 226        out Vector3d difference)
 227    {
 1845228        Vector3d normal = ResolveSupportDirection(direction);
 1845229        Vector3d radialDirection =
 1845230            Vector3d.GetNormalizedProjectionOnPlane(normal, axis);
 1845231        var apexAnchor = new FixedPointAnchor(
 1845232            apex,
 1845233            FixedQuaternion.Identity,
 1845234            Vector3d.Zero);
 1845235        var baseAnchor = new FixedPointAnchor(
 1845236            baseCenter,
 1845237            FixedQuaternion.Identity,
 1845238            radialDirection * endRadius);
 1845239        FixedPointAnchor coneSupport =
 1845240            baseAnchor.ProjectNonNegativeOffsetFrom(apexAnchor, normal)
 1845241                > Fixed64.Zero
 1845242            || apexAnchor.ProjectNonNegativeOffsetFrom(baseAnchor, normal)
 1845243                == Fixed64.Zero
 1845244                ? baseAnchor
 1845245                : apexAnchor;
 1845246        FixedPointAnchor colliderSupport =
 1845247            GetSupportAnchor(collider, -normal, Vector3d.Zero);
 1845248        return coneSupport.TryGetOffsetFrom(
 1845249            colliderSupport,
 1845250            out difference);
 251    }
 252
 253    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 254    private static Vector3d ResolveSupportDirection(Vector3d direction) =>
 6768255        direction != Vector3d.Zero
 6768256            ? direction.Normalized
 6768257            : Vector3d.Right;
 258
 259    internal static Vector3d GetLocalDisplacement(
 260        FixedQuaternion rotation,
 261        Vector3d worldDisplacement)
 262    {
 4798263        if (rotation.Inverse().TryRotate(
 4798264                worldDisplacement,
 4798265                out Vector3d localDisplacement))
 266        {
 4797267            return localDisplacement;
 268        }
 269
 1270        throw new InvalidOperationException(
 1271            "The sweep displacement cannot be represented in the collider's canonical frame.");
 272    }
 273
 274}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/3D/CylinderContactGeometry.cs

#LineLine coverage
 1//=======================================================================
 2// CylinderContactGeometry.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 System.Runtime.CompilerServices;
 11
 12namespace Gravitas.CollisionHandling;
 13
 14internal static class CylinderContactGeometry
 15{
 16    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 17    public static bool IsAxisAligned(
 18        FixedQuaternion rotation,
 19        Vector3d localAxis,
 20        Vector3d direction) =>
 85521        Vector3d.Dot(
 85522            localAxis,
 85523            rotation.Inverse().Rotate(direction)).Abs()
 85524        >= Fixed64.FromFraction(63, 64);
 25
 26    public static void GetCapBasis(LSCylinderCollider cylinder, out Vector3d tangentA, out Vector3d tangentB)
 27    {
 43428        tangentA = (cylinder.Rotation * Vector3d.Right).Normalized;
 43429        tangentB = (cylinder.Rotation * Vector3d.Forward).Normalized;
 43430    }
 31}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/3D/GjkSimplexPolicy.cs

#LineLine coverage
 1//=======================================================================
 2// GjkSimplexPolicy.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;
 10using System.Runtime.CompilerServices;
 11
 12namespace Gravitas.CollisionHandling;
 13
 14internal static class GjkSimplexPolicy
 15{
 16    public static void AddPoint(Span<Vector3d> simplex, ref int count, Vector3d point)
 17    {
 897218        for (int i = Math.Min(count, 3); i > 0; i--)
 300919            simplex[i] = simplex[i - 1];
 20
 147721        simplex[0] = point;
 147722        if (count < 4)
 147623            count++;
 147724    }
 25
 26    public static bool Update(Span<Vector3d> simplex, ref int count, ref Vector3d direction)
 27    {
 147628        return count switch
 147629        {
 47530            2 => UpdateLine(simplex, ref count, ref direction),
 47231            3 => UpdateTriangle(simplex, ref count, ref direction),
 52932            _ => UpdateTetrahedron(simplex, ref count, ref direction)
 147633        };
 34    }
 35
 36    public static bool UpdateLine(Span<Vector3d> simplex, ref int count, ref Vector3d direction)
 37    {
 48238        Vector3d a = simplex[0];
 48239        Vector3d b = simplex[1];
 48240        Vector3d ab = b - a;
 48241        Vector3d ao = -a;
 42
 48243        if (SameDirection(ab, ao))
 44        {
 48045            direction = TripleCross(ab, ao, ab);
 48046            if (direction.MagnitudeSquared <= Fixed64.Epsilon)
 22547                direction = Perpendicular(ab);
 48048            return false;
 49        }
 50
 251        simplex[0] = a;
 252        count = 1;
 253        direction = ao;
 254        return false;
 55    }
 56
 57    public static bool UpdateTriangle(Span<Vector3d> simplex, ref int count, ref Vector3d direction)
 58    {
 47859        Vector3d a = simplex[0];
 47860        Vector3d b = simplex[1];
 47861        Vector3d c = simplex[2];
 47862        Vector3d ab = b - a;
 47863        Vector3d ac = c - a;
 47864        Vector3d ao = -a;
 47865        Vector3d abc = Vector3d.Cross(ab, ac);
 66
 47867        Vector3d acPerp = Vector3d.Cross(abc, ac);
 47868        if (SameDirection(acPerp, ao))
 69        {
 770            if (SameDirection(ac, ao))
 71            {
 672                simplex[1] = c;
 673                count = 2;
 674                direction = TripleCross(ac, ao, ac);
 675                if (direction.MagnitudeSquared <= Fixed64.Epsilon)
 176                    direction = Perpendicular(ac);
 677                return false;
 78            }
 79
 180            simplex[1] = b;
 181            count = 2;
 182            return UpdateLine(simplex, ref count, ref direction);
 83        }
 84
 47185        Vector3d abPerp = Vector3d.Cross(ab, abc);
 47186        if (SameDirection(abPerp, ao))
 87        {
 388            simplex[1] = b;
 389            count = 2;
 390            return UpdateLine(simplex, ref count, ref direction);
 91        }
 92
 46893        if (SameDirection(abc, ao))
 94        {
 1295            direction = abc;
 1296            return false;
 97        }
 98
 45699        simplex[1] = c;
 456100        simplex[2] = b;
 456101        direction = -abc;
 456102        return false;
 103    }
 104
 105    public static bool UpdateTetrahedron(Span<Vector3d> simplex, ref int count, ref Vector3d direction)
 106    {
 533107        Vector3d a = simplex[0];
 533108        Vector3d b = simplex[1];
 533109        Vector3d c = simplex[2];
 533110        Vector3d d = simplex[3];
 533111        Vector3d ao = -a;
 112
 533113        Vector3d abc = OrientFaceNormal(a, b, c, d);
 533114        if (SameDirection(abc, ao))
 115        {
 32116            simplex[0] = a;
 32117            simplex[1] = b;
 32118            simplex[2] = c;
 32119            count = 3;
 32120            direction = abc;
 32121            return false;
 122        }
 123
 501124        Vector3d acd = OrientFaceNormal(a, c, d, b);
 501125        if (SameDirection(acd, ao))
 126        {
 23127            simplex[0] = a;
 23128            simplex[1] = c;
 23129            simplex[2] = d;
 23130            count = 3;
 23131            direction = acd;
 23132            return false;
 133        }
 134
 478135        Vector3d adb = OrientFaceNormal(a, d, b, c);
 478136        if (SameDirection(adb, ao))
 137        {
 14138            simplex[0] = a;
 14139            simplex[1] = d;
 14140            simplex[2] = b;
 14141            count = 3;
 14142            direction = adb;
 14143            return false;
 144        }
 145
 464146        return true;
 147    }
 148
 149    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 150    private static bool SameDirection(Vector3d first, Vector3d second) =>
 3418151        Vector3d.Dot(first, second) > Fixed64.Zero;
 152
 153    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 154    private static Vector3d TripleCross(Vector3d first, Vector3d second, Vector3d third) =>
 486155        Vector3d.Cross(Vector3d.Cross(first, second), third);
 156
 157    private static Vector3d OrientFaceNormal(Vector3d a, Vector3d b, Vector3d c, Vector3d opposite)
 158    {
 1512159        Vector3d normal = Vector3d.Cross(b - a, c - a);
 1512160        return Vector3d.Dot(normal, opposite - a) > Fixed64.Zero ? -normal : normal;
 161    }
 162
 163    private static Vector3d Perpendicular(Vector3d vector)
 164    {
 226165        Vector3d candidate = Vector3d.Cross(vector, Vector3d.Up);
 226166        if (candidate.MagnitudeSquared > Fixed64.Epsilon)
 224167            return candidate;
 168
 2169        candidate = Vector3d.Cross(vector, Vector3d.Right);
 2170        return candidate.MagnitudeSquared > Fixed64.Epsilon ? candidate : Vector3d.Forward;
 171    }
 172}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/3D/Mesh/MeshTriangleContactGenerator.cs

#LineLine coverage
 1//=======================================================================
 2// MeshTriangleContactGenerator.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 SwiftCollections;
 12using SwiftCollections.Query;
 13using System;
 14using System.Runtime.CompilerServices;
 15
 16namespace Gravitas.CollisionHandling;
 17
 18/// <summary>
 19/// Builds deterministic triangle-level manifolds for mesh paths that cannot use
 20/// whole-shape convex assumptions.
 21/// </summary>
 22internal static class MeshTriangleContactGenerator
 23{
 24    public static bool TryBuildMeshSphereManifold(
 25        CollisionWorkItem pair,
 26        LSMeshCollider mesh,
 27        LSSphereCollider sphere,
 28        SwiftList<int> triangleBuffer)
 29    {
 1230        mesh.GetTrianglesInBounds(CreateBounds(sphere.BoundsMin, sphere.BoundsMax), triangleBuffer);
 9031        for (int i = 0; i < triangleBuffer.Count; i++)
 3332            TryAddSphereTriangleContact(pair, mesh, triangleBuffer[i], sphere);
 33
 1234        return pair.Manifold.HasContact;
 35    }
 36
 37    public static bool TryBuildMeshCapsuleManifold(
 38        CollisionWorkItem pair,
 39        LSMeshCollider mesh,
 40        LSCapsuleCollider capsule,
 41        SwiftList<int> triangleBuffer)
 42    {
 543        mesh.GetTrianglesInBounds(CreateBounds(capsule.BoundsMin, capsule.BoundsMax), triangleBuffer);
 44
 545        bool overlaps = false;
 3246        for (int i = 0; i < triangleBuffer.Count; i++)
 1147            overlaps |= TryAddCapsuleTriangleContact(pair, mesh, triangleBuffer[i], capsule);
 48
 549        return overlaps;
 50    }
 51
 52    public static bool TryBuildMeshCuboidManifold(
 53        CollisionWorkItem pair,
 54        LSMeshCollider mesh,
 55        LSCuboidCollider cuboid,
 56        SwiftList<int> triangleBuffer)
 57    {
 68958        mesh.GetTrianglesInBounds(CreateBounds(cuboid.BoundsMin, cuboid.BoundsMax), triangleBuffer);
 364059        for (int i = 0; i < triangleBuffer.Count; i++)
 113160            TryAddCuboidTriangleContact(pair, mesh, triangleBuffer[i], cuboid);
 61
 68962        return pair.Manifold.HasContact;
 63    }
 64
 65    public static bool TryBuildMeshCylinderManifold(
 66        CollisionWorkItem pair,
 67        LSMeshCollider mesh,
 68        LSCylinderCollider cylinder,
 69        SwiftList<int> triangleBuffer)
 70    {
 42871        mesh.GetTrianglesInBounds(CreateBounds(cylinder.BoundsMin, cylinder.BoundsMax), triangleBuffer);
 42872        bool overlaps = false;
 256473        for (int i = 0; i < triangleBuffer.Count; i++)
 85474            overlaps |= TryAddCylinderTriangleContact(pair, mesh, triangleBuffer[i], cylinder);
 75
 42876        return overlaps;
 77    }
 78
 79    public static bool TryBuildMeshMeshManifold(
 80        CollisionWorkItem pair,
 81        LSMeshCollider meshA,
 82        LSMeshCollider meshB,
 83        SwiftList<int> triangleBufferA,
 84        SwiftList<int> triangleBufferB)
 85    {
 64586        bool reverseContact = meshA.Id > meshB.Id;
 64587        LSMeshCollider firstMesh = reverseContact ? meshB : meshA;
 64588        LSMeshCollider secondMesh = reverseContact ? meshA : meshB;
 64589        firstMesh.GetTrianglesInBounds(
 64590            CreateBounds(secondMesh.BoundsMin, secondMesh.BoundsMax),
 64591            triangleBufferA);
 1658292        for (int i = 0; i < triangleBufferA.Count; i++)
 93        {
 764694            int triangleA = triangleBufferA[i];
 764695            firstMesh.Mesh.GetLocalTriangleVertices(
 764696                triangleA,
 764697                out Vector3d firstVertex,
 764698                out Vector3d secondVertex,
 764699                out Vector3d thirdVertex);
 7646100            var firstTriangle =
 7646101                new FixedTriangle(firstVertex, secondVertex, thirdVertex);
 7646102            GetTriangleBoundsInFrame(
 7646103                firstTriangle.Bounds,
 7646104                firstMesh.Mesh.Origin,
 7646105                firstMesh.Mesh.Rotation,
 7646106                secondMesh.Mesh.Origin,
 7646107                secondMesh.Mesh.Rotation,
 7646108                out FixedBoundVolume firstInSecondFrameBounds);
 7646109            secondMesh.Mesh.GetTrianglesInLocalBounds(
 7646110                firstInSecondFrameBounds,
 7646111                triangleBufferB);
 112
 76588113            for (int j = 0; j < triangleBufferB.Count; j++)
 114            {
 30648115                int triangleB = triangleBufferB[j];
 30648116                secondMesh.Mesh.GetLocalTriangleVertices(
 30648117                    triangleB,
 30648118                    out Vector3d secondFirstVertex,
 30648119                    out Vector3d secondSecondVertex,
 30648120                    out Vector3d secondThirdVertex);
 30648121                var secondTriangle = new FixedTriangle(
 30648122                    secondFirstVertex,
 30648123                    secondSecondVertex,
 30648124                    secondThirdVertex);
 30648125                if (!firstTriangle.TryGetContact(
 30648126                        firstMesh.Mesh.Origin,
 30648127                        firstMesh.Mesh.Rotation,
 30648128                        secondMesh.Mesh.Origin,
 30648129                        secondMesh.Mesh.Rotation,
 30648130                        secondTriangle,
 30648131                        out FixedContactAnchors contact))
 132                {
 133                    continue;
 134                }
 135
 19583136                if (reverseContact)
 137                {
 85138                    AddContact(
 85139                        pair,
 85140                        new ContactAnchor(contact.SecondAnchor),
 85141                        new ContactAnchor(contact.FirstAnchor),
 85142                        contact.Depth,
 85143                        -contact.Normal,
 85144                        contact.DepthIsClamped);
 145                }
 146                else
 147                {
 19498148                    AddContact(
 19498149                        pair,
 19498150                        new ContactAnchor(contact.FirstAnchor),
 19498151                        new ContactAnchor(contact.SecondAnchor),
 19498152                        contact.Depth,
 19498153                        contact.Normal,
 19498154                        contact.DepthIsClamped);
 155                }
 156            }
 157        }
 158
 645159        return pair.Manifold.HasContact;
 160    }
 161
 162    private static void TryAddSphereTriangleContact(
 163        CollisionWorkItem pair,
 164        LSMeshCollider mesh,
 165        int triangleIndex,
 166        LSSphereCollider sphere)
 167    {
 33168        mesh.Mesh.GetLocalTriangleVertices(
 33169            triangleIndex,
 33170            out Vector3d first,
 33171            out Vector3d second,
 33172            out Vector3d third);
 33173        if (!new FixedTriangle(first, second, third).TryGetSphereContact(
 33174                mesh.Mesh.Origin,
 33175                mesh.Mesh.Rotation,
 33176                sphere.Center,
 33177                sphere.Rotation,
 33178                sphere.ScaledRadius,
 33179                out FixedContactAnchors contact))
 180        {
 19181            return;
 182        }
 183
 14184        AddContact(
 14185            pair,
 14186            new ContactAnchor(contact.FirstAnchor),
 14187            new ContactAnchor(contact.SecondAnchor),
 14188            contact.Depth,
 14189            contact.Normal,
 14190            contact.DepthIsClamped);
 14191    }
 192
 193    private static bool TryAddCapsuleTriangleContact(
 194        CollisionWorkItem pair,
 195        LSMeshCollider mesh,
 196        int triangleIndex,
 197        LSCapsuleCollider capsule)
 198    {
 11199        mesh.Mesh.GetLocalTriangleVertices(
 11200            triangleIndex,
 11201            out Vector3d first,
 11202            out Vector3d second,
 11203            out Vector3d third);
 11204        var triangle = new FixedTriangle(first, second, third);
 11205        Vector3d fallbackNormal = OrientNormal(
 11206            mesh.Mesh.CreatePointAnchor(triangle.Centroid),
 11207            new FixedPointAnchor(
 11208                capsule.Center,
 11209                FixedQuaternion.Identity,
 11210                Vector3d.Zero),
 11211            mesh.Mesh.GetFaceNormalWorld(triangleIndex));
 11212        if (!triangle.TryGetCenteredCapsuleContact(
 11213                mesh.Mesh.Origin,
 11214                mesh.Mesh.Rotation,
 11215                capsule.Center,
 11216                capsule.Rotation,
 11217                capsule.AxisLength,
 11218                capsule.ScaledRadius,
 11219                fallbackNormal,
 11220                out FixedContactAnchors contact))
 221        {
 4222            return false;
 223        }
 224
 7225        AddContact(
 7226            pair,
 7227            new ContactAnchor(contact.FirstAnchor),
 7228            new ContactAnchor(contact.SecondAnchor),
 7229            contact.Depth,
 7230            contact.Normal,
 7231            contact.DepthIsClamped);
 7232        return true;
 233    }
 234
 235    private static void TryAddCuboidTriangleContact(
 236        CollisionWorkItem pair,
 237        LSMeshCollider mesh,
 238        int triangleIndex,
 239        LSCuboidCollider cuboid)
 240    {
 1131241        mesh.Mesh.GetLocalTriangleVertices(
 1131242            triangleIndex,
 1131243            out Vector3d first,
 1131244            out Vector3d second,
 1131245            out Vector3d third);
 1131246        var triangle = new FixedTriangle(first, second, third);
 1131247        Span<FixedContactLocalPoints> faceContacts =
 1131248            stackalloc FixedContactLocalPoints[4];
 1131249        if (!cuboid.OrientedBox.TryGetTriangleContact(
 1131250                mesh.Mesh.Origin,
 1131251                mesh.Mesh.Rotation,
 1131252                triangle,
 1131253                faceContacts,
 1131254                out FixedContactAnchors contact,
 1131255                out int faceContactCount))
 256        {
 237257            return;
 258        }
 259
 894260        if (faceContactCount > 0)
 261        {
 5962262            for (int index = 0; index < faceContactCount; index++)
 263            {
 2130264                AddReversedBoxTriangleContact(
 2130265                    pair,
 2130266                    contact,
 2130267                    faceContacts[index]);
 268            }
 851269            return;
 270        }
 271
 43272        AddReversedBoxTriangleContact(pair, contact);
 43273    }
 274
 275    private static void AddReversedBoxTriangleContact(
 276        CollisionWorkItem pair,
 277        FixedContactAnchors contact)
 278    {
 43279        AddContact(
 43280            pair,
 43281            new ContactAnchor(contact.SecondAnchor),
 43282            new ContactAnchor(contact.FirstAnchor),
 43283            contact.Depth,
 43284            -contact.Normal,
 43285            contact.DepthIsClamped);
 43286    }
 287
 288    private static void AddReversedBoxTriangleContact(
 289        CollisionWorkItem pair,
 290        FixedContactAnchors primary,
 291        FixedContactLocalPoints contact)
 292    {
 2130293        AddContact(
 2130294            pair,
 2130295            new ContactAnchor(
 2130296                primary.SecondAnchor.Origin,
 2130297                primary.SecondAnchor.Rotation,
 2130298                contact.SecondLocalPoint),
 2130299            new ContactAnchor(
 2130300                primary.FirstAnchor.Origin,
 2130301                primary.FirstAnchor.Rotation,
 2130302                contact.FirstLocalPoint),
 2130303            primary.Depth,
 2130304            -primary.Normal,
 2130305            primary.DepthIsClamped);
 2130306    }
 307
 308    private static bool TryAddCylinderTriangleContact(
 309        CollisionWorkItem pair,
 310        LSMeshCollider mesh,
 311        int triangleIndex,
 312        LSCylinderCollider cylinder)
 313    {
 854314        mesh.Mesh.GetLocalTriangleVertices(
 854315            triangleIndex,
 854316            out Vector3d first,
 854317            out Vector3d second,
 854318            out Vector3d third);
 854319        var triangle = new FixedTriangle(first, second, third);
 854320        var cylinderCenter = new FixedPointAnchor(
 854321            cylinder.Center,
 854322            FixedQuaternion.Identity,
 854323            Vector3d.Zero);
 854324        Vector3d normal = OrientNormal(
 854325            mesh.Mesh.CreatePointAnchor(triangle.Centroid),
 854326            cylinderCenter,
 854327            mesh.Mesh.GetFaceNormalWorld(triangleIndex));
 854328        if (TryAddCylinderCapTriangleContacts(
 854329                pair,
 854330                mesh,
 854331                triangle,
 854332                cylinder,
 854333                normal))
 334        {
 428335            return true;
 336        }
 337
 426338        if (!cylinderCenter.TryGetLocalPointIn(
 426339                mesh.Mesh.Origin,
 426340                mesh.Mesh.Rotation,
 426341                out Vector3d localCylinderCenter))
 342        {
 1343            return false;
 344        }
 345
 425346        Vector3d localPointOnMesh =
 425347            triangle.ClosestPoint(localCylinderCenter);
 425348        FixedPointAnchor meshAnchor =
 425349            mesh.Mesh.CreatePointAnchor(localPointOnMesh);
 350        // The closest point belongs to the admitted triangle candidate, so its
 351        // cylinder-frame offset is finite once the center entered the mesh frame.
 425352        _ = meshAnchor.TryGetLocalPointIn(
 425353            cylinder.Center,
 425354            cylinder.Rotation,
 425355            out Vector3d localPointInCylinder);
 425356        if (!FixedSegment.ContainsPointInCenteredFiniteCylinder(
 425357                localPointInCylinder,
 425358                Vector3d.Zero,
 425359                Vector3d.Up,
 425360                cylinder.Height,
 425361                cylinder.ScaledRadius))
 362        {
 5363            return false;
 364        }
 365        // Exact containment proves the centered canonical surface offset
 366        // remains inside the admitted radius and height.
 420367        _ = FixedSegment.TryGetClosestCenteredFiniteCylinderSurfaceOffset(
 420368            localPointInCylinder,
 420369            Vector3d.Zero,
 420370            Vector3d.Up,
 420371            cylinder.Height,
 420372            cylinder.ScaledRadius,
 420373            Vector3d.Right,
 420374            out Vector3d localCylinderPoint,
 420375            out _,
 420376            out Fixed64 signedDistance);
 377
 420378        Fixed64 depth = -signedDistance;
 420379        AddContact(
 420380            pair,
 420381            new ContactAnchor(meshAnchor),
 420382            new ContactAnchor(
 420383                cylinder.Center,
 420384                cylinder.Rotation,
 420385                localCylinderPoint),
 420386            depth,
 420387            normal,
 420388            depthIsClamped: false);
 420389        return true;
 390    }
 391
 392    private static bool TryAddCylinderCapTriangleContacts(
 393        CollisionWorkItem pair,
 394        LSMeshCollider mesh,
 395        FixedTriangle triangle,
 396        LSCylinderCollider cylinder,
 397        Vector3d normal)
 398    {
 854399        if (!CylinderContactGeometry.IsAxisAligned(
 854400                cylinder.Rotation,
 854401                Vector3d.Up,
 854402                normal))
 423403            return false;
 404
 431405        int initialCount = pair.Manifold.Count;
 431406        CylinderContactGeometry.GetCapBasis(cylinder, out Vector3d tangentA, out Vector3d tangentB);
 431407        TryAddCylinderCapTriangleContact(pair, mesh, triangle, cylinder, -normal + tangentA, normal);
 431408        TryAddCylinderCapTriangleContact(pair, mesh, triangle, cylinder, -normal - tangentA, normal);
 431409        TryAddCylinderCapTriangleContact(pair, mesh, triangle, cylinder, -normal + tangentB, normal);
 431410        TryAddCylinderCapTriangleContact(pair, mesh, triangle, cylinder, -normal - tangentB, normal);
 411
 431412        if (pair.Manifold.Count > initialCount)
 428413            return true;
 414
 3415        TryAddCylinderCapTriangleContact(pair, mesh, triangle, cylinder, -normal, normal);
 3416        return pair.Manifold.Count > initialCount;
 417    }
 418
 419    private static void TryAddCylinderCapTriangleContact(
 420        CollisionWorkItem pair,
 421        LSMeshCollider mesh,
 422        FixedTriangle triangle,
 423        LSCylinderCollider cylinder,
 424        Vector3d supportDirection,
 425        Vector3d normal)
 426    {
 1727427        if (!triangle.TryGetCenteredFiniteCylinderSupportContact(
 1727428                mesh.Mesh.Origin,
 1727429                mesh.Mesh.Rotation,
 1727430                cylinder.Center,
 1727431                cylinder.Rotation,
 1727432                cylinder.Height,
 1727433                cylinder.ScaledRadius,
 1727434                supportDirection,
 1727435                normal,
 1727436                out FixedContactAnchors contact))
 437        {
 868438            return;
 439        }
 440
 859441        AddContact(
 859442            pair,
 859443            new ContactAnchor(contact.FirstAnchor),
 859444            new ContactAnchor(contact.SecondAnchor),
 859445            contact.Depth,
 859446            contact.Normal,
 859447            contact.DepthIsClamped);
 859448    }
 449
 450    private static void GetTriangleBoundsInFrame(
 451        FixedBoundBox triangleBounds,
 452        Vector3d sourceOrigin,
 453        FixedQuaternion sourceRotation,
 454        Vector3d frameOrigin,
 455        FixedQuaternion frameRotation,
 456        out FixedBoundVolume bounds)
 457    {
 7646458        FixedBoundBox reframedBounds =
 7646459            FixedBoundBox.FromRelativeRotatedBoundsClippedToDomain(
 7646460                sourceOrigin,
 7646461                sourceRotation,
 7646462                triangleBounds.Min,
 7646463                triangleBounds.Max,
 7646464                frameOrigin,
 7646465                frameRotation);
 7646466        bounds = CreateBounds(reframedBounds.Min, reframedBounds.Max);
 7646467    }
 468
 469    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 470    private static FixedBoundVolume CreateBounds(Vector3d min, Vector3d max) =>
 9425471        new(min, max);
 472
 473    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 474    private static Vector3d OrientNormal(
 475        in FixedPointAnchor source,
 476        in FixedPointAnchor target,
 477        Vector3d normal)
 478    {
 865479        Vector3d resolved = normal.Normalized;
 865480        return source.ProjectNonNegativeOffsetFrom(target, resolved)
 865481                > Fixed64.Zero
 865482            ? -resolved
 865483            : resolved;
 484    }
 485
 486    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 487    private static void AddContact(
 488        CollisionWorkItem pair,
 489        ContactAnchor anchorOnFirst,
 490        ContactAnchor anchorOnSecond,
 491        Fixed64 depth,
 492        Vector3d normalFirstToSecond,
 493        bool depthIsClamped)
 494    {
 23056495        pair.Manifold.AddContact(
 23056496            anchorOnFirst,
 23056497            anchorOnSecond,
 23056498            depth,
 23056499            normalFirstToSecond,
 23056500            depthIsClamped);
 23056501    }
 502
 503}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/Mixed/CollisionDetectionMixed.Complex.cs

#LineLine coverage
 1//=======================================================================
 2// CollisionDetectionMixed.Complex.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 SwiftCollections;
 12using SwiftCollections.Query;
 13using System;
 14
 15namespace Gravitas.CollisionHandling;
 16
 17public static partial class CollisionDetectionMixed
 18{
 19    private static bool TryCompoundEmbedded2D(LSCompoundCollider compound, LSCollider2D embedded, out MixedContact conta
 20    {
 621        bool found = false;
 622        MixedContact best = default;
 23
 3424        for (int i = 0; i < compound.PartCount; i++)
 25        {
 1126            LSCollider part = compound.GetPartCollider(i);
 1127            if (!part.Bounds.Intersects(embedded.MixedBounds3D)
 1128                || !TryCollide(part, embedded, out MixedContact candidate))
 29            {
 30                continue;
 31            }
 32
 633            candidate = candidate.WithFallbackMaterials(part.Material, embedded.Material);
 34
 635            if (ContactSelectionPolicy.ShouldReplaceWithShallower(candidate, found, best))
 36            {
 537                best = candidate;
 538                found = true;
 39            }
 40        }
 41
 642        if (!found)
 143            return NoContact(out contact);
 44
 545        contact = best;
 546        return true;
 47    }
 48
 49    private static bool TryMeshEmbedded2D(LSMeshCollider mesh, LSCollider2D embedded, out MixedContact contact)
 50    {
 2051        SwiftList<int> triangleBuffer = mesh.Context.CollisionScratch.MeshTriangleCandidatesA;
 2052        FixedBoundVolume embeddedBounds = new(embedded.MixedBounds3D.Min, embedded.MixedBounds3D.Max);
 2053        mesh.GetTrianglesInBounds(embeddedBounds, triangleBuffer);
 54
 2055        bool found = false;
 2056        MixedContact best = default;
 17057        for (int i = 0; i < triangleBuffer.Count; i++)
 58        {
 6559            int triangleIndex = triangleBuffer[i];
 60            MixedContact candidate;
 6561            if (embedded is LSAABBoxCollider2D or LSPolygonCollider2D)
 62            {
 2363                if (!TryGetTriangleConvexPrismContact(
 2364                        mesh,
 2365                        triangleIndex,
 2366                        embedded,
 2367                        out candidate))
 68                {
 1469                    continue;
 70                }
 71            }
 72            else
 73            {
 4274                if (!TryGetTriangleFiniteSurfaceContact(
 4275                        mesh,
 4276                        triangleIndex,
 4277                        embedded,
 4278                        out candidate))
 79                {
 80                    continue;
 81                }
 82            }
 83
 2884            if (ContactSelectionPolicy.ShouldReplaceWithShallower(candidate, found, best))
 85            {
 1086                best = candidate;
 1087                found = true;
 88            }
 89        }
 90
 2091        if (!found)
 1192            return NoContact(out contact);
 93
 994        contact = best;
 995        return true;
 96    }
 97
 98    private static bool TryGetTriangleFiniteSurfaceContact(
 99        LSMeshCollider mesh,
 100        int triangleIndex,
 101        LSCollider2D embedded,
 102        out MixedContact contact)
 103    {
 42104        mesh.Mesh.GetLocalTriangleVertices(
 42105            triangleIndex,
 42106            out Vector3d first,
 42107            out Vector3d second,
 42108            out Vector3d third);
 42109        var triangle = new FixedTriangle(first, second, third);
 42110        Vector3d slabCenter = GetEmbeddedCenter3D(embedded);
 111
 112        bool collided;
 113        FixedContactAnchors relation;
 42114        if (embedded is LSCircleCollider2D circle)
 115        {
 16116            collided = triangle.TryGetCircleSlabContact(
 16117                mesh.Mesh.Origin,
 16118                mesh.Mesh.Rotation,
 16119                slabCenter,
 16120                circle.Rotation,
 16121                circle.MixedHalfThickness,
 16122                circle.ScaledRadius,
 16123                out relation);
 124        }
 26125        else if (embedded is LSCapsuleCollider2D capsule)
 126        {
 14127            collided = triangle.TryGetCenteredCapsuleSlabContact(
 14128                mesh.Mesh.Origin,
 14129                mesh.Mesh.Rotation,
 14130                slabCenter,
 14131                capsule.Rotation,
 14132                Vector2d.Forward,
 14133                capsule.AxisLength,
 14134                capsule.ScaledRadius,
 14135                capsule.MixedHalfThickness,
 14136                out relation);
 137        }
 138        else
 139        {
 12140            return NoContact(out contact);
 141        }
 142
 30143        return TryBuildFinitePrismContact(
 30144            collided,
 30145            relation,
 30146            out contact);
 147    }
 148}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/Mixed/CollisionDetectionMixed.cs

#LineLine coverage
 1//=======================================================================
 2// CollisionDetectionMixed.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 System;
 12using System.Runtime.CompilerServices;
 13
 14namespace Gravitas.CollisionHandling;
 15
 16/// <summary>
 17/// Deterministic mixed 2D/3D narrow-phase collision checks.
 18/// </summary>
 19public static partial class CollisionDetectionMixed
 20{
 21    /// <summary>Tests a 3D collider against an embedded 2D collider.</summary>
 22    public static bool TryCollide(LSCollider collider3D, LSCollider2D collider2D, out MixedContact contact)
 23    {
 804024        SwiftThrowHelper.ThrowIfNull(collider3D, nameof(collider3D));
 804025        SwiftThrowHelper.ThrowIfNull(collider2D, nameof(collider2D));
 26
 804027        if (!collider3D.Bounds.Intersects(collider2D.MixedBounds3D))
 28        {
 315229            contact = default;
 315230            return false;
 31        }
 32
 488833        if (collider2D is LSCompoundCollider2D compound2D)
 634            return TryEmbeddedCompound2D(collider3D, compound2D, out contact);
 35
 488236        return collider3D.Shape switch
 488237        {
 146638            ColliderType.Sphere => TrySphereEmbedded2D((LSSphereCollider)collider3D, collider2D, out contact),
 200339            ColliderType.AABox or ColliderType.OBBox => TryCuboidEmbedded2D((LSCuboidCollider)collider3D, collider2D, ou
 53840            ColliderType.Capsule => TryCapsuleEmbedded2D((LSCapsuleCollider)collider3D, collider2D, out contact),
 41041            ColliderType.Cylinder => TryCylinderEmbedded2D((LSCylinderCollider)collider3D, collider2D, out contact),
 39642            ColliderType.Cone => TryConeEmbedded2D((LSConeCollider)collider3D, collider2D, out contact),
 2043            ColliderType.Mesh => TryMeshEmbedded2D((LSMeshCollider)collider3D, collider2D, out contact),
 644            ColliderType.Compound => TryCompoundEmbedded2D((LSCompoundCollider)collider3D, collider2D, out contact),
 4345            _ => NoContact(out contact)
 488246        };
 47    }
 48
 49    private static bool TryEmbeddedCompound2D(
 50        LSCollider collider3D,
 51        LSCompoundCollider2D compound2D,
 52        out MixedContact contact)
 53    {
 654        bool found = false;
 655        MixedContact best = default;
 56
 3257        for (int i = 0; i < compound2D.PartCount; i++)
 58        {
 1059            LSCollider2D part = compound2D.GetPartCollider(i);
 1060            if (!collider3D.Bounds.Intersects(part.MixedBounds3D)
 1061                || !TryCollide(collider3D, part, out MixedContact candidate))
 62            {
 63                continue;
 64            }
 65
 766            candidate = candidate.WithFallbackMaterials(collider3D.Material, part.Material);
 67
 768            if (ContactSelectionPolicy.ShouldReplaceWithShallower(candidate, found, best))
 69            {
 670                best = candidate;
 671                found = true;
 72            }
 73        }
 74
 675        if (!found)
 176            return NoContact(out contact);
 77
 578        contact = best;
 579        return true;
 80    }
 81
 82    private static bool TrySphereEmbedded2D(LSSphereCollider sphere, LSCollider2D embedded, out MixedContact contact)
 83    {
 146684        if (embedded is LSAABBoxCollider2D or LSPolygonCollider2D)
 118885            return TryGetSphereConvexPrismContact(sphere, embedded, out contact);
 86
 27887        Vector3d sphereCenter = sphere.Center;
 27888        Vector2d planarCenter = new(sphereCenter.X, sphereCenter.Z);
 27889        bool planarInside = embedded.ContainsPoint(planarCenter);
 27890        bool inside = planarInside
 27891            && MixedEmbedded2DGeometry.ContainsPointInSlab(
 27892                embedded,
 27893                sphereCenter.Y);
 27894        ContactAnchor embeddedAnchor =
 27895            MixedEmbedded2DGeometry.GetClosestAnchorOnEmbeddedVolume(
 27896                embedded,
 27897                sphereCenter);
 98        // Broad-phase overlap plus a closest-volume anchor bounds every
 99        // component of this relative offset to the Fixed64 domain.
 278100        _ = embeddedAnchor.TryGetOffsetFrom(
 278101            sphereCenter,
 278102            out Vector3d delta);
 278103        if (!Vector3d.TryGetMagnitude(delta, out Fixed64 distance)
 278104            || (!inside && distance > sphere.ScaledRadius))
 105        {
 8106            contact = default;
 8107            return false;
 108        }
 109
 270110        Vector3d normal = distance > Fixed64.Zero
 270111            ? delta / distance
 270112            : ResolveFallbackNormal(sphereCenter, embedded);
 270113        bool exactDepth = inside
 270114            ? Fixed64.TryAdd(
 270115                sphere.ScaledRadius,
 270116                distance,
 270117                out Fixed64 depth)
 270118            : Fixed64.TrySubtract(
 270119                sphere.ScaledRadius,
 270120                distance,
 270121                out depth);
 270122        if (!exactDepth)
 1123            depth = Fixed64.MaxValue;
 270124        contact = new MixedContact(
 270125            new ContactAnchor(
 270126                sphereCenter,
 270127                normal * sphere.ScaledRadius),
 270128            embeddedAnchor,
 270129            normal,
 270130            depth,
 270131            depthIsClamped: !exactDepth);
 270132        return true;
 133    }
 134
 135    private static bool TryCapsuleEmbedded2D(LSCapsuleCollider capsule, LSCollider2D embedded, out MixedContact contact)
 136    {
 538137        if (embedded is LSAABBoxCollider2D or LSPolygonCollider2D)
 301138            return TryGetCapsuleConvexPrismContact(capsule, embedded, out contact);
 139
 237140        bool collided = embedded.Shape == ColliderType2D.Circle
 237141            ? TryTestCapsuleCircleSlab(capsule, (LSCircleCollider2D)embedded, out AxisPenetration penetration)
 237142            : TryTestCapsuleCapsuleSlab(capsule, (LSCapsuleCollider2D)embedded, out penetration);
 143
 237144        return collided
 237145            ? BuildCapsuleContact(capsule, embedded, penetration, out contact)
 237146            : NoContact(out contact);
 147    }
 148
 149    private static bool TryCylinderEmbedded2D(LSCylinderCollider cylinder, LSCollider2D embedded, out MixedContact conta
 150    {
 410151        if (embedded is LSAABBoxCollider2D or LSPolygonCollider2D)
 239152            return TryGetCylinderConvexPrismContact(cylinder, embedded, out contact);
 153
 171154        bool collided = embedded.Shape == ColliderType2D.Circle
 171155            ? TryTestCylinderCircleSlab(cylinder, (LSCircleCollider2D)embedded, out AxisPenetration penetration)
 171156            : TryTestCylinderCapsuleSlab(cylinder, (LSCapsuleCollider2D)embedded, out penetration);
 157
 171158        return collided
 171159            ? BuildCylinderContact(cylinder, embedded, penetration, out contact)
 171160            : NoContact(out contact);
 161    }
 162
 163    private static bool TryConeEmbedded2D(LSConeCollider cone, LSCollider2D embedded, out MixedContact contact)
 164    {
 396165        if (embedded is LSAABBoxCollider2D or LSPolygonCollider2D)
 230166            return TryGetConeConvexPrismContact(cone, embedded, out contact);
 167
 166168        bool collided = embedded.Shape == ColliderType2D.Circle
 166169            ? TryTestConeCircleSlab(cone, (LSCircleCollider2D)embedded, out AxisPenetration penetration)
 166170            : TryTestConeCapsuleSlab(cone, (LSCapsuleCollider2D)embedded, out penetration);
 171
 166172        return collided
 166173            ? BuildConeContact(cone, embedded, penetration, out contact)
 166174            : NoContact(out contact);
 175    }
 176
 177    private static bool TryTestCapsuleCircleSlab(
 178        LSCapsuleCollider capsule,
 179        LSCircleCollider2D circle,
 180        out AxisPenetration penetration)
 181    {
 96182        penetration = default;
 96183        Vector3d capsuleAxis = GetRigidUpAxis(capsule.Rotation);
 96184        CheckCapsuleCircleSlabAxis(
 96185            capsule,
 96186            circle,
 96187            capsuleAxis,
 96188            Vector3d.Up,
 96189            ref penetration);
 190
 96191        if (!CheckCapsuleCircleSlabAxis(
 96192                capsule,
 96193                circle,
 96194                capsuleAxis,
 96195                capsuleAxis,
 96196                ref penetration))
 1197            return false;
 198
 95199        if (!CheckCapsuleCircleSlabAxis(
 95200                capsule,
 95201                circle,
 95202                capsuleAxis,
 95203                Vector3d.Cross(capsuleAxis, Vector3d.Up),
 95204                ref penetration))
 1205            return false;
 206
 94207        Vector3d closestFeatureAxis =
 94208            FixedSegment.GetClosestDirectionBetweenCenteredAxes(
 94209                capsule.Center,
 94210                capsuleAxis,
 94211                capsule.AxisLength,
 94212                GetEmbeddedCenter3D(circle),
 94213                Vector3d.Up,
 94214                circle.MixedHalfThickness * Fixed64.Two);
 94215        if (!CheckCapsuleCircleSlabAxis(
 94216                capsule,
 94217                circle,
 94218                capsuleAxis,
 94219                closestFeatureAxis,
 94220                ref penetration))
 1221            return false;
 222
 93223        return penetration.HasValue;
 224    }
 225
 226    private static bool TryTestCylinderCircleSlab(
 227        LSCylinderCollider cylinder,
 228        LSCircleCollider2D circle,
 229        out AxisPenetration penetration)
 230    {
 67231        penetration = default;
 67232        Vector3d cylinderAxis = GetRigidUpAxis(cylinder.Rotation);
 67233        if (!CheckCylinderCircleSlabAxis(
 67234                cylinder,
 67235                circle,
 67236                cylinderAxis,
 67237                cylinderAxis,
 67238                ref penetration))
 1239            return false;
 240
 66241        CheckCylinderCircleSlabAxis(
 66242            cylinder,
 66243            circle,
 66244            cylinderAxis,
 66245            Vector3d.Up,
 66246            ref penetration);
 247
 66248        if (!CheckCylinderCircleSlabAxis(
 66249                cylinder,
 66250                circle,
 66251                cylinderAxis,
 66252                Vector3d.Cross(cylinderAxis, Vector3d.Up),
 66253                ref penetration))
 1254            return false;
 255
 65256        Vector3d closestFeatureAxis =
 65257            FixedSegment.GetClosestDirectionBetweenCenteredAxes(
 65258                cylinder.Center,
 65259                cylinderAxis,
 65260                cylinder.Height,
 65261                GetEmbeddedCenter3D(circle),
 65262                Vector3d.Up,
 65263                circle.MixedHalfThickness * Fixed64.Two);
 65264        if (!CheckCylinderCircleSlabAxis(
 65265                cylinder,
 65266                circle,
 65267                cylinderAxis,
 65268                closestFeatureAxis,
 65269                ref penetration))
 1270            return false;
 271
 64272        return penetration.HasValue;
 273    }
 274
 275    private static bool TryTestConeCircleSlab(
 276        LSConeCollider cone,
 277        LSCircleCollider2D circle,
 278        out AxisPenetration penetration)
 279    {
 65280        penetration = default;
 65281        Vector3d coneAxis = GetRigidUpAxis(cone.Rotation);
 282
 65283        if (!CheckConeCircleSlabAxis(cone, circle, coneAxis, ref penetration))
 1284            return false;
 285
 64286        CheckConeCircleSlabAxis(cone, circle, Vector3d.Up, ref penetration);
 287
 64288        if (!CheckConeCircleSlabAxis(
 64289                cone,
 64290                circle,
 64291                Vector3d.Cross(coneAxis, Vector3d.Up),
 64292                ref penetration))
 1293            return false;
 294
 63295        Vector3d circleCenter = GetEmbeddedCenter3D(circle);
 296        // The admitted broad-phase bounds and separating axes guarantee that
 297        // this closest-feature offset is representable in either rigid frame.
 63298        _ = FixedSegment.TryGetClosestCenteredFiniteConeSurfaceAnchor(
 63299            circleCenter,
 63300            cone.Center,
 63301            cone.Rotation,
 63302            Vector3d.Up,
 63303            cone.Height,
 63304            cone.ScaledRadius,
 63305            Vector3d.Right,
 63306            out FixedPointAnchor conePoint,
 63307            out _,
 63308            out _);
 63309        _ = conePoint.TryGetOffsetFrom(
 63310            new FixedPointAnchor(
 63311                circleCenter,
 63312                FixedQuaternion.Identity,
 63313                Vector3d.Zero),
 63314            out Vector3d conePointFromCircle);
 315
 63316        Fixed64 closestCircleY = FixedMath.Clamp(
 63317            conePointFromCircle.Y,
 63318            -circle.MixedHalfThickness,
 63319            circle.MixedHalfThickness);
 63320        _ = Vector3d.TrySubtract(
 63321            new Vector3d(Fixed64.Zero, closestCircleY, Fixed64.Zero),
 63322            conePointFromCircle,
 63323            out Vector3d closestFeatureAxis);
 63324        if (!CheckConeCircleSlabAxis(
 63325                cone,
 63326                circle,
 63327                closestFeatureAxis,
 63328                ref penetration))
 1329            return false;
 330
 62331        return penetration.HasValue;
 332    }
 333
 334    private static bool TryTestCapsuleCapsuleSlab(
 335        LSCapsuleCollider capsule,
 336        LSCapsuleCollider2D prism,
 337        out AxisPenetration penetration)
 338    {
 141339        penetration = default;
 141340        Vector3d capsuleAxis = GetRigidUpAxis(capsule.Rotation);
 141341        GetEmbeddedCapsuleAxes(
 141342            prism,
 141343            out Vector2d prismPlanarAxis,
 141344            out Vector3d prismAxis,
 141345            out Vector3d prismNormal);
 346
 141347        if (!CheckCapsulePrismAxis(
 141348                capsule,
 141349                prism,
 141350                capsuleAxis,
 141351                prismPlanarAxis,
 141352                capsuleAxis,
 141353                ref penetration))
 1354            return false;
 355
 140356        CheckCapsulePrismAxis(
 140357            capsule,
 140358            prism,
 140359            capsuleAxis,
 140360            prismPlanarAxis,
 140361            Vector3d.Up,
 140362            ref penetration);
 363
 140364        if (!CheckCapsulePrismAxis(
 140365                capsule,
 140366                prism,
 140367                capsuleAxis,
 140368                prismPlanarAxis,
 140369                Vector3d.Cross(capsuleAxis, Vector3d.Up),
 140370                ref penetration))
 8371            return false;
 372
 132373        if (!CheckEmbeddedCapsuleAxes(
 132374                capsule,
 132375                capsuleAxis,
 132376                prism,
 132377                prismPlanarAxis,
 132378                prismAxis,
 132379                prismNormal,
 132380                ref penetration)
 132381            || !CheckCapsuleEmbeddedCapsuleEdgeAxis(
 132382                capsule,
 132383                capsuleAxis,
 132384                prism,
 132385                prismPlanarAxis,
 132386                prismAxis,
 132387                ref penetration))
 388        {
 8389            return false;
 390        }
 391
 124392        Vector3d closestFeatureAxis =
 124393            FixedSegment.GetClosestDirectionBetweenCenteredAxes(
 124394                capsule.Center,
 124395                capsuleAxis,
 124396                capsule.AxisLength,
 124397                GetEmbeddedCenter3D(prism),
 124398                prism.AxisLength <= Fixed64.Epsilon
 124399                    ? Vector3d.Up
 124400                    : prismAxis,
 124401                prism.AxisLength);
 124402        if (!CheckCapsulePrismAxis(
 124403                capsule,
 124404                prism,
 124405                capsuleAxis,
 124406                prismPlanarAxis,
 124407                closestFeatureAxis,
 124408                ref penetration))
 1409            return false;
 410
 123411        return penetration.HasValue;
 412    }
 413
 414    private static bool TryTestCylinderCapsuleSlab(
 415        LSCylinderCollider cylinder,
 416        LSCapsuleCollider2D prism,
 417        out AxisPenetration penetration)
 418    {
 104419        penetration = default;
 104420        Vector3d cylinderAxis = GetRigidUpAxis(cylinder.Rotation);
 104421        GetEmbeddedCapsuleAxes(
 104422            prism,
 104423            out Vector2d prismPlanarAxis,
 104424            out Vector3d prismAxis,
 104425            out Vector3d prismNormal);
 426
 104427        if (!CheckCylinderPrismAxis(
 104428                cylinder,
 104429                prism,
 104430                cylinderAxis,
 104431                prismPlanarAxis,
 104432                cylinderAxis,
 104433                ref penetration))
 1434            return false;
 435
 103436        CheckCylinderPrismAxis(
 103437            cylinder,
 103438            prism,
 103439            cylinderAxis,
 103440            prismPlanarAxis,
 103441            Vector3d.Up,
 103442            ref penetration);
 443
 103444        if (!CheckCylinderPrismAxis(
 103445                cylinder,
 103446                prism,
 103447                cylinderAxis,
 103448                prismPlanarAxis,
 103449                Vector3d.Cross(cylinderAxis, Vector3d.Up),
 103450                ref penetration))
 1451            return false;
 452
 102453        if (!CheckEmbeddedCapsuleAxes(
 102454                cylinder,
 102455                cylinderAxis,
 102456                prism,
 102457                prismPlanarAxis,
 102458                prismAxis,
 102459                prismNormal,
 102460                ref penetration)
 102461            || !CheckCylinderEmbeddedCapsuleEdgeAxis(
 102462                cylinder,
 102463                cylinderAxis,
 102464                prism,
 102465                prismPlanarAxis,
 102466                prismAxis,
 102467                ref penetration))
 468        {
 8469            return false;
 470        }
 471
 94472        Vector3d closestFeatureAxis =
 94473            FixedSegment.GetClosestDirectionBetweenCenteredAxes(
 94474                cylinder.Center,
 94475                cylinderAxis,
 94476                cylinder.Height,
 94477                GetEmbeddedCenter3D(prism),
 94478                prism.AxisLength <= Fixed64.Epsilon
 94479                    ? Vector3d.Up
 94480                    : prismAxis,
 94481                prism.AxisLength);
 94482        if (!CheckCylinderPrismAxis(
 94483                cylinder,
 94484                prism,
 94485                cylinderAxis,
 94486                prismPlanarAxis,
 94487                closestFeatureAxis,
 94488                ref penetration))
 1489            return false;
 490
 93491        return penetration.HasValue;
 492    }
 493
 494    private static bool TryTestConeCapsuleSlab(
 495        LSConeCollider cone,
 496        LSCapsuleCollider2D prism,
 497        out AxisPenetration penetration)
 498    {
 101499        penetration = default;
 101500        Vector3d coneAxis = GetRigidUpAxis(cone.Rotation);
 101501        GetEmbeddedCapsuleAxes(
 101502            prism,
 101503            out Vector2d prismPlanarAxis,
 101504            out Vector3d prismAxis,
 101505            out Vector3d prismNormal);
 506
 101507        if (!CheckConePrismAxis(
 101508                cone,
 101509                prism,
 101510                prismPlanarAxis,
 101511                coneAxis,
 101512                ref penetration))
 1513            return false;
 514
 100515        CheckConePrismAxis(
 100516            cone,
 100517            prism,
 100518            prismPlanarAxis,
 100519            Vector3d.Up,
 100520            ref penetration);
 521
 100522        if (!CheckConePrismAxis(
 100523                cone,
 100524                prism,
 100525                prismPlanarAxis,
 100526                Vector3d.Cross(coneAxis, Vector3d.Up),
 100527                ref penetration))
 1528            return false;
 529
 99530        if (!CheckEmbeddedCapsuleAxes(
 99531                cone,
 99532                prism,
 99533                prismPlanarAxis,
 99534                prismAxis,
 99535                prismNormal,
 99536                ref penetration)
 99537            || !CheckConeEmbeddedCapsuleEdgeAxis(
 99538                cone,
 99539                coneAxis,
 99540                prism,
 99541                prismPlanarAxis,
 99542                prismAxis,
 99543                ref penetration))
 544        {
 3545            return false;
 546        }
 547
 96548        ContactAnchor embeddedPoint =
 96549            MixedEmbedded2DGeometry.GetClosestAnchorOnEmbeddedVolume(
 96550                prism,
 96551                cone.Center);
 552        // Earlier exact axes admit a shared finite feature neighborhood, so
 553        // each relative offset remains representable through the cone frame.
 96554        _ = embeddedPoint.TryGetOffsetFrom(
 96555            cone.Center,
 96556            out Vector3d centerToEmbedded);
 96557        _ = cone.Rotation.Inverse().TryRotate(
 96558            centerToEmbedded,
 96559            out Vector3d localEmbeddedPoint);
 96560        _ = FixedSegment.TryGetClosestCenteredFiniteConeSurfaceOffset(
 96561            localEmbeddedPoint,
 96562            Vector3d.Zero,
 96563            Vector3d.Up,
 96564            cone.Height,
 96565            cone.ScaledRadius,
 96566            Vector3d.Right,
 96567            out Vector3d coneSurfaceOffset,
 96568            out _,
 96569            out _);
 96570        _ = embeddedPoint.TryGetOffsetFrom(
 96571            new ContactAnchor(
 96572                cone.Center,
 96573                cone.Rotation,
 96574                coneSurfaceOffset),
 96575            out Vector3d closestFeatureAxis);
 96576        if (!CheckConePrismAxis(
 96577                cone,
 96578                prism,
 96579                prismPlanarAxis,
 96580                closestFeatureAxis,
 96581                ref penetration))
 8582            return false;
 583
 88584        return penetration.HasValue;
 585    }
 586
 587    private static bool CheckCapsuleCircleSlabAxis(
 588        LSCapsuleCollider capsule,
 589        LSCircleCollider2D circle,
 590        Vector3d capsuleAxis,
 591        Vector3d axis,
 592        ref AxisPenetration penetration)
 593    {
 381594        if (!TryNormalizeAxis(axis, out Vector3d normalizedAxis))
 45595            return true;
 596
 336597        if (!FixedSegment.TryGetCenteredFiniteCylinderCapsuleAxisPenetration(
 336598            normalizedAxis,
 336599            GetEmbeddedCenter3D(circle),
 336600            Vector3d.Up,
 336601            circle.MixedHalfThickness * Fixed64.Two,
 336602            circle.ScaledRadius,
 336603            capsule.Center,
 336604            capsuleAxis,
 336605            capsule.AxisLength,
 336606            capsule.ScaledRadius,
 336607            out Vector3d circleToCapsuleAxis,
 336608            out Fixed64 depth,
 336609            out bool depthIsClamped))
 610        {
 3611            return false;
 612        }
 613
 333614        if (!penetration.HasValue || depth < penetration.Depth)
 615        {
 225616            penetration = new AxisPenetration(
 225617                -circleToCapsuleAxis,
 225618                depth,
 225619                depthIsClamped);
 620        }
 621
 333622        return true;
 623    }
 624
 625    private static bool CheckCylinderCircleSlabAxis(
 626        LSCylinderCollider cylinder,
 627        LSCircleCollider2D circle,
 628        Vector3d cylinderAxis,
 629        Vector3d axis,
 630        ref AxisPenetration penetration)
 631    {
 264632        if (!TryNormalizeAxis(axis, out Vector3d normalizedAxis))
 28633            return true;
 634
 236635        if (!FixedSegment.TryGetCenteredFiniteCylinderCapsuleSlabAxisPenetration(
 236636                normalizedAxis,
 236637                cylinder.Center,
 236638                cylinderAxis,
 236639                cylinder.Height,
 236640                cylinder.ScaledRadius,
 236641                GetEmbeddedCenter3D(circle),
 236642                Vector2d.Forward,
 236643                Fixed64.Zero,
 236644                circle.ScaledRadius,
 236645                circle.MixedHalfThickness,
 236646                out Vector3d orientedAxis,
 236647                out Fixed64 depth,
 236648                out bool depthIsClamped))
 649        {
 3650            return false;
 651        }
 652
 233653        return KeepAxisPenetration(
 233654            orientedAxis,
 233655            depth,
 233656            depthIsClamped,
 233657            ref penetration);
 658    }
 659
 660    private static bool CheckConeCircleSlabAxis(
 661        LSConeCollider cone,
 662        LSCircleCollider2D circle,
 663        Vector3d axis,
 664        ref AxisPenetration penetration)
 665    {
 256666        if (!TryNormalizeAxis(axis, out Vector3d normalizedAxis))
 22667            return true;
 668
 234669        if (!FixedSegment.TryGetCenteredFiniteConeCapsuleSlabAxisPenetration(
 234670                normalizedAxis,
 234671                cone.Center,
 234672                GetRigidUpAxis(cone.Rotation),
 234673                cone.Height,
 234674                cone.ScaledRadius,
 234675                GetEmbeddedCenter3D(circle),
 234676                Vector2d.Forward,
 234677                Fixed64.Zero,
 234678                circle.ScaledRadius,
 234679                circle.MixedHalfThickness,
 234680                out Vector3d orientedAxis,
 234681                out Fixed64 depth,
 234682                out bool depthIsClamped))
 683        {
 3684            return false;
 685        }
 686
 231687        return KeepAxisPenetration(
 231688            orientedAxis,
 231689            depth,
 231690            depthIsClamped,
 231691            ref penetration);
 692    }
 693
 694    private static bool CheckCapsulePrismAxis(
 695        LSCapsuleCollider capsule,
 696        LSCapsuleCollider2D prism,
 697        Vector3d capsuleAxis,
 698        Vector2d prismAxis,
 699        Vector3d axis,
 700        ref AxisPenetration penetration)
 701    {
 932702        if (!TryNormalizeAxis(axis, out Vector3d normalizedAxis))
 51703            return true;
 704
 881705        if (!FixedSegment.TryGetCenteredCapsuleCapsuleSlabAxisPenetration(
 881706                normalizedAxis,
 881707                capsule.Center,
 881708                capsuleAxis,
 881709                capsule.AxisLength,
 881710                capsule.ScaledRadius,
 881711                GetEmbeddedCenter3D(prism),
 881712                prismAxis,
 881713                prism.AxisLength,
 881714                prism.ScaledRadius,
 881715                prism.MixedHalfThickness,
 881716                out Vector3d orientedAxis,
 881717                out Fixed64 depth,
 881718                out bool depthIsClamped))
 18719            return false;
 720
 863721        return KeepAxisPenetration(
 863722            orientedAxis,
 863723            depth,
 863724            depthIsClamped,
 863725            ref penetration);
 726    }
 727
 728    private static bool CheckCylinderPrismAxis(
 729        LSCylinderCollider cylinder,
 730        LSCapsuleCollider2D prism,
 731        Vector3d cylinderAxis,
 732        Vector2d prismAxis,
 733        Vector3d axis,
 734        ref AxisPenetration penetration)
 735    {
 701736        if (!TryNormalizeAxis(axis, out Vector3d normalizedAxis))
 43737            return true;
 738
 658739        if (!FixedSegment.TryGetCenteredFiniteCylinderCapsuleSlabAxisPenetration(
 658740                normalizedAxis,
 658741                cylinder.Center,
 658742                cylinderAxis,
 658743                cylinder.Height,
 658744                cylinder.ScaledRadius,
 658745                GetEmbeddedCenter3D(prism),
 658746                prismAxis,
 658747                prism.AxisLength,
 658748                prism.ScaledRadius,
 658749                prism.MixedHalfThickness,
 658750                out Vector3d orientedAxis,
 658751                out Fixed64 depth,
 658752                out bool depthIsClamped))
 11753            return false;
 754
 647755        return KeepAxisPenetration(
 647756            orientedAxis,
 647757            depth,
 647758            depthIsClamped,
 647759            ref penetration);
 760    }
 761
 762    private static bool CheckConePrismAxis(
 763        LSConeCollider cone,
 764        LSCapsuleCollider2D prism,
 765        Vector2d prismAxis,
 766        Vector3d axis,
 767        ref AxisPenetration penetration)
 768    {
 690769        if (!TryNormalizeAxis(axis, out Vector3d normalizedAxis))
 46770            return true;
 771
 644772        if (!FixedSegment.TryGetCenteredFiniteConeCapsuleSlabAxisPenetration(
 644773                normalizedAxis,
 644774                cone.Center,
 644775                GetRigidUpAxis(cone.Rotation),
 644776                cone.Height,
 644777                cone.ScaledRadius,
 644778                GetEmbeddedCenter3D(prism),
 644779                prismAxis,
 644780                prism.AxisLength,
 644781                prism.ScaledRadius,
 644782                prism.MixedHalfThickness,
 644783                out Vector3d orientedAxis,
 644784                out Fixed64 depth,
 644785                out bool depthIsClamped))
 13786            return false;
 787
 631788        return KeepAxisPenetration(
 631789            orientedAxis,
 631790            depth,
 631791            depthIsClamped,
 631792            ref penetration);
 793    }
 794
 795    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 796    private static bool KeepAxisPenetration(
 797        Vector3d axis,
 798        Fixed64 depth,
 799        bool depthIsClamped,
 800        ref AxisPenetration penetration)
 801    {
 2605802        if (!penetration.HasValue || depth < penetration.Depth)
 803        {
 1184804            penetration = new AxisPenetration(
 1184805                axis,
 1184806                depth,
 1184807                depthIsClamped);
 808        }
 809
 2605810        return true;
 811    }
 812
 813    private static bool CheckEmbeddedCapsuleAxes(
 814        LSCapsuleCollider capsule3D,
 815        Vector3d capsule3DAxis,
 816        LSCapsuleCollider2D capsule2D,
 817        Vector2d capsule2DPlanarAxis,
 818        Vector3d capsule2DAxis,
 819        Vector3d capsule2DNormal,
 820        ref AxisPenetration penetration)
 821    {
 132822        return CheckCapsulePrismAxis(
 132823                capsule3D,
 132824                capsule2D,
 132825                capsule3DAxis,
 132826                capsule2DPlanarAxis,
 132827                capsule2DAxis,
 132828                ref penetration)
 132829            && CheckCapsulePrismAxis(
 132830                capsule3D,
 132831                capsule2D,
 132832                capsule3DAxis,
 132833                capsule2DPlanarAxis,
 132834                capsule2DNormal,
 132835                ref penetration);
 836    }
 837
 838    private static bool CheckEmbeddedCapsuleAxes(
 839        LSCylinderCollider cylinder,
 840        Vector3d cylinderAxis,
 841        LSCapsuleCollider2D capsule,
 842        Vector2d capsulePlanarAxis,
 843        Vector3d capsuleAxis,
 844        Vector3d capsuleNormal,
 845        ref AxisPenetration penetration)
 846    {
 102847        return CheckCylinderPrismAxis(
 102848                cylinder,
 102849                capsule,
 102850                cylinderAxis,
 102851                capsulePlanarAxis,
 102852                capsuleAxis,
 102853                ref penetration)
 102854            && CheckCylinderPrismAxis(
 102855                cylinder,
 102856                capsule,
 102857                cylinderAxis,
 102858                capsulePlanarAxis,
 102859                capsuleNormal,
 102860                ref penetration);
 861    }
 862
 863    private static bool CheckEmbeddedCapsuleAxes(
 864        LSConeCollider cone,
 865        LSCapsuleCollider2D capsule,
 866        Vector2d capsulePlanarAxis,
 867        Vector3d capsuleAxis,
 868        Vector3d capsuleNormal,
 869        ref AxisPenetration penetration)
 870    {
 99871        return CheckConePrismAxis(
 99872                cone,
 99873                capsule,
 99874                capsulePlanarAxis,
 99875                capsuleAxis,
 99876                ref penetration)
 99877            && CheckConePrismAxis(
 99878                cone,
 99879                capsule,
 99880                capsulePlanarAxis,
 99881                capsuleNormal,
 99882                ref penetration);
 883    }
 884
 885    private static bool CheckCapsuleEmbeddedCapsuleEdgeAxis(
 886        LSCapsuleCollider capsule3D,
 887        Vector3d capsule3DAxis,
 888        LSCapsuleCollider2D capsule2D,
 889        Vector2d capsule2DPlanarAxis,
 890        Vector3d capsule2DAxis,
 891        ref AxisPenetration penetration)
 892    {
 124893        return CheckCapsulePrismAxis(
 124894            capsule3D,
 124895            capsule2D,
 124896            capsule3DAxis,
 124897            capsule2DPlanarAxis,
 124898            Vector3d.Cross(capsule3DAxis, capsule2DAxis),
 124899            ref penetration);
 900    }
 901
 902    private static bool CheckCylinderEmbeddedCapsuleEdgeAxis(
 903        LSCylinderCollider cylinder,
 904        Vector3d cylinderAxis,
 905        LSCapsuleCollider2D capsule,
 906        Vector2d capsulePlanarAxis,
 907        Vector3d capsuleAxis,
 908        ref AxisPenetration penetration)
 909    {
 94910        return CheckCylinderPrismAxis(
 94911            cylinder,
 94912            capsule,
 94913            cylinderAxis,
 94914            capsulePlanarAxis,
 94915            Vector3d.Cross(cylinderAxis, capsuleAxis),
 94916            ref penetration);
 917    }
 918
 919    private static bool CheckConeEmbeddedCapsuleEdgeAxis(
 920        LSConeCollider cone,
 921        Vector3d coneAxis,
 922        LSCapsuleCollider2D capsule,
 923        Vector2d capsulePlanarAxis,
 924        Vector3d capsuleAxis,
 925        ref AxisPenetration penetration)
 926    {
 96927        return CheckConePrismAxis(
 96928            cone,
 96929            capsule,
 96930            capsulePlanarAxis,
 96931            Vector3d.Cross(coneAxis, capsuleAxis),
 96932            ref penetration);
 933    }
 934
 935    private static bool BuildCapsuleContact(
 936        LSCapsuleCollider capsule,
 937        LSCollider2D embedded,
 938        AxisPenetration penetration,
 939        out MixedContact contact)
 940    {
 216941        contact = BuildCanonicalSupportContact(
 216942            capsule,
 216943            embedded,
 216944            penetration.Axis,
 216945            penetration.Depth,
 216946            penetration.DepthIsClamped);
 216947        return true;
 948    }
 949
 950    private static bool BuildCylinderContact(
 951        LSCylinderCollider cylinder,
 952        LSCollider2D embedded,
 953        AxisPenetration penetration,
 954        out MixedContact contact)
 955    {
 157956        contact = BuildCanonicalSupportContact(
 157957            cylinder,
 157958            embedded,
 157959            penetration.Axis,
 157960            penetration.Depth,
 157961            penetration.DepthIsClamped);
 157962        return true;
 963    }
 964
 965    private static bool BuildConeContact(
 966        LSConeCollider cone,
 967        LSCollider2D embedded,
 968        AxisPenetration penetration,
 969        out MixedContact contact)
 970    {
 150971        contact = BuildCanonicalSupportContact(
 150972            cone,
 150973            embedded,
 150974            penetration.Axis,
 150975            penetration.Depth,
 150976            penetration.DepthIsClamped);
 150977        return true;
 978    }
 979
 980    private static MixedContact BuildCanonicalSupportContact(
 981        LSCollider collider3D,
 982        LSCollider2D embedded,
 983        Vector3d normal3DTo2D,
 984        Fixed64 depth,
 985        bool depthIsClamped)
 986    {
 523987        return new MixedContact(
 523988            new ContactAnchor(
 523989                ConvexColliderSupport.GetSupportAnchor(
 523990                    collider3D,
 523991                    normal3DTo2D,
 523992                    Vector3d.Zero)),
 523993            MixedEmbedded2DGeometry.GetSupportAnchor(embedded, -normal3DTo2D),
 523994            normal3DTo2D,
 523995            depth,
 523996            depthIsClamped);
 997    }
 998
 999}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/Mixed/CollisionDetectionMixed.FinitePrisms.cs

#LineLine coverage
 1//=======================================================================
 2// CollisionDetectionMixed.FinitePrisms.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 System;
 12
 13namespace Gravitas.CollisionHandling;
 14
 15public static partial class CollisionDetectionMixed
 16{
 17    private static bool TryCuboidEmbedded2D(
 18        LSCuboidCollider cuboid,
 19        LSCollider2D embedded,
 20        out MixedContact contact)
 21    {
 200322        if (embedded is LSCircleCollider2D circle)
 23        {
 137224            return TryGetCuboidRelationContact(
 137225                cuboid.OrientedBox.TryGetCircleSlabContact(
 137226                    GetEmbeddedCenter3D(circle),
 137227                    circle.Rotation,
 137228                    circle.MixedHalfThickness,
 137229                    circle.ScaledRadius,
 137230                    out FixedContactAnchors anchors),
 137231                anchors,
 137232                out contact);
 33        }
 34
 63135        if (embedded is LSCapsuleCollider2D capsule)
 36        {
 11937            return TryGetCuboidRelationContact(
 11938                cuboid.OrientedBox.TryGetCenteredCapsuleSlabContact(
 11939                    GetEmbeddedCenter3D(capsule),
 11940                    capsule.Rotation,
 11941                    Vector2d.Forward,
 11942                    capsule.AxisLength,
 11943                    capsule.ScaledRadius,
 11944                    capsule.MixedHalfThickness,
 11945                    out FixedContactAnchors anchors),
 11946                anchors,
 11947                out contact);
 48        }
 49
 51250        if (embedded is LSPolygonCollider2D polygon)
 51        {
 37652            return TryGetCuboidPrismContact(
 37653                cuboid,
 37654                embedded,
 37655                polygon.ScaledLocalVertices,
 37656                out contact);
 57        }
 58
 13659        Vector2d halfExtents =
 13660            ((LSAABBoxCollider2D)embedded).ScaledHalfExtents;
 13661        Span<Vector2d> centerRelativeVertices = stackalloc Vector2d[4];
 13662        centerRelativeVertices[0] = new Vector2d(-halfExtents.X, -halfExtents.Y);
 13663        centerRelativeVertices[1] = new Vector2d(halfExtents.X, -halfExtents.Y);
 13664        centerRelativeVertices[2] = new Vector2d(halfExtents.X, halfExtents.Y);
 13665        centerRelativeVertices[3] = new Vector2d(-halfExtents.X, halfExtents.Y);
 13666        return TryGetCuboidPrismContact(
 13667            cuboid,
 13668            embedded,
 13669            centerRelativeVertices,
 13670            out contact);
 71    }
 72
 73    private static bool TryGetCuboidPrismContact(
 74        LSCuboidCollider cuboid,
 75        LSCollider2D embedded,
 76        ReadOnlySpan<Vector2d> centerRelativeVertices,
 77        out MixedContact contact)
 78    {
 51279        Vector3d embeddedCenter = GetEmbeddedCenter3D(embedded);
 51280        return TryGetCuboidRelationContact(
 51281            cuboid.OrientedBox.TryGetConvexPrismContact(
 51282                embeddedCenter,
 51283                embedded.ConvexRotation,
 51284                centerRelativeVertices,
 51285                embedded.MixedHalfThickness,
 51286                out FixedContactAnchors anchors),
 51287            anchors,
 51288            out contact);
 89    }
 90
 91    private static bool TryGetCuboidRelationContact(
 92        bool collided,
 93        in FixedContactAnchors anchors,
 94        out MixedContact contact)
 95    {
 200396        if (!collided)
 75497            return NoContact(out contact);
 98
 124999        contact = new MixedContact(
 1249100            new ContactAnchor(anchors.FirstAnchor),
 1249101            new ContactAnchor(anchors.SecondAnchor),
 1249102            anchors.Normal,
 1249103            anchors.Depth,
 1249104            anchors.DepthIsClamped);
 1249105        return true;
 106    }
 107
 108    private static bool TryGetTriangleConvexPrismContact(
 109        LSMeshCollider mesh,
 110        int triangleIndex,
 111        LSCollider2D embedded,
 112        out MixedContact contact)
 113    {
 23114        mesh.Mesh.GetLocalTriangleVertices(
 23115            triangleIndex,
 23116            out Vector3d first,
 23117            out Vector3d second,
 23118            out Vector3d third);
 23119        Span<Vector2d> boxOffsets = stackalloc Vector2d[4];
 23120        ReadOnlySpan<Vector2d> prismOffsets = GetConvexPrismOffsets(
 23121            embedded,
 23122            boxOffsets);
 23123        bool collided = FixedConvexPrismRelations.TryGetTriangleContact(
 23124            mesh.Mesh.Origin,
 23125            mesh.Mesh.Rotation,
 23126            new FixedTriangle(first, second, third),
 23127            GetEmbeddedCenter3D(embedded),
 23128            embedded.ConvexRotation,
 23129            prismOffsets,
 23130            embedded.MixedHalfThickness,
 23131            out FixedContactAnchors relation);
 23132        return TryBuildFinitePrismContact(
 23133            collided,
 23134            relation,
 23135            out contact);
 136    }
 137
 138    private static bool TryGetSphereConvexPrismContact(
 139        LSSphereCollider sphere,
 140        LSCollider2D embedded,
 141        out MixedContact contact)
 142    {
 1188143        Span<Vector2d> boxOffsets = stackalloc Vector2d[4];
 1188144        ReadOnlySpan<Vector2d> offsets = GetConvexPrismOffsets(
 1188145            embedded,
 1188146            boxOffsets);
 1188147        bool collided = FixedConvexPrismRelations.TryGetSphereContact(
 1188148            sphere.Center,
 1188149            sphere.ScaledRadius,
 1188150            GetEmbeddedCenter3D(embedded),
 1188151            embedded.ConvexRotation,
 1188152            offsets,
 1188153            embedded.MixedHalfThickness,
 1188154            out FixedContactAnchors relation);
 1188155        return TryBuildFinitePrismContact(
 1188156            collided,
 1188157            relation,
 1188158            out contact);
 159    }
 160
 161    private static bool TryGetCapsuleConvexPrismContact(
 162        LSCapsuleCollider capsule,
 163        LSCollider2D embedded,
 164        out MixedContact contact)
 165    {
 301166        Span<Vector2d> boxOffsets = stackalloc Vector2d[4];
 301167        ReadOnlySpan<Vector2d> offsets = GetConvexPrismOffsets(
 301168            embedded,
 301169            boxOffsets);
 301170        bool collided =
 301171            FixedConvexPrismRelations.TryGetCenteredCapsuleContact(
 301172                capsule.Center,
 301173                capsule.Rotation,
 301174                Vector3d.Up,
 301175                capsule.AxisLength,
 301176                capsule.ScaledRadius,
 301177                GetEmbeddedCenter3D(embedded),
 301178                embedded.ConvexRotation,
 301179                offsets,
 301180                embedded.MixedHalfThickness,
 301181                out FixedContactAnchors relation);
 301182        return TryBuildFinitePrismContact(
 301183            collided,
 301184            relation,
 301185            out contact);
 186    }
 187
 188    private static bool TryGetCylinderConvexPrismContact(
 189        LSCylinderCollider cylinder,
 190        LSCollider2D embedded,
 191        out MixedContact contact)
 192    {
 239193        Span<Vector2d> boxOffsets = stackalloc Vector2d[4];
 239194        ReadOnlySpan<Vector2d> offsets = GetConvexPrismOffsets(
 239195            embedded,
 239196            boxOffsets);
 239197        bool collided =
 239198            FixedConvexPrismRelations.TryGetCenteredCylinderContact(
 239199                cylinder.Center,
 239200                cylinder.Rotation,
 239201                Vector3d.Up,
 239202                cylinder.Height,
 239203                cylinder.ScaledRadius,
 239204                GetEmbeddedCenter3D(embedded),
 239205                embedded.ConvexRotation,
 239206                offsets,
 239207                embedded.MixedHalfThickness,
 239208                out FixedContactAnchors relation);
 239209        return TryBuildFinitePrismContact(
 239210            collided,
 239211            relation,
 239212            out contact);
 213    }
 214
 215    private static bool TryGetConeConvexPrismContact(
 216        LSConeCollider cone,
 217        LSCollider2D embedded,
 218        out MixedContact contact)
 219    {
 230220        Span<Vector2d> boxOffsets = stackalloc Vector2d[4];
 230221        ReadOnlySpan<Vector2d> offsets = GetConvexPrismOffsets(
 230222            embedded,
 230223            boxOffsets);
 230224        bool collided =
 230225            FixedConvexPrismRelations.TryGetCenteredConeContact(
 230226                cone.Center,
 230227                cone.Rotation,
 230228                Vector3d.Up,
 230229                cone.Height,
 230230                cone.ScaledRadius,
 230231                GetEmbeddedCenter3D(embedded),
 230232                embedded.ConvexRotation,
 230233                offsets,
 230234                embedded.MixedHalfThickness,
 230235                out FixedContactAnchors relation);
 230236        return TryBuildFinitePrismContact(
 230237            collided,
 230238            relation,
 230239            out contact);
 240    }
 241
 242    private static ReadOnlySpan<Vector2d> GetConvexPrismOffsets(
 243        LSCollider2D embedded,
 244        Span<Vector2d> boxOffsets)
 245    {
 1981246        if (embedded is LSPolygonCollider2D polygon)
 1580247            return polygon.ScaledLocalVertices;
 248
 401249        Vector2d halfExtents =
 401250            ((LSAABBoxCollider2D)embedded).ScaledHalfExtents;
 401251        boxOffsets[0] = new Vector2d(-halfExtents.X, -halfExtents.Y);
 401252        boxOffsets[1] = new Vector2d(halfExtents.X, -halfExtents.Y);
 401253        boxOffsets[2] = new Vector2d(halfExtents.X, halfExtents.Y);
 401254        boxOffsets[3] = new Vector2d(-halfExtents.X, halfExtents.Y);
 401255        return boxOffsets;
 256    }
 257
 258    private static bool TryBuildFinitePrismContact(
 259        bool collided,
 260        in FixedContactAnchors relation,
 261        out MixedContact contact)
 262    {
 2011263        if (!collided)
 619264            return NoContact(out contact);
 265
 1392266        contact = new MixedContact(
 1392267            new ContactAnchor(relation.FirstAnchor),
 1392268            new ContactAnchor(relation.SecondAnchor),
 1392269            relation.Normal,
 1392270            relation.Depth,
 1392271            relation.DepthIsClamped);
 1392272        return true;
 273    }
 274}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/Mixed/CollisionDetectionMixed.Separation.cs

#LineLine coverage
 1//=======================================================================
 2// CollisionDetectionMixed.Separation.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 System;
 12
 13namespace Gravitas.CollisionHandling;
 14
 15public static partial class CollisionDetectionMixed
 16{
 17    internal static bool TryGetRotationalSeparationGap(
 18        LSCollider collider3D,
 19        LSCollider2D collider2D,
 20        out Fixed64 separationGap,
 21        out bool supported)
 22    {
 467023        separationGap = Fixed64.Zero;
 467024        supported = false;
 25
 467026        if (collider3D is LSSphereCollider sphere
 467027            && collider2D is LSPolygonCollider2D polygon)
 28        {
 178929            supported = true;
 178930            return TryGetSpherePolygonSlabSeparationGap(
 178931                sphere,
 178932                polygon,
 178933                out separationGap);
 34        }
 35
 288136        if (collider3D is LSCuboidCollider cuboid
 288137            && collider2D is LSCircleCollider2D circle)
 38        {
 199739            supported = true;
 199740            separationGap = cuboid.OrientedBox.GetCircleSlabSeparationLowerBound(
 199741                new Vector3d(
 199742                    circle.Center.X,
 199743                    circle.MixedSlabCenterY,
 199744                    circle.Center.Y),
 199745                circle.MixedHalfThickness,
 199746                circle.ScaledRadius);
 199747            return true;
 48        }
 49
 88450        return false;
 51    }
 52
 53    private static bool TryGetSpherePolygonSlabSeparationGap(
 54        LSSphereCollider sphere,
 55        LSPolygonCollider2D polygon,
 56        out Fixed64 separationGap)
 57    {
 178958        Vector3d center3D = sphere.Center;
 178959        Vector2d center2D = new(center3D.X, center3D.Z);
 178960        if (!TryGetPolygonExteriorGap(polygon, center2D, out Fixed64 planarGap)
 178961            || !TryGetPointIntervalGap(
 178962                center3D.Y,
 178963                polygon.MixedBounds3D.Min.Y,
 178964                polygon.MixedBounds3D.Max.Y,
 178965                out Fixed64 verticalGap))
 66        {
 567            separationGap = default;
 568            return false;
 69        }
 70
 71        // Both gaps are non-negative representable distances, so subtracting a
 72        // positive representable radius cannot leave Fixed64's signed domain.
 178473        separationGap = FixedMath.Max(planarGap, verticalGap) - sphere.ScaledRadius;
 178474        if (separationGap < Fixed64.Zero)
 71275            separationGap = Fixed64.Zero;
 178476        return true;
 77    }
 78
 79    private static bool TryGetPolygonExteriorGap(
 80        LSPolygonCollider2D polygon,
 81        Vector2d point,
 82        out Fixed64 exteriorGap)
 83    {
 178984        ReadOnlySpan<Vector2d> offsets = polygon.ScaledLocalVertices;
 178985        if (FixedConvex2dRelations.ContainsPoint(
 178986                point,
 178987                polygon.Center,
 178988                polygon.Rotation,
 178989                offsets))
 90        {
 691            exteriorGap = Fixed64.Zero;
 692            return true;
 93        }
 94
 178395        FixedPointAnchor2d closestPoint =
 178396            FixedConvex2dRelations.GetClosestPointAnchor(
 178397                point,
 178398                polygon.Center,
 178399                polygon.Rotation,
 1783100                offsets);
 1783101        if (!new ContactAnchor2D(closestPoint)
 1783102                .TryGetOffsetFrom(point, out Vector2d difference)
 1783103            || !Vector2d.TryGetMagnitude(difference, out exteriorGap))
 104        {
 3105            exteriorGap = default;
 3106            return false;
 107        }
 108
 1780109        return true;
 110    }
 111
 112    private static bool TryGetPointIntervalGap(
 113        Fixed64 point,
 114        Fixed64 min,
 115        Fixed64 max,
 116        out Fixed64 gap) =>
 1786117        TryGetIntervalGap(point, point, min, max, out gap);
 118
 119    private static bool TryGetIntervalGap(
 120        Fixed64 firstMin,
 121        Fixed64 firstMax,
 122        Fixed64 secondMin,
 123        Fixed64 secondMax,
 124        out Fixed64 gap)
 125    {
 1786126        if (firstMax < secondMin)
 26127            return Fixed64.TrySubtract(secondMin, firstMax, out gap);
 1760128        if (secondMax < firstMin)
 2129            return Fixed64.TrySubtract(firstMin, secondMax, out gap);
 130
 1758131        gap = Fixed64.Zero;
 1758132        return true;
 133    }
 134}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/Mixed/CollisionDetectionMixed.Support.cs

#LineLine coverage
 1//=======================================================================
 2// CollisionDetectionMixed.Support.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 System.Runtime.CompilerServices;
 12
 13namespace Gravitas.CollisionHandling;
 14
 15public static partial class CollisionDetectionMixed
 16{
 17    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 18    private static bool TryNormalizeAxis(
 19        Vector3d axis,
 20        out Vector3d normalizedAxis)
 21    {
 322422        Fixed64 magnitudeSqr = axis.MagnitudeSquared;
 322423        if (magnitudeSqr <= Fixed64.Epsilon)
 24        {
 23525            normalizedAxis = Vector3d.Zero;
 23526            return false;
 27        }
 28
 298929        normalizedAxis = axis.Normalized;
 298930        return true;
 31    }
 32
 33    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 34    private static Vector3d GetEmbeddedCenter3D(LSCollider2D embedded) =>
 745535        MixedEmbedded2DGeometry.GetCenter3D(embedded);
 36
 37    private static void GetEmbeddedCapsuleAxes(
 38        LSCapsuleCollider2D capsule,
 39        out Vector2d planarAxis,
 40        out Vector3d axis,
 41        out Vector3d normal)
 42    {
 34643        if (capsule.AxisLength <= Fixed64.Epsilon)
 44        {
 345            planarAxis = Vector2d.Forward;
 346            axis = Vector3d.Zero;
 347            normal = Vector3d.Zero;
 348            return;
 49        }
 50
 34351        planarAxis = new Vector2d(
 34352            -FixedMath.Sin(capsule.Rotation),
 34353            FixedMath.Cos(capsule.Rotation));
 34354        axis = new Vector3d(
 34355            planarAxis.X,
 34356            Fixed64.Zero,
 34357            planarAxis.Y);
 34358        Vector2d planarNormal = planarAxis.RightHandNormal;
 34359        normal = new Vector3d(
 34360            planarNormal.X,
 34361            Fixed64.Zero,
 34362            planarNormal.Y);
 34363    }
 64
 65    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 66    private static Vector3d GetRigidUpAxis(FixedQuaternion rotation) =>
 145267        (rotation * Vector3d.Up).Normalized;
 68
 69    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 70    private static Vector3d ResolveFallbackNormal(
 71        Vector3d sphereCenter,
 72        LSCollider2D embedded)
 73    {
 474        Vector3d fallback = new(
 475            embedded.Center.X - sphereCenter.X,
 476            embedded.MixedSlabCenterY - sphereCenter.Y,
 477            embedded.Center.Y - sphereCenter.Z);
 478        return fallback.MagnitudeSquared > Fixed64.Epsilon
 479            ? fallback.Normalized
 480            : Vector3d.Right;
 81    }
 82
 83    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 84    private static bool NoContact(out MixedContact contact)
 85    {
 149286        contact = default;
 149287        return false;
 88    }
 89}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/Mixed/MixedEmbedded2DGeometry.cs

#LineLine coverage
 1//=======================================================================
 2// MixedEmbedded2DGeometry.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 System;
 12
 13namespace Gravitas.CollisionHandling;
 14
 15/// <summary>
 16/// Shared fixed-point geometry helpers for embedded 2D mixed slabs.
 17/// </summary>
 18internal static class MixedEmbedded2DGeometry
 19{
 20    public static Vector3d GetCenter3D(LSCollider2D embedded) =>
 749121        new(embedded.Center.X, embedded.MixedSlabCenterY, embedded.Center.Y);
 22
 23    public static ContactAnchor GetSupportAnchor(LSCollider2D embedded, Vector3d worldDirection)
 24    {
 53425        Vector2d planarDirection = new(worldDirection.X, worldDirection.Z);
 53426        if (!TryGetPlanarSupportAnchor(
 53427                embedded,
 53428                planarDirection,
 53429                out ContactAnchor2D planarAnchor))
 30        {
 131            throw new InvalidOperationException(
 132                "Committed embedded 2D geometry has no representable owner-relative support anchor.");
 33        }
 34
 53335        Fixed64 yOffset = worldDirection.Y > Fixed64.Zero
 53336            ? embedded.MixedHalfThickness
 53337            : worldDirection.Y < Fixed64.Zero
 53338                ? -embedded.MixedHalfThickness
 53339                : Fixed64.Zero;
 53340        return EmbedPlanarAnchor(
 53341            planarAnchor,
 53342            embedded.MixedSlabCenterY,
 53343            yOffset);
 44    }
 45
 46    public static ContactAnchor GetClosestAnchorOnEmbeddedVolume(
 47        LSCollider2D embedded,
 48        Vector3d point)
 49    {
 126750        Vector2d planarPoint = new(point.X, point.Z);
 126751        bool planarInside = embedded.ContainsPoint(planarPoint);
 126752        GetClosestSlabOffset(
 126753            point.Y,
 126754            embedded.MixedSlabCenterY,
 126755            embedded.MixedHalfThickness,
 126756            out Fixed64 closestYOffset,
 126757            out bool yInside,
 126758            out Fixed64 signedCenterOffset);
 59
 126760        if (!planarInside || !yInside)
 61        {
 115062            if (planarInside)
 63            {
 50464                return new ContactAnchor(
 50465                    new Vector3d(
 50466                        planarPoint.X,
 50467                        embedded.MixedSlabCenterY,
 50468                        planarPoint.Y),
 50469                    new Vector3d(
 50470                        Fixed64.Zero,
 50471                        closestYOffset,
 50472                        Fixed64.Zero));
 73            }
 74
 64675            if (TryGetPlanarBoundaryAnchor(
 64676                    embedded,
 64677                    planarPoint,
 64678                    out ContactAnchor2D closestPlanar))
 79            {
 64580                return EmbedPlanarAnchor(
 64581                    closestPlanar,
 64582                    embedded.MixedSlabCenterY,
 64583                    closestYOffset);
 84            }
 85
 186            throw new InvalidOperationException(
 187                "Committed embedded 2D geometry has no semantic boundary anchor.");
 88        }
 89
 11790        bool upperYBoundary = signedCenterOffset > Fixed64.Zero;
 11791        Fixed64 yBoundaryOffset = upperYBoundary
 11792            ? embedded.MixedHalfThickness
 11793            : -embedded.MixedHalfThickness;
 11794        Fixed64 bestDistance = upperYBoundary
 11795            ? embedded.MixedHalfThickness - signedCenterOffset
 11796            : embedded.MixedHalfThickness + signedCenterOffset;
 11797        ContactAnchor closest = new(
 11798            new Vector3d(
 11799                planarPoint.X,
 117100                embedded.MixedSlabCenterY,
 117101                planarPoint.Y),
 117102            new Vector3d(
 117103                Fixed64.Zero,
 117104                yBoundaryOffset,
 117105                Fixed64.Zero));
 106
 107        // A point inside an admitted built-in shape is no farther from its
 108        // nearest boundary than that shape's representable dimensions.
 117109        _ = TryGetPlanarBoundaryAnchor(
 117110            embedded,
 117111            planarPoint,
 117112            out ContactAnchor2D planarBoundary,
 117113            out Fixed64 planarDistance);
 117114        if (planarDistance < bestDistance)
 115        {
 36116            closest = EmbedPlanarAnchor(
 36117                planarBoundary,
 36118                embedded.MixedSlabCenterY,
 36119                signedCenterOffset);
 120        }
 121
 117122        return closest;
 123    }
 124
 125    public static bool ContainsPointInSlab(LSCollider2D embedded, Fixed64 y)
 126    {
 140127        GetClosestSlabOffset(
 140128            y,
 140129            embedded.MixedSlabCenterY,
 140130            embedded.MixedHalfThickness,
 140131            out _,
 140132            out bool inside,
 140133            out _);
 140134        return inside;
 135    }
 136
 137    public static bool TryGetPlanarBoundaryPoint(
 138        LSCollider2D embedded,
 139        Vector2d point,
 140        out Vector2d boundary,
 141        out Fixed64 distance)
 142    {
 11143        if (TryGetPlanarBoundaryAnchor(
 11144                embedded,
 11145                point,
 11146                out ContactAnchor2D anchor,
 11147                out distance)
 11148            && anchor.TryGetWorldPoint(out boundary))
 149        {
 9150            return true;
 151        }
 152
 2153        boundary = default;
 2154        return false;
 155    }
 156
 157    public static bool TryGetPlanarBoundaryAnchor(
 158        LSCollider2D embedded,
 159        Vector2d point,
 160        out ContactAnchor2D boundary)
 161    {
 646162        if (embedded.TryGetClosestBoundaryAnchor(
 646163                point,
 646164                out FixedPointAnchor2d anchor))
 165        {
 645166            boundary = new ContactAnchor2D(anchor);
 645167            return true;
 168        }
 169
 1170        boundary = default;
 1171        return false;
 172    }
 173
 174    public static bool TryGetPlanarBoundaryAnchor(
 175        LSCollider2D embedded,
 176        Vector2d point,
 177        out ContactAnchor2D boundary,
 178        out Fixed64 distance)
 179    {
 134180        if (embedded.TryGetClosestBoundaryAnchor(
 134181                point,
 134182                out FixedPointAnchor2d anchor,
 134183                out distance))
 184        {
 130185            boundary = new ContactAnchor2D(anchor);
 130186            return true;
 187        }
 188
 4189        boundary = default;
 4190        return false;
 191    }
 192
 193    private static bool TryGetPlanarSupportAnchor(
 194        LSCollider2D embedded,
 195        Vector2d direction,
 196        out ContactAnchor2D anchor)
 197    {
 534198        switch (embedded.Shape)
 199        {
 200            case ColliderType2D.Circle:
 201                {
 223202                    var circle = (LSCircleCollider2D)embedded;
 223203                    return TryGetRoundSupportAnchor(
 223204                        circle.Center,
 223205                        circle.Rotation,
 223206                        Fixed64.Zero,
 223207                        circle.ScaledRadius,
 223208                        direction,
 223209                        out anchor);
 210                }
 211            case ColliderType2D.Capsule:
 212                {
 307213                    var capsule = (LSCapsuleCollider2D)embedded;
 307214                    return TryGetRoundSupportAnchor(
 307215                        capsule.Center,
 307216                        capsule.Rotation,
 307217                        capsule.AxisLength,
 307218                        capsule.ScaledRadius,
 307219                        direction,
 307220                        out anchor);
 221                }
 222            case ColliderType2D.AABox:
 223            case ColliderType2D.ConvexPolygon:
 3224                anchor = new ContactAnchor2D(
 3225                    embedded.GetConvexSupportAnchor(
 3226                        direction == Vector2d.Zero
 3227                            ? Vector2d.Right
 3228                            : direction));
 3229                return true;
 230            default:
 1231                anchor = default;
 1232                return false;
 233        }
 234    }
 235
 236    private static bool TryGetRoundSupportAnchor(
 237        Vector2d center,
 238        Fixed64 rotation,
 239        Fixed64 axisLength,
 240        Fixed64 radius,
 241        Vector2d direction,
 242        out ContactAnchor2D anchor)
 243    {
 530244        Vector2d localDirection = direction == Vector2d.Zero
 530245            ? Vector2d.Zero
 530246            : Vector2d.Rotate(direction.Normalized, -rotation);
 247
 530248        FixedPointAnchor2d localAnchor =
 530249            FixedSegment2d.GetCenteredCapsuleSupportAnchor(
 530250                center,
 530251                rotation,
 530252                Vector2d.Forward,
 530253                axisLength,
 530254                radius,
 530255                localDirection);
 530256        anchor = new ContactAnchor2D(localAnchor);
 530257        return true;
 258    }
 259
 260    private static ContactAnchor EmbedPlanarAnchor(
 261        ContactAnchor2D planar,
 262        Fixed64 slabCenterY,
 263        Fixed64 localYDisplacement = default)
 264    {
 1214265        return new ContactAnchor(
 1214266            new Vector3d(planar.Origin.X, slabCenterY, planar.Origin.Y),
 1214267            FixedQuaternion.FromAxisAngle(Vector3d.Up, -planar.Rotation),
 1214268            new Vector3d(
 1214269                planar.LocalPoint.X,
 1214270                Fixed64.Zero,
 1214271                planar.LocalPoint.Y),
 1214272            new Vector3d(
 1214273                planar.LocalDisplacement.X,
 1214274                localYDisplacement,
 1214275                planar.LocalDisplacement.Y));
 276    }
 277
 278    private static void GetClosestSlabOffset(
 279        Fixed64 y,
 280        Fixed64 slabCenterY,
 281        Fixed64 halfThickness,
 282        out Fixed64 closestOffset,
 283        out bool inside,
 284        out Fixed64 signedCenterOffset)
 285    {
 1407286        if (!Fixed64.TrySubtract(y, slabCenterY, out signedCenterOffset))
 287        {
 3288            closestOffset = y < slabCenterY
 3289                ? -halfThickness
 3290                : halfThickness;
 3291            inside = false;
 3292            signedCenterOffset = closestOffset;
 3293            return;
 294        }
 295
 1404296        if (signedCenterOffset < -halfThickness)
 297        {
 11298            closestOffset = -halfThickness;
 11299            inside = false;
 11300            return;
 301        }
 302
 1393303        if (signedCenterOffset > halfThickness)
 304        {
 560305            closestOffset = halfThickness;
 560306            inside = false;
 560307            return;
 308        }
 309
 833310        closestOffset = signedCenterOffset;
 833311        inside = true;
 833312    }
 313}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Pairs/3D/CollisionPair.cs

#LineLine coverage
 1//=======================================================================
 2// CollisionPair.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 GridForge.Grids;
 11using GridForge.Grids.Topology;
 12using SwiftCollections;
 13using System;
 14using System.Runtime.CompilerServices;
 15
 16namespace Gravitas.CollisionHandling;
 17
 18internal enum CollisionResponseDispatchMode
 19{
 20    Immediate,
 21    Deferred
 22}
 23
 24/// <summary>
 25/// Owns deterministic 3D pair lifecycle, culling, contact, and notification state.
 26/// </summary>
 27public partial class CollisionPair
 28{
 29    /// <summary>Stores the host-controlled pair diagnostic flag.</summary>
 63930    public bool Debug = true;
 31
 32    /// <summary>Gets whether this pooled pair currently represents two registered colliders.</summary>
 33    public bool Active { get; private set; }
 34
 35    private bool _isPooledForDeactivation;
 36
 37    /// <summary>Gets the world context that owns this pair.</summary>
 38    public GravitasWorldContext Context { get; private set; } = null!;
 39
 40    /// <summary>Gets the GridForge world owned by <see cref="Context"/>.</summary>
 168241    public GridWorld World => Context.World;
 42
 43    // stores order in which they come in
 44    /// <summary>Gets the first collider ID used to identify the pair.</summary>
 45    public int Id1 { get; private set; }
 46    /// <summary>Gets the second collider ID used to identify the pair.</summary>
 47    public int Id2 { get; private set; }
 48
 49    /// <summary>Gets the collider selected to lead narrow-phase dispatch.</summary>
 50    public LSCollider ColliderA { get; private set; } = null!;
 51    /// <summary>Gets the other collider in the pair.</summary>
 52    public LSCollider ColliderB { get; private set; } = null!;
 53
 54    /// <summary>Tracks the collision-service partition pass that last processed this pair.</summary>
 55    public uint PartitionVersion;
 56    /// <summary>Tracks reuse of this pooled pair instance.</summary>
 63957    public ushort PairVersion = 1;
 58
 59    /// <summary>Gets the last simulation frame in which this pair was updated.</summary>
 60    public int LastFrame { get; private set; }
 61    /// <summary>Gets the last simulation frame in which this pair had contact.</summary>
 62    public int LastCollidedFrame { get; private set; }
 63
 64    private Fixed64 _fastCollideDistance;
 65    private Fixed64 _fastDistance;
 66    /// <summary>Gets the narrow-phase dispatch type for this collider combination.</summary>
 67    public CollisionType CollisionType { get; private set; }
 63968    private bool _doPhysics = true;
 69
 70    /// <summary>Gets the remaining deferred updates before this pair is checked again.</summary>
 71    public short CullCounter { get; private set; }
 72    private bool _preventDistanceCull;
 73    private Fixed64 _fastDistanceOffset;
 74    private uint _lastColliderABroadPhaseVersion;
 75    private uint _lastColliderBBroadPhaseVersion;
 76
 77    private bool _isColliding;
 78    private bool _isCollidingChanged;
 79    private bool _notificationInProgress;
 80    private bool _separationPending;
 81    private bool _colliderANotified;
 82    private bool _colliderBNotified;
 83    private SolidBody? _pendingBodyA;
 84    private SolidBody? _pendingBodyB;
 85    private long _lifetimeVersion;
 86
 3687    internal bool IsNotificationInProgress => _notificationInProgress;
 88
 979889    internal long LifetimeVersion => _lifetimeVersion;
 90
 91    /// <summary>Gets the deterministic contact manifold owned by this pair.</summary>
 92    public ContactManifold Manifold { get; } = new();
 93
 94    private ContactWarmStartCache _warmStart;
 95
 127896    internal CollisionPair(LSCollider c1, LSCollider c2) => Initialize(c1, c2);
 97
 98    /// <summary>
 99    /// Initializes the CollisionPair with the given colliders.
 100    /// </summary>
 101    /// <param name="c1">The first collider.</param>
 102    /// <param name="c2">The second collider.</param>
 103    internal void Initialize(LSCollider c1, LSCollider c2)
 104    {
 640105        SwiftThrowHelper.ThrowIfNull(c1, nameof(c1));
 640106        SwiftThrowHelper.ThrowIfNull(c2, nameof(c2));
 640107        SwiftThrowHelper.ThrowIfArgument(c1 == c2, nameof(c2), "Cannot create a CollisionPair with the same collider.");
 640108        GravitasWorldContext context = c1.Context;
 640109        SwiftThrowHelper.ThrowIfArgument(
 640110            !ReferenceEquals(context, c2.Context),
 640111            nameof(c2),
 640112            "Colliders must be in the same context to create a CollisionPair.");
 113
 639114        Context = context;
 639115        _lifetimeVersion++;
 116
 639117        Reset();
 118
 639119        AssignPriority(c1, c2);
 639120        Id1 = ColliderA.Id;
 639121        Id2 = ColliderB.Id;
 122
 639123        CollisionType = ColliderSettings.GetCollisionType(ColliderA.Shape, ColliderB.Shape);
 124
 125        // Calculate the square of the sum of the radii of the bounding spheres
 639126        _fastCollideDistance = ColliderA!.Bounds.Scope.Magnitude + ColliderB!.Bounds.Scope.Magnitude;
 639127        _fastCollideDistance *= _fastCollideDistance;
 128
 639129        _doPhysics = ColliderA!.Body != null && ColliderB!.Body != null && !ColliderA!.IsTrigger && !ColliderB!.IsTrigge
 130
 131        // Immediately check collision. If collision distance is too large, do
 132        // not cull based on distance.
 639133        CullCounter = 0;
 639134        _preventDistanceCull = _fastCollideDistance > Context.Environment.CullFastDistanceMax;
 639135        _fastDistanceOffset = Fixed64.FromRaw((int)_fastCollideDistance) + (Fixed64.One * 2) * (Fixed64.One * 2);
 136
 639137        LastCollidedFrame = Context.FrameCount;
 639138        RefreshBroadPhaseVersions();
 639139        PairVersion++;
 639140        Active = true;
 639141    }
 142
 143    /// <summary>Orders the colliders for stable narrow-phase dispatch.</summary>
 144    public void AssignPriority(LSCollider c1, LSCollider c2)
 145    {
 639146        if (ShouldFirstColliderLead(c1, c2))
 147        {
 558148            ColliderA = c1;
 558149            ColliderB = c2;
 558150            return;
 151        }
 152
 81153        ColliderA = c2;
 81154        ColliderB = c1;
 81155    }
 156
 157    private static bool ShouldFirstColliderLead(LSCollider c1, LSCollider c2)
 158    {
 639159        if (c1.Priority != c2.Priority)
 268160            return c1.Priority > c2.Priority;
 161
 371162        if (c1.Body == null || c2.Body == null)
 26163            return true;
 164
 345165        if (c1.Body.LinearSpeed != c2.Body.LinearSpeed)
 84166            return c1.Body.LinearSpeed > c2.Body.LinearSpeed;
 167
 261168        return true;
 169    }
 170
 171    /// <summary>
 172    /// Checks and distributes collisions between colliders.
 173    /// Called by Partition Manager every fixed update if 2 colliders are on the same partion.
 174    /// </summary>
 22175    public void UpdateCollision() => UpdateCollision(CollisionResponseDispatchMode.Immediate);
 176
 4297177    internal void UpdateCollisionDeferred() => UpdateCollision(CollisionResponseDispatchMode.Deferred);
 178
 179    private void UpdateCollision(CollisionResponseDispatchMode responseMode)
 180    {
 4319181        if (!Active)
 1182            return;
 183
 4318184        UpdateLastFrame();
 4318185        DeactivateAndPoolIfRequired();
 186
 4318187        if (IsCullStateInvalidated())
 4223188            CullCounter = 0;
 189
 4318190        if (CullCounter <= 0)
 191        {
 4317192            ProcessCollision(responseMode);
 4317193            RefreshBroadPhaseVersions();
 4317194            if (_isCollidingChanged && !_isColliding)
 41195                Manifold.Reset();
 196
 4317197            HandleCullingIfNotColliding();
 4317198            return;
 199        }
 200
 1201        CullCounter--;  // Culled and one step closer to checking again.
 1202    }
 203
 4318204    private void UpdateLastFrame() => LastFrame = Context.FrameCount;
 205
 206    private void DeactivateAndPoolIfRequired()
 207    {
 4318208        if (_isPooledForDeactivation)
 4045209            return;
 210
 273211        Context.Physics.PoolForDeactivation(this);
 273212        _isPooledForDeactivation = true;
 273213    }
 214
 215    private void ProcessCollision(CollisionResponseDispatchMode responseMode)
 216    {
 4317217        if (!ShouldPerformCollisionCheck())
 218        {
 1813219            _isCollidingChanged = _isColliding;
 1813220            _isColliding = false;
 1813221            Manifold.Reset();
 1813222            _warmStart.Clear();
 1813223            return;
 224        }
 225
 2504226        bool result = CheckCollision();
 2504227        if (result)
 2488228            Context.Diagnostics.EmitContact(this, result);
 229
 2504230        if (result ^ _isColliding)
 231        {
 191232            _isColliding = result;
 191233            _isCollidingChanged = true;
 234        }
 235
 2504236        if (!result || !Manifold.HasContact)
 237        {
 16238            _warmStart.Clear();
 16239            return;
 240        }
 241
 2488242        if (!_doPhysics)
 1652243            return;
 244
 836245        if (responseMode == CollisionResponseDispatchMode.Deferred)
 246        {
 824247            Context.Physics.QueueDiscreteResponsePair(this);
 824248            return;
 249        }
 250
 12251        WakeSleepingBodiesForCollision();
 12252        CollisionResponse.CalculateImpulse(this);
 12253    }
 254
 255    internal void WakeSleepingBodiesForCollision()
 256    {
 79257        SolidBody? bodyA = ColliderA.Body;
 79258        SolidBody? bodyB = ColliderB.Body;
 79259        if (bodyA == null || bodyB == null)
 2260            return;
 261
 77262        bool bodyAAwake = bodyA.IsAwakeForCollision;
 77263        bool bodyBAwake = bodyB.IsAwakeForCollision;
 264
 77265        if (bodyA.IsSleeping && bodyBAwake)
 1266            bodyA.Wake();
 77267        if (bodyB.IsSleeping && bodyAAwake)
 2268            bodyB.Wake();
 77269    }
 270
 271    /// <summary>Raises contact or separation notifications for the current pair state.</summary>
 272    public void NotifyCollidersOfContact()
 273    {
 3855274        bool isColliding = _isColliding;
 3855275        bool isChanged = _isCollidingChanged;
 276
 3855277        var registrationA = new ColliderLifetimeToken(ColliderA);
 3855278        var registrationB = new ColliderLifetimeToken(ColliderB);
 3855279        LSCollider colliderA = registrationA.Collider;
 3855280        LSCollider colliderB = registrationB.Collider;
 3855281        SolidBody? bodyA = colliderA.Body;
 3855282        SolidBody? bodyB = colliderB.Body;
 3855283        bool isTriggerPair = colliderA.IsTrigger || colliderB.IsTrigger;
 3855284        bool shouldRaiseTriggerA = isTriggerPair && ColliderTriggerEventPolicy.ShouldRaise(colliderA, colliderB);
 3855285        bool shouldRaiseTriggerB = isTriggerPair && ColliderTriggerEventPolicy.ShouldRaise(colliderB, colliderA);
 3855286        _notificationInProgress = true;
 3855287        SwiftList<Exception>? notificationExceptions = null;
 288        try
 289        {
 3855290            if (isColliding)
 291            {
 2848292                bool notifyEnterA = isChanged && !_colliderANotified;
 2848293                _colliderANotified = true;
 2848294                colliderA.NotifyContact(
 2848295                    colliderB,
 2848296                    bodyB,
 2848297                    isColliding: true,
 2848298                    notifyEnterA,
 2848299                    allowInactive: false,
 2848300                    registrationA,
 2848301                    registrationB,
 2848302                    isTriggerPair,
 2848303                    shouldRaiseTriggerA);
 2845304                if (registrationA.IsActive && registrationB.IsActive && !_separationPending)
 305                {
 2837306                    bool notifyEnterB = isChanged && !_colliderBNotified;
 2837307                    _colliderBNotified = true;
 2837308                    colliderB.NotifyContact(
 2837309                        colliderA,
 2837310                        bodyA,
 2837311                        isColliding: true,
 2837312                        notifyEnterB,
 2837313                        allowInactive: false,
 2837314                        registrationB,
 2837315                        registrationA,
 2837316                        isTriggerPair,
 2837317                        shouldRaiseTriggerB);
 318                }
 319            }
 320            else
 321            {
 1007322                NotifySeparation(
 1007323                    registrationA,
 1007324                    registrationB,
 1007325                    bodyA,
 1007326                    bodyB,
 1007327                    isChanged,
 1007328                    isTriggerPair,
 1007329                    shouldRaiseTriggerA,
 1007330                    shouldRaiseTriggerB);
 331            }
 3849332        }
 6333        catch (Exception exception)
 334        {
 6335            CollisionNotificationExceptions.Capture(ref notificationExceptions, exception);
 6336        }
 337
 338        try
 339        {
 3855340            EndNotification(
 3855341                registrationA,
 3855342                registrationB,
 3855343                isTriggerPair,
 3855344                shouldRaiseTriggerA,
 3855345                shouldRaiseTriggerB);
 3854346        }
 1347        catch (Exception exception)
 348        {
 1349            CollisionNotificationExceptions.Capture(ref notificationExceptions, exception);
 1350        }
 351
 3855352        _isCollidingChanged &= _isColliding
 3855353            & !(_colliderANotified & _colliderBNotified);
 354
 3855355        CollisionNotificationExceptions.ThrowIfAny(notificationExceptions);
 3848356    }
 357
 358    private void EndNotification(
 359        in ColliderLifetimeToken registrationA,
 360        in ColliderLifetimeToken registrationB,
 361        bool isTriggerPair,
 362        bool shouldRaiseTriggerA,
 363        bool shouldRaiseTriggerB)
 364    {
 365        try
 366        {
 3855367            if (!_separationPending)
 3846368                return;
 369
 9370            SolidBody? bodyA = _pendingBodyA;
 9371            SolidBody? bodyB = _pendingBodyB;
 9372            ClearPendingNotificationState();
 9373            NotifySeparation(
 9374                registrationA,
 9375                registrationB,
 9376                bodyA,
 9377                bodyB,
 9378                isChanged: true,
 9379                isTriggerPair,
 9380                shouldRaiseTriggerA,
 9381                shouldRaiseTriggerB);
 8382        }
 383        finally
 384        {
 3855385            ClearPendingNotificationState();
 3855386            _notificationInProgress = false;
 3855387        }
 3854388    }
 389
 390    private void NotifySeparation(
 391        in ColliderLifetimeToken registrationA,
 392        in ColliderLifetimeToken registrationB,
 393        SolidBody? bodyA,
 394        SolidBody? bodyB,
 395        bool isChanged,
 396        bool isTriggerPair,
 397        bool shouldRaiseTriggerA,
 398        bool shouldRaiseTriggerB)
 399    {
 1016400        bool notifyA = _colliderANotified;
 1016401        bool notifyB = _colliderBNotified;
 1016402        _colliderANotified = false;
 1016403        _colliderBNotified = false;
 404
 1016405        SwiftList<Exception>? notificationExceptions = null;
 1016406        if (notifyA)
 407        {
 408            try
 409            {
 67410                registrationA.Collider.NotifyContact(
 67411                    registrationB.Collider,
 67412                    bodyB,
 67413                    isColliding: false,
 67414                    isChanged,
 67415                    allowInactive: true,
 67416                    registrationA,
 67417                    registrationB,
 67418                    isTriggerPair,
 67419                    shouldRaiseTriggerA);
 63420            }
 4421            catch (Exception exception)
 422            {
 4423                CollisionNotificationExceptions.Capture(ref notificationExceptions, exception);
 4424            }
 425        }
 426
 1016427        if (notifyB && registrationB.IsCurrentLifetime)
 428        {
 429            try
 430            {
 56431                registrationB.Collider.NotifyContact(
 56432                    registrationA.Collider,
 56433                    bodyA,
 56434                    isColliding: false,
 56435                    isChanged,
 56436                    allowInactive: true,
 56437                    registrationB,
 56438                    registrationA,
 56439                    isTriggerPair,
 56440                    shouldRaiseTriggerB);
 55441            }
 1442            catch (Exception exception)
 443            {
 1444                CollisionNotificationExceptions.Capture(ref notificationExceptions, exception);
 1445            }
 446        }
 447
 1016448        CollisionNotificationExceptions.ThrowIfAny(notificationExceptions);
 1012449    }
 450
 451    private void ClearPendingNotificationState()
 452    {
 4504453        _separationPending = false;
 4504454        _pendingBodyA = null;
 4504455        _pendingBodyB = null;
 4504456    }
 457
 458    private void HandleCullingIfNotColliding()
 459    {
 4317460        if (_isColliding)
 461        {
 2488462            LastCollidedFrame = Context.FrameCount;
 2488463            return;
 464        }
 465
 1829466        CalculateCullScore();
 1829467    }
 468
 469    internal bool TryPreserveSleepingRestingContact()
 470    {
 4728471        if (!_isColliding || !Manifold.HasContact)
 1884472            return false;
 473
 2844474        if (ColliderA.Body?.IsSleeping != true && ColliderB.Body?.IsSleeping != true)
 2454475            return false;
 476
 390477        LastCollidedFrame = Context.FrameCount;
 390478        return true;
 479    }
 480
 481    private bool CheckCollision()
 482    {
 2504483        if (!BroadPhaseVersionChanged() && _isColliding)
 19484            return _isColliding;
 485
 2485486        return CollisionDetection.DoCollisionCheck(this);
 487    }
 488
 489    private bool IsCullStateInvalidated()
 490    {
 4318491        return ColliderA.PartitionChanged
 4318492            || ColliderB.PartitionChanged
 4318493            || BroadPhaseVersionChanged();
 494    }
 495
 496    private bool BroadPhaseVersionChanged()
 497    {
 2601498        return ColliderA.BroadPhaseVersion != _lastColliderABroadPhaseVersion
 2601499            || ColliderB.BroadPhaseVersion != _lastColliderBBroadPhaseVersion;
 500    }
 501
 502    private void RefreshBroadPhaseVersions()
 503    {
 4956504        _lastColliderABroadPhaseVersion = ColliderA.BroadPhaseVersion;
 4956505        _lastColliderBBroadPhaseVersion = ColliderB.BroadPhaseVersion;
 4956506    }
 507
 508    private bool ShouldPerformCollisionCheck()
 509    {
 510        // Center distance remains a scheduling signal only. Canonical geometry
 511        // may extend beyond a scalar face while its conservative broad-phase
 512        // bounds are clipped to the representable domain, which can shorten
 513        // Bounds.Scope without shortening the physical shape.
 4317514        _fastDistance = Vector3d.DistanceSquared(ColliderA.Center, ColliderB.Center);
 515
 516        // Inclusive bounds overlap preserves zero-depth touching contacts for the manifold pass.
 4317517        return BoundsOverlapInclusive(ColliderA, ColliderB);
 518    }
 519
 520    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 521    private static bool BoundsOverlapInclusive(LSCollider colliderA, LSCollider colliderB)
 522    {
 4317523        return colliderA.BoundsMin.X <= colliderB.BoundsMax.X
 4317524            && colliderA.BoundsMax.X >= colliderB.BoundsMin.X
 4317525            && colliderA.BoundsMin.Y <= colliderB.BoundsMax.Y
 4317526            && colliderA.BoundsMax.Y >= colliderB.BoundsMin.Y
 4317527            && colliderA.BoundsMin.Z <= colliderB.BoundsMax.Z
 4317528            && colliderA.BoundsMax.Z >= colliderB.BoundsMin.Z;
 529    }
 530
 531    private void CalculateCullScore()
 532    {
 1829533        int distanceScore = 0;
 1829534        int velocityScore = 0;
 1829535        if (!_preventDistanceCull)
 536        {
 1683537            int distanceMax = Context.Environment.CullDistanceMax;
 1683538            if (distanceMax > 0)
 539            {
 1682540                int step = GetCullDistanceStep(World!);
 1682541                distanceScore = Math.Clamp((int)(_fastDistance - _fastDistanceOffset) / step + Context.Collisions.CullDi
 542            }
 543
 1683544            int cullVelocityStep = Context.Environment.CullVelocityStep;
 1683545            if (cullVelocityStep > 0)
 1682546                velocityScore = Math.Clamp((int)(ColliderA.Velocity - ColliderB.Velocity).Magnitude / cullVelocityStep, 
 547        }
 548
 1829549        int timeScore = 0;
 1829550        int cullTimeStep = Context.Environment.CullTimeStep;
 1829551        if (cullTimeStep > 0)
 1828552            timeScore = Math.Clamp((Context.FrameCount - LastCollidedFrame) / cullTimeStep, 0, Context.Environment.CullT
 553
 1829554        CullCounter = (short)Math.Clamp(distanceScore + timeScore - velocityScore, 0, short.MaxValue);
 1829555    }
 556
 557    /// <summary>
 558    /// Defines the step value for distance-based culling. The score is increased
 559    /// when the distance between objects increases. Higher values make the culling more aggressive for distant objects.
 560    /// </summary>
 561    private int GetCullDistanceStep(GridWorld world)
 562    {
 1682563        int distanceMax = Context.Environment.CullDistanceMax;
 1682564        Fixed64 cellEdge = GridTopologyMetricUtility.GetRepresentativeCellEdge(world);
 1682565        int step = ((cellEdge + Fixed64.One * 2) * (cellEdge + Fixed64.One * 2) / distanceMax).CeilToInt();
 1682566        return Math.Max(1, step);
 567    }
 568
 569    /// <summary>Clears transient contact and notification state before reuse.</summary>
 570    public void Reset()
 571    {
 640572        Manifold.Reset();
 640573        _warmStart.Clear();
 640574        _isColliding = false;
 640575        _isCollidingChanged = false;
 640576        _isPooledForDeactivation = false;
 640577        _notificationInProgress = false;
 640578        _colliderANotified = false;
 640579        _colliderBNotified = false;
 640580        ClearPendingNotificationState();
 640581    }
 582
 583    internal void StoreWarmStartImpulse(
 584        ulong contactId,
 585        Vector3d normal,
 586        Fixed64 normalImpulse,
 587        Fixed64 tangentImpulse,
 588        Fixed64 secondaryTangentImpulse = default) =>
 9118589        _warmStart.Set(contactId, normal, normalImpulse, tangentImpulse, secondaryTangentImpulse);
 590
 591    internal bool TryGetWarmStartImpulse(ulong contactId, out ContactWarmStartImpulse impulse) =>
 9132592        _warmStart.TryGet(contactId, out impulse);
 593
 594    internal void RemoveWarmStartImpulse(ulong contactId) =>
 11595        _warmStart.Remove(contactId);
 596
 2597    internal void ClearWarmStart() => _warmStart.Clear();
 598
 599    /// <summary>
 600    /// Deactivates the CollisionPair.
 601    /// </summary>
 602    public void Deactivate()
 603    {
 39604        bool notifySeparation = _isColliding || _colliderANotified || _colliderBNotified;
 39605        if (notifySeparation)
 606        {
 30607            _isColliding = false;
 30608            _isCollidingChanged = true;
 30609            if (_notificationInProgress)
 610            {
 9611                _separationPending = true;
 9612                _pendingBodyA = ColliderA.Body;
 9613                _pendingBodyB = ColliderB.Body;
 614            }
 615        }
 616
 39617        Manifold.Reset();
 39618        _warmStart.Clear();
 39619        _isPooledForDeactivation = false;
 39620        Active = false;
 621
 39622        if (notifySeparation && !_notificationInProgress)
 21623            NotifyCollidersOfContact();
 38624    }
 625}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Pairs/3D/CollisionPair.ReplayHash.cs

#LineLine coverage
 1//=======================================================================
 2// CollisionPair.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;
 10using Gravitas.Materials;
 11
 12namespace Gravitas.CollisionHandling;
 13
 14public partial class CollisionPair
 15{
 16    internal void ContributeReplayHash(
 17        ref ChronicleHashWriter writer,
 18        GravitasReplayHashMode mode)
 19    {
 4320        writer.WriteSection("pair.3d", 2);
 4321        writer.WriteBool(Active);
 4322        writer.WriteInt32(ColliderA.ReplayOrdinal);
 4323        writer.WriteInt32(ColliderB.ReplayOrdinal);
 4324        writer.WriteEnum(CollisionType);
 4325        writer.WriteUInt32(PartitionVersion);
 4326        writer.WriteInt32(PairVersion);
 4327        writer.WriteInt32(LastFrame);
 4328        writer.WriteInt32(LastCollidedFrame);
 4329        writer.WriteBool(_doPhysics);
 4330        writer.WriteInt32(CullCounter);
 4331        writer.WriteBool(_preventDistanceCull);
 4332        writer.WriteBool(_isColliding);
 4333        writer.WriteBool(_isCollidingChanged);
 4334        writer.WriteFixed64(_fastCollideDistance);
 4335        writer.WriteFixed64(_fastDistance);
 4336        writer.WriteFixed64(_fastDistanceOffset);
 4337        writer.WriteUInt32(_lastColliderABroadPhaseVersion);
 4338        writer.WriteUInt32(_lastColliderBBroadPhaseVersion);
 4339        ContributeManifoldReplayHash(ref writer, Manifold);
 4340        ContributeWarmStartReplayHash(ref writer, _warmStart);
 41
 4342        if (mode != GravitasReplayHashMode.AuthoritativeWithSolverCaches)
 3943            return;
 44
 445        writer.WriteSection("pair.3d.caches", 1);
 446        writer.WriteBool(_isPooledForDeactivation);
 447    }
 48
 49    private static void ContributeManifoldReplayHash(
 50        ref ChronicleHashWriter writer,
 51        ContactManifold manifold)
 52    {
 4353        writer.WriteSection("manifold.3d", 6);
 4354        writer.WriteInt32(manifold.LastUpdatedFrame);
 4355        writer.WriteInt32(manifold.Count);
 10056        for (int i = 0; i < manifold.Count; i++)
 57        {
 758            ManifoldContact contact = manifold[i];
 759            writer.WriteUInt64(contact.ContactId);
 760            writer.WriteInt32(contact.FeatureNamespaceA);
 761            writer.WriteVector3d(contact.AnchorA.Origin);
 762            writer.WriteQuaternion(contact.AnchorA.Rotation);
 763            writer.WriteVector3d(contact.AnchorA.LocalPoint);
 764            writer.WriteVector3d(contact.AnchorA.LocalDisplacement);
 765            writer.WriteUInt64(contact.AnchorA.GetLocalFeatureHash64());
 766            writer.WriteInt32(contact.FeatureNamespaceB);
 767            writer.WriteVector3d(contact.AnchorB.Origin);
 768            writer.WriteQuaternion(contact.AnchorB.Rotation);
 769            writer.WriteVector3d(contact.AnchorB.LocalPoint);
 770            writer.WriteVector3d(contact.AnchorB.LocalDisplacement);
 771            writer.WriteUInt64(contact.AnchorB.GetLocalFeatureHash64());
 772            writer.WriteFixed64(contact.Depth);
 773            writer.WriteBool(contact.DepthIsClamped);
 774            writer.WriteVector3d(contact.Normal);
 775            writer.WriteBool(contact.HasMaterialOverride);
 776            if (contact.HasMaterialOverride)
 77            {
 378                WriteMaterial(ref writer, contact.MaterialA);
 379                WriteMaterial(ref writer, contact.MaterialB);
 80            }
 81        }
 4382    }
 83
 84    private static void WriteMaterial(ref ChronicleHashWriter writer, PhysicsMaterial material)
 85    {
 686        writer.WriteFixed64(material.StaticFriction);
 687        writer.WriteFixed64(material.DynamicFriction);
 688        writer.WriteFixed64(material.Restitution);
 689        writer.WriteEnum(material.FrictionCombine);
 690        writer.WriteEnum(material.RestitutionCombine);
 691    }
 92
 93    private static void ContributeWarmStartReplayHash(
 94        ref ChronicleHashWriter writer,
 95        ContactWarmStartCache warmStart)
 96    {
 4397        writer.WriteSection("warm-start.3d", 1);
 4398        writer.WriteInt32(warmStart.Count);
 8899        for (int i = 0; i < warmStart.Count; i++)
 100        {
 1101            writer.WriteUInt64(warmStart.GetContactIdForReplayHash(i));
 1102            ContactWarmStartImpulse impulse = warmStart.GetImpulseForReplayHash(i);
 1103            writer.WriteVector3d(impulse.Normal);
 1104            writer.WriteFixed64(impulse.NormalImpulse);
 1105            writer.WriteFixed64(impulse.TangentImpulse);
 1106            writer.WriteFixed64(impulse.SecondaryTangentImpulse);
 107        }
 43108    }
 109}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Pairs/CollisionNotificationExceptions.cs

#LineLine coverage
 1//=======================================================================
 2// CollisionNotificationExceptions.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 SwiftCollections;
 9using System;
 10using System.Runtime.CompilerServices;
 11using System.Runtime.ExceptionServices;
 12
 13namespace Gravitas.CollisionHandling;
 14
 15internal static class CollisionNotificationExceptions
 16{
 17    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 18    internal static void Capture(ref SwiftList<Exception>? exceptions, Exception exception) =>
 2019        (exceptions ??= new SwiftList<Exception>(2)).Add(exception);
 20
 21    [MethodImpl(MethodImplOptions.NoInlining)]
 22    internal static void ThrowIfAny(SwiftList<Exception>? exceptions)
 23    {
 578424        if (exceptions == null)
 576725            return;
 26
 1727        if (exceptions.Count == 1)
 1428            ExceptionDispatchInfo.Capture(exceptions[0]).Throw();
 29
 330        throw new AggregateException(exceptions);
 31    }
 32}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Pairs/Mixed/CollisionPairMixed.cs

#LineLine coverage
 1//=======================================================================
 2// CollisionPairMixed.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 Gravitas.Colliders;
 9using SwiftCollections;
 10using System;
 11using System.Runtime.CompilerServices;
 12
 13namespace Gravitas.CollisionHandling;
 14
 15/// <summary>
 16/// Owns one stable mixed 3D/2D collision pair identity and contact lifecycle.
 17/// </summary>
 18internal sealed partial class CollisionPairMixed
 19{
 20    private bool _isColliding;
 21    private bool _isTriggerPair;
 22    private bool _notificationInProgress;
 23    private bool _separationPending;
 24    private bool _collider3DNotified;
 25    private bool _collider2DNotified;
 26    private long _lifetimeVersion;
 27
 21228    public CollisionPairMixed(LSCollider collider3D, LSCollider2D collider2D)
 29    {
 21230        Collider3D = collider3D;
 21231        Collider2D = collider2D;
 21232        Initialize(collider3D, collider2D);
 21233    }
 34
 35    public LSCollider Collider3D { get; private set; }
 36
 37    public LSCollider2D Collider2D { get; private set; }
 38
 39    public int Collider3DId { get; private set; }
 40
 41    public int Collider2DId { get; private set; }
 42
 43    public ulong Key { get; private set; }
 44
 89445    public GravitasWorldContext Context => Collider3D.Context;
 46
 47    public int LastFrame { get; private set; } = -1;
 48
 49    public MixedContact Contact { get; private set; }
 50
 42051    internal long LifetimeVersion => _lifetimeVersion;
 52
 3253    internal bool IsNotificationInProgress => _notificationInProgress;
 54
 55    public void Initialize(LSCollider collider3D, LSCollider2D collider2D)
 56    {
 21457        SwiftThrowHelper.ThrowIfNull(collider3D, nameof(collider3D));
 21458        SwiftThrowHelper.ThrowIfNull(collider2D, nameof(collider2D));
 59
 21460        _lifetimeVersion++;
 21461        Collider3D = collider3D;
 21462        Collider2D = collider2D;
 21463        Collider3DId = collider3D.Id;
 21464        Collider2DId = collider2D.Id;
 21465        Key = MixedColliderKey.CreateKey(Collider3DId, Collider2DId);
 21466        LastFrame = -1;
 21467        _isColliding = false;
 21468        _isTriggerPair = collider3D.IsTrigger || collider2D.IsTrigger;
 21469        _notificationInProgress = false;
 21470        _separationPending = false;
 21471        _collider3DNotified = false;
 21472        _collider2DNotified = false;
 21473        Contact = default;
 21474    }
 75
 76    public void MarkColliding(int frame, MixedContact contact)
 77    {
 18478        bool changed = !_isColliding;
 18479        _isColliding = true;
 18480        _isTriggerPair = Collider3D.IsTrigger || Collider2D.IsTrigger;
 18481        Contact = contact;
 18482        LastFrame = frame;
 83
 18484        Context.Diagnostics.EmitMixedContact(this, contact);
 85
 18486        var registration3D = new ColliderLifetimeToken(Collider3D);
 18487        var registration2D = new ColliderLifetimeToken2D(Collider2D);
 18488        bool shouldRaiseTrigger = ColliderTriggerEventPolicy.ShouldRaise(Collider3D, Collider2D);
 18489        _notificationInProgress = true;
 18490        SwiftList<Exception>? notificationExceptions = null;
 91        try
 92        {
 18493            bool notifyEnter3D = changed || !_collider3DNotified;
 18494            _collider3DNotified = true;
 18495            Collider3D.NotifyMixedContact(
 18496                Collider2D,
 18497                true,
 18498                notifyEnter3D,
 18499                _isTriggerPair,
 184100                allowInactive: false,
 184101                registration3D,
 184102                registration2D,
 184103                shouldRaiseTrigger);
 183104            if (_isColliding && registration3D.IsActive && registration2D.IsActive && !_separationPending)
 105            {
 179106                bool notifyEnter2D = changed || !_collider2DNotified;
 179107                _collider2DNotified = true;
 179108                Collider2D.NotifyMixedContact(
 179109                    Collider3D,
 179110                    true,
 179111                    notifyEnter2D,
 179112                    _isTriggerPair,
 179113                    allowInactive: false,
 179114                    registration2D,
 179115                    registration3D,
 179116                    shouldRaiseTrigger);
 117            }
 183118        }
 1119        catch (Exception exception)
 120        {
 1121            CollisionNotificationExceptions.Capture(ref notificationExceptions, exception);
 1122        }
 123
 124        try
 125        {
 184126            EndNotification(
 184127                registration3D,
 184128                registration2D,
 184129                shouldRaiseTrigger);
 183130        }
 1131        catch (Exception exception)
 132        {
 1133            CollisionNotificationExceptions.Capture(ref notificationExceptions, exception);
 1134        }
 135
 184136        CollisionNotificationExceptions.ThrowIfAny(notificationExceptions);
 182137    }
 138
 139    public void MarkResting(int frame)
 140    {
 6141        LastFrame = frame;
 6142    }
 143
 144    public void MarkResting(int frame, MixedContact contact)
 145    {
 2146        Contact = contact;
 2147        LastFrame = frame;
 2148    }
 149
 150    public void MarkSeparated()
 151    {
 152        // The 2D side is admitted only after the 3D side, so it cannot be the sole notified side.
 33153        if (!_isColliding && !_collider3DNotified)
 2154            return;
 155
 31156        _isColliding = false;
 31157        Contact = default;
 31158        if (_notificationInProgress)
 159        {
 9160            _separationPending = true;
 9161            return;
 162        }
 163
 22164        var registration3D = new ColliderLifetimeToken(Collider3D);
 22165        var registration2D = new ColliderLifetimeToken2D(Collider2D);
 22166        _notificationInProgress = true;
 167        try
 168        {
 22169            NotifySeparation(
 22170                registration3D,
 22171                registration2D,
 22172                ColliderTriggerEventPolicy.ShouldRaise(Collider3D, Collider2D));
 22173        }
 174        finally
 175        {
 22176            _separationPending = false;
 22177            _notificationInProgress = false;
 22178        }
 22179    }
 180
 181    private void EndNotification(
 182        in ColliderLifetimeToken registration3D,
 183        in ColliderLifetimeToken2D registration2D,
 184        bool shouldRaiseTrigger)
 185    {
 186        try
 187        {
 184188            if (!_separationPending)
 175189                return;
 190
 9191            _separationPending = false;
 9192            NotifySeparation(registration3D, registration2D, shouldRaiseTrigger);
 8193        }
 194        finally
 195        {
 184196            _separationPending = false;
 184197            _notificationInProgress = false;
 184198        }
 183199    }
 200
 201    private void NotifySeparation(
 202        in ColliderLifetimeToken registration3D,
 203        in ColliderLifetimeToken2D registration2D,
 204        bool shouldRaiseTrigger)
 205    {
 31206        bool notify2D = _collider2DNotified;
 31207        _collider3DNotified = false;
 31208        _collider2DNotified = false;
 31209        SwiftList<Exception>? notificationExceptions = null;
 210        try
 211        {
 31212            Collider3D.NotifyMixedContact(
 31213                Collider2D,
 31214                false,
 31215                true,
 31216                _isTriggerPair,
 31217                allowInactive: true,
 31218                registration3D,
 31219                registration2D,
 31220                shouldRaiseTrigger);
 30221        }
 1222        catch (Exception exception)
 223        {
 1224            CollisionNotificationExceptions.Capture(ref notificationExceptions, exception);
 1225        }
 226
 31227        if (notify2D && registration2D.IsCurrentLifetime)
 228        {
 229            try
 230            {
 25231                Collider2D.NotifyMixedContact(
 25232                    Collider3D,
 25233                    false,
 25234                    true,
 25235                    _isTriggerPair,
 25236                    allowInactive: true,
 25237                    registration2D,
 25238                    registration3D,
 25239                    shouldRaiseTrigger);
 24240            }
 1241            catch (Exception exception)
 242            {
 1243                CollisionNotificationExceptions.Capture(ref notificationExceptions, exception);
 1244            }
 245        }
 246
 31247        CollisionNotificationExceptions.ThrowIfAny(notificationExceptions);
 30248    }
 249
 250    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 251    internal void WakeSleepingBodiesForCollision()
 252    {
 41253        SolidBody? body3D = Collider3D.Body;
 41254        SolidBody2D? body2D = Collider2D.Body;
 41255        if (body3D == null || body2D == null)
 3256            return;
 257
 38258        bool body3DAwake = body3D.IsAwakeForCollision;
 38259        bool body2DAwake = body2D.IsAwakeForCollision;
 38260        if (body3D.IsSleeping && body2DAwake)
 1261            body3D.Wake();
 38262        if (body2D.IsSleeping && body3DAwake)
 1263            body2D.Wake();
 38264    }
 265}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Pairs/Mixed/CollisionPairMixed.ReplayHash.cs

#LineLine coverage
 1//=======================================================================
 2// CollisionPairMixed.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;
 10using Gravitas.Materials;
 11
 12namespace Gravitas.CollisionHandling;
 13
 14internal sealed partial class CollisionPairMixed
 15{
 16    internal void ContributeReplayHash(
 17        ref ChronicleHashWriter writer,
 18        GravitasReplayHashMode mode)
 19    {
 720        writer.WriteSection("pair.mixed", 5);
 721        writer.WriteInt32(Collider3D.ReplayOrdinal);
 722        writer.WriteInt32(Collider2D.ReplayOrdinal);
 723        writer.WriteUInt64(MixedColliderKey.CreateKey(Collider3D.ReplayOrdinal, Collider2D.ReplayOrdinal));
 724        writer.WriteInt32(LastFrame);
 725        writer.WriteBool(_isColliding);
 726        writer.WriteBool(_isTriggerPair);
 727        writer.WriteBool(Contact.HasContact);
 728        if (!Contact.HasContact)
 129            return;
 30
 631        writer.WriteVector3d(Contact.Anchor3D.Origin);
 632        writer.WriteQuaternion(Contact.Anchor3D.Rotation);
 633        writer.WriteVector3d(Contact.Anchor3D.LocalPoint);
 634        writer.WriteVector3d(Contact.Anchor3D.LocalDisplacement);
 635        writer.WriteUInt64(Contact.Anchor3D.GetLocalFeatureHash64());
 636        writer.WriteVector3d(Contact.Anchor2D.Origin);
 637        writer.WriteQuaternion(Contact.Anchor2D.Rotation);
 638        writer.WriteVector3d(Contact.Anchor2D.LocalPoint);
 639        writer.WriteVector3d(Contact.Anchor2D.LocalDisplacement);
 640        writer.WriteUInt64(Contact.Anchor2D.GetLocalFeatureHash64());
 641        writer.WriteVector3d(Contact.Normal3DTo2D);
 642        writer.WriteFixed64(Contact.Depth);
 643        writer.WriteBool(Contact.DepthIsClamped);
 644        writer.WriteBool(Contact.HasMaterialOverride);
 645        if (!Contact.HasMaterialOverride)
 546            return;
 47
 148        WriteMaterial(ref writer, Contact.Material3D);
 149        WriteMaterial(ref writer, Contact.Material2D);
 150    }
 51
 52    private static void WriteMaterial(ref ChronicleHashWriter writer, PhysicsMaterial material)
 53    {
 254        writer.WriteFixed64(material.StaticFriction);
 255        writer.WriteFixed64(material.DynamicFriction);
 256        writer.WriteFixed64(material.Restitution);
 257        writer.WriteEnum(material.FrictionCombine);
 258        writer.WriteEnum(material.RestitutionCombine);
 259    }
 60}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/2D/CollisionResponse2D.cs

#LineLine coverage
 1//=======================================================================
 2// CollisionResponse2D.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.Materials;
 11using System.Runtime.CompilerServices;
 12
 13namespace Gravitas.CollisionHandling;
 14
 15/// <summary>
 16/// Deterministic pure 2D manifold contact response.
 17/// </summary>
 18public static class CollisionResponse2D
 19{
 20    /// <summary>Penetration tolerated before positional correction is applied.</summary>
 121    public static readonly Fixed64 PenetrationSlop = (Fixed64)0.01f;
 22
 23    /// <summary>Fraction of excess penetration corrected by the solver.</summary>
 124    public static readonly Fixed64 PenetrationCorrectionPercent = Fixed64.One;
 25
 26    internal static void Resolve(CollisionPair2D pair) =>
 45427        Resolve(pair, applyCachedImpulse: true, applyPositionCorrection: true);
 28
 29    internal static void Resolve(
 30        CollisionPair2D pair,
 31        bool applyCachedImpulse,
 32        bool applyPositionCorrection)
 33    {
 99534        if (!TryCreateBodyPair(pair, out ResponseBody2D bodyA, out ResponseBody2D bodyB))
 535            return;
 36
 99037        ContactAnchor2D responseCenterA = bodyA.Body?.GetCenterOfMassAnchor() ?? default;
 99038        ContactAnchor2D responseCenterB = bodyB.Body?.GetCenterOfMassAnchor() ?? default;
 99039        SolverContactBuffer2D contacts = BuildContactBuffer(
 99040            pair,
 99041            bodyA,
 99042            bodyB,
 99043            responseCenterA,
 99044            responseCenterB);
 99045        if (contacts.Count == 0)
 146            return;
 47
 98948        Vector2d responsePositionA = bodyA.Body?.Position ?? Vector2d.Zero;
 98949        Vector2d responsePositionB = bodyB.Body?.Position ?? Vector2d.Zero;
 98950        byte failedResponseMask = 0;
 98951        Fixed64 contactShare = Fixed64.One / (Fixed64)contacts.Count;
 98952        if (applyPositionCorrection)
 53        {
 214054            for (int i = 0; i < contacts.Count; i++)
 53955                ApplyPositionCorrection(contacts.GetContact(i), contactShare);
 56        }
 57
 98958        if (applyCachedImpulse)
 59        {
 53660            bool rebuildContacts = false;
 216261            for (int i = 0; i < contacts.Count; i++)
 62            {
 54563                SolverContact2D contact = contacts.GetContact(i);
 54564                if (TryApplyCachedImpulse(
 54565                        pair,
 54566                        contact,
 54567                        responsePositionA,
 54568                        responsePositionB))
 69                {
 70                    continue;
 71                }
 72
 273                pair.RemoveWarmStartImpulse(contact.ContactId);
 274                rebuildContacts = true;
 75            }
 76
 53677            if (rebuildContacts)
 78            {
 279                contacts = BuildContactBuffer(
 280                    pair,
 281                    bodyA,
 282                    bodyB,
 283                    responseCenterA,
 284                    responseCenterB);
 85            }
 86        }
 87
 98988        Vector2d normalLinearVelocityA =
 98989            ResolveLinearVelocity(bodyA.Body);
 98990        Fixed64 normalAngularVelocityA =
 98991            ResolveAngularVelocity(bodyA.Body);
 98992        Vector2d normalLinearVelocityB =
 98993            ResolveLinearVelocity(bodyB.Body);
 98994        Fixed64 normalAngularVelocityB =
 98995            ResolveAngularVelocity(bodyB.Body);
 98996        Fixed64 restitutionVelocityThreshold = pair.ColliderA.Context.Settings.RestitutionVelocityThreshold;
 399497        for (int i = 0; i < contacts.Count; i++)
 98        {
 100899            SolverContact2D contact = contacts.GetContact(i);
 1008100            SolidBody2D? contactBodyA = contact.A.Body;
 1008101            SolidBody2D? contactBodyB = contact.B.Body;
 102            bool normalResolved;
 1008103            ContactNormalImpulseResult2D normalResult = default;
 1008104            if (contact.RelativeA.IsExact || contact.RelativeB.IsExact)
 105            {
 19106                normalResolved = TryCalculateExactNormalResult(
 19107                    pair,
 19108                    contact,
 19109                    responsePositionA,
 19110                    responsePositionB,
 19111                    normalLinearVelocityA,
 19112                    normalAngularVelocityA,
 19113                    normalLinearVelocityB,
 19114                    normalAngularVelocityB,
 19115                    restitutionVelocityThreshold,
 19116                    contactShare,
 19117                    out normalResult);
 118            }
 119            else
 120            {
 989121                normalResolved = CanUseCompactAxisResponse(
 989122                        contact,
 989123                        contact.Normal)
 989124                    && ContactNormalImpulse2D.TryCalculateAccumulatedDelta(
 989125                        contactBodyA,
 989126                        normalLinearVelocityA,
 989127                        normalAngularVelocityA,
 989128                        contact.RelativeA.Vector,
 989129                        contactBodyB,
 989130                        normalLinearVelocityB,
 989131                        normalAngularVelocityB,
 989132                        contact.RelativeB.Vector,
 989133                        contact.Normal,
 989134                        contact.Restitution,
 989135                        restitutionVelocityThreshold,
 989136                        contact.CachedNormalImpulse,
 989137                        contactShare,
 989138                        contactShare,
 989139                        out normalResult);
 989140                if (!normalResolved)
 141                {
 17142                    normalResolved = TryCalculateExactNormalResult(
 17143                        pair,
 17144                        contact,
 17145                        responsePositionA,
 17146                        responsePositionB,
 17147                        normalLinearVelocityA,
 17148                        normalAngularVelocityA,
 17149                        normalLinearVelocityB,
 17150                        normalAngularVelocityB,
 17151                        restitutionVelocityThreshold,
 17152                        contactShare,
 17153                        out normalResult);
 154                }
 155            }
 1008156            if (!normalResolved)
 157            {
 2158                RejectResponse(pair, contact, i, ref failedResponseMask);
 2159                continue;
 160            }
 1006161            Fixed64 normalImpulse = contact.CachedNormalImpulse + normalResult.ImpulseScalar;
 1006162            contacts.SetNormalImpulse(
 1006163                i,
 1006164                normalImpulse,
 1006165                normalResult);
 166        }
 167
 3994168        for (int i = 0; i < contacts.Count; i++)
 169        {
 1008170            if (HasFailedResponse(failedResponseMask, i))
 171                continue;
 172
 1006173            if (!TryApplyNormalImpulse(
 1006174                    contacts.GetContact(i),
 1006175                    contacts.GetNormalResult(i)))
 176            {
 1177                RejectResponse(
 1178                    pair,
 1179                    contacts.GetContact(i),
 1180                    i,
 1181                    ref failedResponseMask);
 182            }
 183        }
 184
 3994185        for (int i = 0; i < contacts.Count; i++)
 186        {
 1008187            if (HasFailedResponse(failedResponseMask, i))
 188                continue;
 189
 1005190            SolverContact2D contact = contacts.GetContact(i);
 1005191            if (!TrySolveFrictionImpulse(
 1005192                    pair,
 1005193                    contact,
 1005194                    responsePositionA,
 1005195                    responsePositionB,
 1005196                    normalLinearVelocityA,
 1005197                    normalAngularVelocityA,
 1005198                    normalLinearVelocityB,
 1005199                    normalAngularVelocityB,
 1005200                    restitutionVelocityThreshold,
 1005201                    contactShare,
 1005202                    contacts.GetNormalResult(i),
 1005203                    contacts.GetNormalImpulse(i),
 1005204                    out Fixed64 tangentImpulse))
 205            {
 2206                RejectResponse(pair, contact, i, ref failedResponseMask);
 2207                continue;
 208            }
 1003209            contacts.SetTangentImpulse(
 1003210                i,
 1003211                tangentImpulse);
 212        }
 213
 3994214        for (int i = 0; i < contacts.Count; i++)
 215        {
 1008216            if (HasFailedResponse(failedResponseMask, i))
 217                continue;
 218
 1003219            SolverContact2D contact = contacts.GetContact(i);
 1003220            pair.StoreWarmStartImpulse(
 1003221                contact.ContactId,
 1003222                contacts.GetNormalImpulse(i),
 1003223                contacts.GetTangentImpulse(i));
 224        }
 989225    }
 226
 227    private static bool TryCreateBodyPair(
 228        CollisionPair2D pair,
 229        out ResponseBody2D bodyA,
 230        out ResponseBody2D bodyB)
 231    {
 995232        bodyA = default;
 995233        bodyB = default;
 234
 995235        if (pair.ColliderA.IsTrigger || pair.ColliderB.IsTrigger || !pair.Manifold.HasContact)
 3236            return false;
 237
 992238        bodyA = ResponseBody2D.Create(pair.ColliderA);
 992239        bodyB = ResponseBody2D.Create(pair.ColliderB);
 992240        return bodyA.HasSolverMobility || bodyB.HasSolverMobility;
 241    }
 242
 243    private static SolverContactBuffer2D BuildContactBuffer(
 244        CollisionPair2D pair,
 245        ResponseBody2D bodyA,
 246        ResponseBody2D bodyB,
 247        in ContactAnchor2D responseCenterA,
 248        in ContactAnchor2D responseCenterB)
 249    {
 992250        SolverContactBuffer2D contacts = default;
 4008251        for (int i = 0; i < pair.Manifold.Count; i++)
 252        {
 1012253            if (TryCreateContact(
 1012254                    pair,
 1012255                    bodyA,
 1012256                    bodyB,
 1012257                    responseCenterA,
 1012258                    responseCenterB,
 1012259                    i,
 1012260                    out SolverContact2D contact))
 1011261                contacts.Add(contact);
 262        }
 263
 992264        return contacts;
 265    }
 266
 267    private static bool TryCreateContact(
 268        CollisionPair2D pair,
 269        ResponseBody2D bodyA,
 270        ResponseBody2D bodyB,
 271        in ContactAnchor2D responseCenterA,
 272        in ContactAnchor2D responseCenterB,
 273        int contactIndex,
 274        out SolverContact2D contact)
 275    {
 1012276        contact = default;
 1012277        ManifoldContact2D manifoldContact = pair.Manifold[contactIndex];
 1012278        Vector2d normal = ResolveContactNormal(
 1012279            manifoldContact.Normal,
 1012280            pair.ColliderB.Center - pair.ColliderA.Center);
 1012281        if (normal == Vector2d.Zero)
 1282            return false;
 1011283        ContactLever2D relativeA = bodyA.Body == null
 1011284            ? ContactLever2D.Zero
 1011285            : ContactLever2D.Create(
 1011286                manifoldContact.AnchorA,
 1011287                responseCenterA);
 1011288        ContactLever2D relativeB = bodyB.Body == null
 1011289            ? ContactLever2D.Zero
 1011290            : ContactLever2D.Create(
 1011291                manifoldContact.AnchorB,
 1011292                responseCenterB);
 1011293        PhysicsMaterial materialA = manifoldContact.HasMaterialOverride
 1011294            ? manifoldContact.MaterialA
 1011295            : pair.ColliderA.Material;
 1011296        PhysicsMaterial materialB = manifoldContact.HasMaterialOverride
 1011297            ? manifoldContact.MaterialB
 1011298            : pair.ColliderB.Material;
 299
 1011300        Fixed64 cachedNormalImpulse = Fixed64.Zero;
 1011301        Fixed64 cachedTangentImpulse = Fixed64.Zero;
 1011302        if (pair.TryGetWarmStartImpulse(manifoldContact.ContactId, out ContactWarmStartImpulse cached))
 303        {
 717304            cachedNormalImpulse = cached.NormalImpulse;
 717305            cachedTangentImpulse = cached.TangentImpulse;
 306        }
 307
 1011308        contact = new SolverContact2D(
 1011309            contactIndex,
 1011310            manifoldContact.ContactId,
 1011311            bodyA,
 1011312            bodyB,
 1011313            relativeA,
 1011314            relativeB,
 1011315            manifoldContact.Depth,
 1011316            normal,
 1011317            materialA,
 1011318            materialB,
 1011319            cachedNormalImpulse,
 1011320            cachedTangentImpulse);
 1011321        return true;
 322    }
 323
 324    [MethodImpl(MethodImplOptions.NoInlining)]
 325    private static bool TryCalculateExactNormalResult(
 326        CollisionPair2D pair,
 327        SolverContact2D contact,
 328        Vector2d responsePositionA,
 329        Vector2d responsePositionB,
 330        Vector2d linearVelocityA,
 331        Fixed64 angularVelocityA,
 332        Vector2d linearVelocityB,
 333        Fixed64 angularVelocityB,
 334        Fixed64 restitutionVelocityThreshold,
 335        Fixed64 contactShare,
 336        out ContactNormalImpulseResult2D result)
 337    {
 36338        GetExactLevers(
 36339            pair,
 36340            contact,
 36341            responsePositionA,
 36342            responsePositionB,
 36343            out ExactLever3D exactA,
 36344            out ExactLever3D exactB);
 36345        return ContactNormalImpulse2D.TryCalculateAccumulatedDeltaExact(
 36346            contact.A.Body,
 36347            linearVelocityA,
 36348            angularVelocityA,
 36349            exactA,
 36350            contact.B.Body,
 36351            linearVelocityB,
 36352            angularVelocityB,
 36353            exactB,
 36354            contact.Normal,
 36355            contact.Restitution,
 36356            restitutionVelocityThreshold,
 36357            contact.CachedNormalImpulse,
 36358            contactShare,
 36359            contactShare,
 36360            out result);
 361    }
 362
 363    private static void ApplyPositionCorrection(SolverContact2D contact, Fixed64 contactShare)
 364    {
 539365        Fixed64 correctionDepth = contact.Depth - PenetrationSlop;
 539366        if (correctionDepth <= Fixed64.Zero)
 101367            return;
 368
 438369        Fixed64 inverseMassA = contact.A.GetConstrainedInverseMass(contact.Normal);
 438370        Fixed64 inverseMassB = contact.B.GetConstrainedInverseMass(contact.Normal);
 438371        Fixed64 totalInverseMass = inverseMassA + inverseMassB;
 438372        if (totalInverseMass <= Fixed64.Zero)
 1373            return;
 374
 437375        Vector2d correction = contact.Normal
 437376            * (correctionDepth * PenetrationCorrectionPercent * contactShare / totalInverseMass);
 437377        ApplyPositionCorrection(contact.A, -correction * inverseMassA);
 437378        ApplyPositionCorrection(contact.B, correction * inverseMassB);
 437379    }
 380
 381    private static bool TryApplyCachedImpulse(
 382        CollisionPair2D pair,
 383        SolverContact2D contact,
 384        Vector2d responsePositionA,
 385        Vector2d responsePositionB)
 386    {
 545387        if (contact.CachedNormalImpulse == Fixed64.Zero && contact.CachedTangentImpulse == Fixed64.Zero)
 537388            return true;
 389
 8390        return TryApplyContactImpulseCombination(
 8391            pair,
 8392            contact,
 8393            responsePositionA,
 8394            responsePositionB,
 8395            contact.Normal,
 8396            contact.CachedNormalImpulse,
 8397            contact.Tangent,
 8398            contact.CachedTangentImpulse);
 399    }
 400
 401    private static bool TryApplyNormalImpulse(
 402        SolverContact2D contact,
 403        ContactNormalImpulseResult2D result)
 404    {
 1006405        if (result.LinearVelocityDeltaA == Vector2d.Zero
 1006406            && result.AngularVelocityDeltaA == Fixed64.Zero
 1006407            && result.LinearVelocityDeltaB == Vector2d.Zero
 1006408            && result.AngularVelocityDeltaB == Fixed64.Zero)
 409        {
 926410            return true;
 411        }
 412
 80413        return TryApplyVelocityDeltas(
 80414            contact,
 80415            result.LinearVelocityDeltaA,
 80416            result.AngularVelocityDeltaA,
 80417            result.LinearVelocityDeltaB,
 80418            result.AngularVelocityDeltaB);
 419    }
 420
 421    private static void ApplyVelocityDelta(
 422        ResponseBody2D body,
 423        Vector2d linearVelocityDelta,
 424        Fixed64 angularVelocityDelta)
 425    {
 236426        if (!body.HasSolverMobility)
 64427            return;
 428
 172429        if (body.CanTranslate)
 168430            body.Body!.ApplyCollisionLinearVelocityDelta(linearVelocityDelta);
 172431        if (body.CanRotate)
 136432            body.Body!.ApplyCollisionAngularVelocityDelta(angularVelocityDelta);
 172433    }
 434
 435    private static bool TrySolveFrictionImpulse(
 436        CollisionPair2D pair,
 437        SolverContact2D contact,
 438        Vector2d responsePositionA,
 439        Vector2d responsePositionB,
 440        Vector2d normalLinearVelocityA,
 441        Fixed64 normalAngularVelocityA,
 442        Vector2d normalLinearVelocityB,
 443        Fixed64 normalAngularVelocityB,
 444        Fixed64 restitutionVelocityThreshold,
 445        Fixed64 contactShare,
 446        ContactNormalImpulseResult2D normalResult,
 447        Fixed64 normalImpulseScalar,
 448        out Fixed64 accumulated)
 449    {
 1005450        accumulated = default;
 1005451        if (contact.RelativeA.IsExact
 1005452            || contact.RelativeB.IsExact
 1005453            || !normalResult.HasRepresentableAccumulatedImpulse
 1005454            || !CanUseCompactAxisResponse(contact, contact.Tangent)
 1005455            || !Fixed64.TryMultiplyDivide(
 1005456                normalImpulseScalar,
 1005457                contact.StaticFriction,
 1005458                Fixed64.One,
 1005459                out Fixed64 staticFrictionLimit)
 1005460            || !Fixed64.TryMultiplyDivide(
 1005461                normalImpulseScalar,
 1005462                contact.DynamicFriction,
 1005463                Fixed64.One,
 1005464                out Fixed64 dynamicFrictionLimit))
 465        {
 24466            return TrySolveFrictionImpulseExact(
 24467                pair,
 24468                contact,
 24469                responsePositionA,
 24470                responsePositionB,
 24471                normalLinearVelocityA,
 24472                normalAngularVelocityA,
 24473                normalLinearVelocityB,
 24474                normalAngularVelocityB,
 24475                restitutionVelocityThreshold,
 24476                contactShare,
 24477                out accumulated);
 478        }
 479
 981480        Fixed64 impulseScalar = Fixed64.Zero;
 981481        if (staticFrictionLimit > Fixed64.Zero || dynamicFrictionLimit > Fixed64.Zero)
 482        {
 55483            Vector3d spatialTangent =
 55484                ExactContactLever2D.ToSpatial(contact.Tangent);
 55485            Fixed64 tangentVelocity = default;
 55486            bool tangentVelocityResolved =
 55487                ContactResponseArithmetic3D.TryGetRelativePointVelocity(
 55488                    ExactContactLever2D.ToSpatial(
 55489                        ResolveLinearVelocity(contact.A.Body)),
 55490                    new Vector3d(
 55491                        Fixed64.Zero,
 55492                        -ResolveAngularVelocity(contact.A.Body),
 55493                        Fixed64.Zero),
 55494                    ExactContactLever2D.ToSpatial(contact.RelativeA.Vector),
 55495                    ExactContactLever2D.ToSpatial(
 55496                        ResolveLinearVelocity(contact.B.Body)),
 55497                    new Vector3d(
 55498                        Fixed64.Zero,
 55499                        -ResolveAngularVelocity(contact.B.Body),
 55500                        Fixed64.Zero),
 55501                    ExactContactLever2D.ToSpatial(contact.RelativeB.Vector),
 55502                    spatialTangent,
 55503                    out Vector3d relativeVelocity)
 55504                && ContactResponseArithmetic3D.TryDot(
 55505                    relativeVelocity,
 55506                    spatialTangent,
 55507                    out tangentVelocity);
 55508            bool denominatorResolved =
 55509                ContactNormalImpulse2D.TryComputeDenominator(
 55510                    contact.A.Body,
 55511                    contact.RelativeA.Vector,
 55512                    contact.B.Body,
 55513                    contact.RelativeB.Vector,
 55514                    contact.Tangent,
 55515                    out Fixed64 denominator);
 55516            if (!(tangentVelocityResolved & denominatorResolved))
 517            {
 3518                return TrySolveFrictionImpulseExact(
 3519                    pair,
 3520                    contact,
 3521                    responsePositionA,
 3522                    responsePositionB,
 3523                    normalLinearVelocityA,
 3524                    normalAngularVelocityA,
 3525                    normalLinearVelocityB,
 3526                    normalAngularVelocityB,
 3527                    restitutionVelocityThreshold,
 3528                    contactShare,
 3529                    out accumulated);
 530            }
 52531            if (tangentVelocity.Abs() > Fixed64.Epsilon
 52532                && denominator > Fixed64.Zero
 52533                && !Fixed64.TryMultiplyDivide(
 52534                    -tangentVelocity,
 52535                    Fixed64.One,
 52536                    denominator,
 52537                    out impulseScalar))
 538            {
 1539                return TrySolveFrictionImpulseExact(
 1540                    pair,
 1541                    contact,
 1542                    responsePositionA,
 1543                    responsePositionB,
 1544                    normalLinearVelocityA,
 1545                    normalAngularVelocityA,
 1546                    normalLinearVelocityB,
 1547                    normalAngularVelocityB,
 1548                    restitutionVelocityThreshold,
 1549                    contactShare,
 1550                    out accumulated);
 551            }
 552        }
 553
 977554        if (!Fixed64.TryAdd(
 977555                contact.CachedTangentImpulse,
 977556                impulseScalar,
 977557                out Fixed64 desiredAccumulated))
 558        {
 2559            return TrySolveFrictionImpulseExact(
 2560                pair,
 2561                contact,
 2562                responsePositionA,
 2563                responsePositionB,
 2564                normalLinearVelocityA,
 2565                normalAngularVelocityA,
 2566                normalLinearVelocityB,
 2567                normalAngularVelocityB,
 2568                restitutionVelocityThreshold,
 2569                contactShare,
 2570                out accumulated);
 571        }
 975572        accumulated = desiredAccumulated >= -staticFrictionLimit
 975573            && desiredAccumulated <= staticFrictionLimit
 975574            ? desiredAccumulated
 975575            : FixedMath.Clamp(
 975576                desiredAccumulated,
 975577                -dynamicFrictionLimit,
 975578                dynamicFrictionLimit);
 975579        if (!Fixed64.TrySubtract(
 975580                accumulated,
 975581                contact.CachedTangentImpulse,
 975582                out impulseScalar))
 583        {
 1584            return TrySolveFrictionImpulseExact(
 1585                pair,
 1586                contact,
 1587                responsePositionA,
 1588                responsePositionB,
 1589                normalLinearVelocityA,
 1590                normalAngularVelocityA,
 1591                normalLinearVelocityB,
 1592                normalAngularVelocityB,
 1593                restitutionVelocityThreshold,
 1594                contactShare,
 1595                out accumulated);
 596        }
 974597        return impulseScalar == Fixed64.Zero
 974598            || TryApplyContactImpulseCombination(
 974599                pair,
 974600                contact,
 974601                responsePositionA,
 974602                responsePositionB,
 974603                contact.Normal,
 974604                Fixed64.Zero,
 974605                contact.Tangent,
 974606                impulseScalar);
 607    }
 608
 609    [MethodImpl(MethodImplOptions.NoInlining)]
 610    private static bool TrySolveFrictionImpulseExact(
 611        CollisionPair2D pair,
 612        SolverContact2D contact,
 613        Vector2d responsePositionA,
 614        Vector2d responsePositionB,
 615        Vector2d normalLinearVelocityA,
 616        Fixed64 normalAngularVelocityA,
 617        Vector2d normalLinearVelocityB,
 618        Fixed64 normalAngularVelocityB,
 619        Fixed64 restitutionVelocityThreshold,
 620        Fixed64 contactShare,
 621        out Fixed64 accumulated)
 622    {
 31623        accumulated = default;
 31624        GetExactLevers(
 31625            pair,
 31626            contact,
 31627            responsePositionA,
 31628            responsePositionB,
 31629            out ExactLever3D exactA,
 31630            out ExactLever3D exactB);
 31631        Vector3d spatialNormal =
 31632            ExactContactLever2D.ToSpatial(contact.Normal);
 31633        var normalConstraint = new ExactNormalConstraint3D(
 31634            ExactContactLever2D.CreateResponseOperand(
 31635                contact.A.Body,
 31636                normalLinearVelocityA,
 31637                normalAngularVelocityA,
 31638                exactA,
 31639                -spatialNormal),
 31640            ExactContactLever2D.CreateResponseOperand(
 31641                contact.B.Body,
 31642                normalLinearVelocityB,
 31643                normalAngularVelocityB,
 31644                exactB,
 31645                spatialNormal),
 31646            spatialNormal,
 31647            contact.Restitution,
 31648            restitutionVelocityThreshold,
 31649            contact.CachedNormalImpulse,
 31650            contactShare,
 31651            contactShare);
 31652        Vector3d spatialTangent =
 31653            ExactContactLever2D.ToSpatial(contact.Tangent);
 31654        if (!ExactContactResponseKernel.TryGetCoulombLineResponse(
 31655                normalConstraint,
 31656                ExactContactLever2D.CreateResponseOperand(
 31657                    contact.A.Body,
 31658                    ResolveLinearVelocity(contact.A.Body),
 31659                    ResolveAngularVelocity(contact.A.Body),
 31660                    exactA,
 31661                    -spatialTangent),
 31662                ExactContactLever2D.CreateResponseOperand(
 31663                    contact.B.Body,
 31664                    ResolveLinearVelocity(contact.B.Body),
 31665                    ResolveAngularVelocity(contact.B.Body),
 31666                    exactB,
 31667                    spatialTangent),
 31668                spatialTangent,
 31669                contact.CachedTangentImpulse,
 31670                contact.StaticFriction,
 31671                contact.DynamicFriction,
 31672                out ExactCoulombResponse3D response))
 673        {
 2674            return false;
 675        }
 676
 29677        accumulated = response.TryGetPrimaryAccumulatedImpulse(
 29678                out Fixed64 projectedAccumulated)
 29679            ? projectedAccumulated
 29680            : Fixed64.Zero;
 29681        return !response.HasAppliedImpulse
 29682            || TryApplyVelocityDeltas(
 29683                contact,
 29684                ExactContactLever2D.ToPlanar(
 29685                    response.FirstLinearVelocityDelta),
 29686                ExactContactLever2D.ToPlanarAngular(
 29687                    response.FirstAngularVelocityDelta),
 29688                ExactContactLever2D.ToPlanar(
 29689                    response.SecondLinearVelocityDelta),
 29690                ExactContactLever2D.ToPlanarAngular(
 29691                    response.SecondAngularVelocityDelta));
 692    }
 693
 694    private static bool CanUseCompactAxisResponse(
 695        SolverContact2D contact,
 696        Vector2d axis) =>
 1973697        ExactContactLever2D.CanUseCompactResponse(
 1973698            contact.A.Body,
 1973699            ResolveLinearVelocity(contact.A.Body),
 1973700            ResolveAngularVelocity(contact.A.Body),
 1973701            contact.RelativeA.Vector,
 1973702            contact.B.Body,
 1973703            ResolveLinearVelocity(contact.B.Body),
 1973704            ResolveAngularVelocity(contact.B.Body),
 1973705            contact.RelativeB.Vector,
 1973706            axis);
 707
 708    private static void ApplyPositionCorrection(ResponseBody2D body, Vector2d correction)
 709    {
 874710        if (!body.CanTranslate || correction == Vector2d.Zero)
 421711            return;
 712
 453713        body.Body!.ApplyCollisionPositionCorrection(correction);
 453714    }
 715
 716    private static bool TryApplyContactImpulseCombination(
 717        CollisionPair2D pair,
 718        SolverContact2D contact,
 719        Vector2d responsePositionA,
 720        Vector2d responsePositionB,
 721        Vector2d firstAxis,
 722        Fixed64 firstScale,
 723        Vector2d secondAxis,
 724        Fixed64 secondScale)
 725    {
 20726        if (!contact.RelativeA.IsExact
 20727            && !contact.RelativeB.IsExact
 20728            && Vector3d.TryLinearCombination(
 20729                ExactContactLever2D.ToSpatial(firstAxis),
 20730                firstScale,
 20731                ExactContactLever2D.ToSpatial(secondAxis),
 20732                secondScale,
 20733                Vector3d.Zero,
 20734                Fixed64.Zero,
 20735                out Vector3d spatialImpulse)
 20736            && TryApplyCompactImpulse(
 20737                contact,
 20738                ExactContactLever2D.ToPlanar(spatialImpulse)))
 739        {
 17740            return true;
 741        }
 742
 3743        GetExactLevers(
 3744            pair,
 3745            contact,
 3746            responsePositionA,
 3747            responsePositionB,
 3748            out ExactLever3D exactA,
 3749            out ExactLever3D exactB);
 3750        return ExactContactLever2D.TryGetImpulseVelocityDeltas(
 3751                contact.A.Body,
 3752                exactA,
 3753                contact.B.Body,
 3754                exactB,
 3755                firstAxis,
 3756                firstScale,
 3757                secondAxis,
 3758                secondScale,
 3759                out Vector2d linearA,
 3760                out Fixed64 angularA,
 3761                out Vector2d linearB,
 3762                out Fixed64 angularB)
 3763            && TryApplyVelocityDeltas(
 3764                contact,
 3765                linearA,
 3766                angularA,
 3767                linearB,
 3768                angularB);
 769    }
 770
 771    private static bool TryApplyCompactImpulse(
 772        SolverContact2D contact,
 773        Vector2d impulseB)
 774    {
 19775        Vector2d impulseA = -impulseB;
 19776        bool linearAResolved = TryGetLinearVelocityDelta(
 19777            contact.A,
 19778            impulseA,
 19779            out Vector2d linearA);
 19780        bool angularAResolved = TryGetAngularVelocityDelta(
 19781            contact.A,
 19782            contact.RelativeA.Vector,
 19783            impulseA,
 19784            out Fixed64 angularA);
 19785        bool linearBResolved = TryGetLinearVelocityDelta(
 19786            contact.B,
 19787            impulseB,
 19788            out Vector2d linearB);
 19789        bool angularBResolved = TryGetAngularVelocityDelta(
 19790            contact.B,
 19791            contact.RelativeB.Vector,
 19792            impulseB,
 19793            out Fixed64 angularB);
 19794        return linearAResolved
 19795            & angularAResolved
 19796            & linearBResolved
 19797            & angularBResolved
 19798            && TryApplyVelocityDeltas(
 19799                contact,
 19800                linearA,
 19801                angularA,
 19802                linearB,
 19803                angularB);
 804    }
 805
 806    private static bool TryGetLinearVelocityDelta(
 807        ResponseBody2D body,
 808        Vector2d impulse,
 809        out Vector2d velocityDelta)
 810    {
 38811        if (!body.CanTranslate)
 812        {
 19813            velocityDelta = Vector2d.Zero;
 19814            return true;
 815        }
 816
 19817        return ContinuousCollisionImpulsePolicy.TryResolveVelocityDelta(
 19818            body.Body!.ProjectLinearMotion(impulse),
 19819            Fixed64.One,
 19820            body.InverseMass,
 19821            Fixed64.One,
 19822            out velocityDelta);
 823    }
 824
 825    private static bool TryGetAngularVelocityDelta(
 826        ResponseBody2D body,
 827        Vector2d relativeContactPoint,
 828        Vector2d impulse,
 829        out Fixed64 velocityDelta)
 830    {
 38831        velocityDelta = Fixed64.Zero;
 38832        if (!body.CanRotate)
 25833            return true;
 834
 13835        return ContactResponseArithmetic3D.TryCross(
 13836                ExactContactLever2D.ToSpatial(relativeContactPoint),
 13837                ExactContactLever2D.ToSpatial(impulse),
 13838                out Vector3d torque)
 13839            && Fixed64.TryMultiplyDivide(
 13840                -torque.Y,
 13841                body.InverseMoment,
 13842                Fixed64.One,
 13843                out velocityDelta);
 844    }
 845
 846    private static bool TryApplyVelocityDeltas(
 847        SolverContact2D contact,
 848        Vector2d linearA,
 849        Fixed64 angularA,
 850        Vector2d linearB,
 851        Fixed64 angularB)
 852    {
 119853        bool firstFits = contact.A.Body?.CanApplyCollisionVelocityDeltas(
 119854                linearA,
 119855                angularA)
 119856            ?? true;
 119857        bool secondFits = contact.B.Body?.CanApplyCollisionVelocityDeltas(
 119858                linearB,
 119859                angularB)
 119860            ?? true;
 119861        if (!(firstFits & secondFits))
 1862            return false;
 863
 118864        ApplyVelocityDelta(contact.A, linearA, angularA);
 118865        ApplyVelocityDelta(contact.B, linearB, angularB);
 118866        return true;
 867    }
 868
 869    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 870    private static Vector2d ResolveLinearVelocity(SolidBody2D? body) =>
 6096871        body == null
 6096872            ? Vector2d.Zero
 6096873            : body.ProjectLinearMotion(
 6096874                body.IsKinematic
 6096875                    ? body.SampleContinuousCollisionLinearVelocity(Fixed64.One)
 6096876                    : body.LinearVelocity);
 877
 878    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 879    private static Fixed64 ResolveAngularVelocity(SolidBody2D? body) =>
 6096880        body == null
 6096881            ? Fixed64.Zero
 6096882            : body.IsKinematic
 6096883                ? body.SampleContinuousCollisionAngularVelocity(Fixed64.One)
 6096884                : body.AngularVelocity;
 885
 886    private static Vector2d ResolveContactNormal(Vector2d normal, Vector2d fallbackDirection)
 887    {
 1012888        Vector2d resolved = normal.MagnitudeSquared > Fixed64.Epsilon
 1012889            ? normal.Normalized
 1012890            : fallbackDirection.MagnitudeSquared > Fixed64.Epsilon
 1012891                ? fallbackDirection.Normalized
 1012892                : Vector2d.Zero;
 893
 1012894        if (resolved == Vector2d.Zero)
 1895            return resolved;
 896
 1011897        return fallbackDirection.MagnitudeSquared > Fixed64.Epsilon
 1011898            && Vector2d.Dot(resolved, fallbackDirection) < Fixed64.Zero
 1011899                ? -resolved
 1011900                : resolved;
 901    }
 902
 903    private static void GetExactLevers(
 904        CollisionPair2D pair,
 905        SolverContact2D contact,
 906        Vector2d responsePositionA,
 907        Vector2d responsePositionB,
 908        out ExactLever3D exactA,
 909        out ExactLever3D exactB)
 910    {
 70911        ManifoldContact2D manifoldContact =
 70912            pair.Manifold[contact.ManifoldIndex];
 70913        ContactAnchor2D zero =
 70914            ContactAnchor2D.FromWorldPoint(Vector2d.Zero);
 70915        exactA = contact.A.Body == null
 70916            ? zero.GetXZLeverFrom(zero)
 70917            : manifoldContact.AnchorA.GetXZLeverFrom(
 70918                new ContactAnchor2D(
 70919                    responsePositionA,
 70920                    contact.A.Body.Rotation,
 70921                    contact.A.Body.LocalCenterOfMassOffset));
 70922        exactB = contact.B.Body == null
 70923            ? zero.GetXZLeverFrom(zero)
 70924            : manifoldContact.AnchorB.GetXZLeverFrom(
 70925                new ContactAnchor2D(
 70926                    responsePositionB,
 70927                    contact.B.Body.Rotation,
 70928                    contact.B.Body.LocalCenterOfMassOffset));
 70929    }
 930
 931    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 932    private static bool HasFailedResponse(
 933        byte failedResponseMask,
 934        int contactIndex) =>
 3024935        (failedResponseMask & (1 << contactIndex)) != 0;
 936
 937    private static void RejectResponse(
 938        CollisionPair2D pair,
 939        SolverContact2D contact,
 940        int contactIndex,
 941        ref byte failedResponseMask)
 942    {
 5943        failedResponseMask |= (byte)(1 << contactIndex);
 5944        pair.RemoveWarmStartImpulse(contact.ContactId);
 5945        GravitasLogger.Channel.Error(
 5946            $"2D contact response is outside the representable velocity domain.");
 5947    }
 948}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/2D/ContactNormalImpulse2D.cs

#LineLine coverage
 1//=======================================================================
 2// ContactNormalImpulse2D.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 System.Runtime.CompilerServices;
 11
 12namespace Gravitas.CollisionHandling;
 13
 14/// <summary>
 15/// Side-effect-free 2D contact-normal impulse result for two participants.
 16/// </summary>
 17internal readonly struct ContactNormalImpulseResult2D
 18{
 19    public ContactNormalImpulseResult2D(
 20        Fixed64 normalVelocity,
 21        Fixed64 impulseScalar,
 22        Vector2d linearVelocityDeltaA,
 23        Fixed64 angularVelocityDeltaA,
 24        Vector2d linearVelocityDeltaB,
 25        Fixed64 angularVelocityDeltaB)
 97426        : this(
 97427            normalVelocity,
 97428            impulseScalar,
 97429            impulseScalar,
 97430            linearVelocityDeltaA,
 97431            angularVelocityDeltaA,
 97432            linearVelocityDeltaB,
 97433            angularVelocityDeltaB)
 34    {
 97435    }
 36
 37    public ContactNormalImpulseResult2D(
 38        Fixed64 normalVelocity,
 39        Fixed64 impulseScalar,
 40        Fixed64 appliedImpulseScalar,
 41        Vector2d linearVelocityDeltaA,
 42        Fixed64 angularVelocityDeltaA,
 43        Vector2d linearVelocityDeltaB,
 44        Fixed64 angularVelocityDeltaB,
 45        bool hasRepresentableNormalVelocity = true,
 46        bool hasRepresentableAppliedImpulse = true,
 47        bool hasRepresentableAccumulatedImpulse = true)
 48    {
 100949        NormalVelocity = normalVelocity;
 100950        ImpulseScalar = impulseScalar;
 100951        AppliedImpulseScalar = appliedImpulseScalar;
 100952        LinearVelocityDeltaA = linearVelocityDeltaA;
 100953        AngularVelocityDeltaA = angularVelocityDeltaA;
 100954        LinearVelocityDeltaB = linearVelocityDeltaB;
 100955        AngularVelocityDeltaB = angularVelocityDeltaB;
 100956        HasRepresentableNormalVelocity = hasRepresentableNormalVelocity;
 100957        HasRepresentableAppliedImpulse = hasRepresentableAppliedImpulse;
 100958        HasRepresentableAccumulatedImpulse =
 100959            hasRepresentableAccumulatedImpulse;
 100960    }
 61
 62    public Fixed64 NormalVelocity { get; }
 63
 64    public Fixed64 ImpulseScalar { get; }
 65
 66    public Fixed64 AppliedImpulseScalar { get; }
 67
 68    public bool HasRepresentableNormalVelocity { get; }
 69
 70    public bool HasRepresentableAppliedImpulse { get; }
 71
 72    public bool HasRepresentableAccumulatedImpulse { get; }
 73
 74    public Vector2d LinearVelocityDeltaA { get; }
 75
 76    public Fixed64 AngularVelocityDeltaA { get; }
 77
 78    public Vector2d LinearVelocityDeltaB { get; }
 79
 80    public Fixed64 AngularVelocityDeltaB { get; }
 81}
 82
 83/// <summary>
 84/// Side-effect-free 2D velocity deltas for a normal response whose impulse
 85/// scalar does not need to be representable.
 86/// </summary>
 87internal readonly struct ContactNormalVelocityDeltaResult2D
 88{
 89    public ContactNormalVelocityDeltaResult2D(
 90        Fixed64 normalVelocity,
 91        Vector2d linearVelocityDeltaA,
 92        Fixed64 angularVelocityDeltaA,
 93        Vector2d linearVelocityDeltaB,
 94        Fixed64 angularVelocityDeltaB)
 3095        : this(
 3096            normalVelocity,
 3097            linearVelocityDeltaA,
 3098            angularVelocityDeltaA,
 3099            linearVelocityDeltaB,
 30100            angularVelocityDeltaB,
 30101            normalVelocity < Fixed64.Zero,
 30102            hasRepresentableNormalVelocity: true)
 103    {
 30104    }
 105
 106    public ContactNormalVelocityDeltaResult2D(
 107        Fixed64 normalVelocity,
 108        Vector2d linearVelocityDeltaA,
 109        Fixed64 angularVelocityDeltaA,
 110        Vector2d linearVelocityDeltaB,
 111        Fixed64 angularVelocityDeltaB,
 112        bool isClosing,
 113        bool hasRepresentableNormalVelocity)
 114    {
 52115        NormalVelocity = normalVelocity;
 52116        LinearVelocityDeltaA = linearVelocityDeltaA;
 52117        AngularVelocityDeltaA = angularVelocityDeltaA;
 52118        LinearVelocityDeltaB = linearVelocityDeltaB;
 52119        AngularVelocityDeltaB = angularVelocityDeltaB;
 52120        IsClosing = isClosing;
 52121        HasRepresentableNormalVelocity = hasRepresentableNormalVelocity;
 52122    }
 123
 124    public Fixed64 NormalVelocity { get; }
 125
 126    public bool IsClosing { get; }
 127
 128    public bool HasRepresentableNormalVelocity { get; }
 129
 130    public Vector2d LinearVelocityDeltaA { get; }
 131
 132    public Fixed64 AngularVelocityDeltaA { get; }
 133
 134    public Vector2d LinearVelocityDeltaB { get; }
 135
 136    public Fixed64 AngularVelocityDeltaB { get; }
 137}
 138
 139/// <summary>
 140/// Calculates allocation-free 2D contact-point normal response without mutating either body.
 141/// </summary>
 142internal static class ContactNormalImpulse2D
 143{
 144    internal static bool TryCalculateVelocityDeltas(
 145        SolidBody2D? bodyA,
 146        Vector2d linearVelocityA,
 147        Fixed64 angularVelocityA,
 148        Vector2d relativeContactPointA,
 149        SolidBody2D? bodyB,
 150        Vector2d linearVelocityB,
 151        Fixed64 angularVelocityB,
 152        Vector2d relativeContactPointB,
 153        Vector2d normal,
 154        Fixed64 restitution,
 155        Fixed64 restitutionVelocityThreshold,
 156        out ContactNormalVelocityDeltaResult2D result)
 157    {
 39158        result = default;
 39159        if (!TryComputeNormalVelocity(
 39160                linearVelocityA,
 39161                angularVelocityA,
 39162                relativeContactPointA,
 39163                linearVelocityB,
 39164                angularVelocityB,
 39165                relativeContactPointB,
 39166                normal,
 39167                out Fixed64 normalVelocity))
 168        {
 1169            return false;
 170        }
 38171        if (normalVelocity >= Fixed64.Zero)
 172        {
 1173            result = ZeroVelocityDelta(normalVelocity);
 1174            return true;
 175        }
 176
 37177        if (!TryComputeDenominator(
 37178                bodyA,
 37179                relativeContactPointA,
 37180                bodyB,
 37181                relativeContactPointB,
 37182                normal,
 37183                out Fixed64 denominator)
 37184            || denominator <= Fixed64.Zero)
 185        {
 7186            return false;
 187        }
 188
 30189        Fixed64 appliedRestitution = normalVelocity < -restitutionVelocityThreshold
 30190            ? restitution
 30191            : Fixed64.Zero;
 30192        Fixed64 responseFactor = -(Fixed64.One + appliedRestitution);
 30193        bool linearAResolved = ContinuousCollisionImpulsePolicy.TryResolveVelocityDelta(
 30194                bodyA?.ProjectLinearMotion(-normal) ?? Vector2d.Zero,
 30195                normalVelocity,
 30196                responseFactor,
 30197                bodyA?.EffectiveInverseMass ?? Fixed64.Zero,
 30198                denominator,
 30199                out Vector2d linearVelocityDeltaA);
 30200        bool angularAResolved = TryResolveAngularVelocityDelta(
 30201                bodyA,
 30202                relativeContactPointA,
 30203                -normal,
 30204                normalVelocity,
 30205                responseFactor,
 30206                denominator,
 30207                out Fixed64 angularVelocityDeltaA);
 30208        bool linearBResolved = ContinuousCollisionImpulsePolicy.TryResolveVelocityDelta(
 30209                bodyB?.ProjectLinearMotion(normal) ?? Vector2d.Zero,
 30210                normalVelocity,
 30211                responseFactor,
 30212                bodyB?.EffectiveInverseMass ?? Fixed64.Zero,
 30213                denominator,
 30214                out Vector2d linearVelocityDeltaB);
 30215        bool angularBResolved = TryResolveAngularVelocityDelta(
 30216                bodyB,
 30217                relativeContactPointB,
 30218                normal,
 30219                normalVelocity,
 30220                responseFactor,
 30221                denominator,
 30222                out Fixed64 angularVelocityDeltaB);
 30223        if (!(linearAResolved
 30224                & angularAResolved
 30225                & linearBResolved
 30226                & angularBResolved))
 227        {
 1228            return false;
 229        }
 230
 29231        result = new ContactNormalVelocityDeltaResult2D(
 29232            normalVelocity,
 29233            linearVelocityDeltaA,
 29234            angularVelocityDeltaA,
 29235            linearVelocityDeltaB,
 29236            angularVelocityDeltaB);
 29237        return true;
 238    }
 239
 240    internal static bool TryCalculateAccumulatedDelta(
 241        SolidBody2D? bodyA,
 242        Vector2d linearVelocityA,
 243        Fixed64 angularVelocityA,
 244        Vector2d relativeContactPointA,
 245        SolidBody2D? bodyB,
 246        Vector2d linearVelocityB,
 247        Fixed64 angularVelocityB,
 248        Vector2d relativeContactPointB,
 249        Vector2d normal,
 250        Fixed64 restitution,
 251        Fixed64 restitutionVelocityThreshold,
 252        Fixed64 accumulatedImpulse,
 253        Fixed64 positiveImpulseScale,
 254        Fixed64 negativeImpulseScale,
 255        out ContactNormalImpulseResult2D result)
 256    {
 995257        result = default;
 995258        bool inputsValid =
 995259            accumulatedImpulse >= Fixed64.Zero
 995260            & positiveImpulseScale >= Fixed64.Zero
 995261            & negativeImpulseScale >= Fixed64.Zero;
 995262        if (!inputsValid)
 3263            return false;
 992264        if (!TryComputeNormalVelocity(
 992265                linearVelocityA,
 992266                angularVelocityA,
 992267                relativeContactPointA,
 992268                linearVelocityB,
 992269                angularVelocityB,
 992270                relativeContactPointB,
 992271                normal,
 992272                out Fixed64 normalVelocity)
 992273            || !TryComputeDenominator(
 992274                bodyA,
 992275                relativeContactPointA,
 992276                bodyB,
 992277                relativeContactPointB,
 992278                normal,
 992279                out Fixed64 denominator))
 280        {
 12281            return false;
 282        }
 980283        if (denominator <= Fixed64.Zero)
 284        {
 1285            result = Zero(normalVelocity);
 1286            return true;
 287        }
 288
 979289        Fixed64 appliedRestitution = normalVelocity < -restitutionVelocityThreshold
 979290            ? restitution
 979291            : Fixed64.Zero;
 979292        Fixed64 responseFactor = -(Fixed64.One + appliedRestitution);
 979293        Fixed64 impulseScale = normalVelocity < Fixed64.Zero
 979294            ? positiveImpulseScale
 979295            : negativeImpulseScale;
 296        Fixed64 impulseScalar;
 979297        if (!Fixed64.TryMultiplyDivide(
 979298                normalVelocity,
 979299                responseFactor,
 979300                impulseScale,
 979301                denominator,
 979302                out Fixed64 scaledImpulse))
 303        {
 6304            if (normalVelocity < Fixed64.Zero)
 4305                return false;
 2306            impulseScalar = -accumulatedImpulse;
 307        }
 973308        else if (!Fixed64.TryAdd(
 973309                    accumulatedImpulse,
 973310                    scaledImpulse,
 973311                    out Fixed64 accumulated)
 973312                || !Fixed64.TrySubtract(
 973313                    FixedMath.Max(Fixed64.Zero, accumulated),
 973314                    accumulatedImpulse,
 973315                    out impulseScalar))
 316        {
 1317            return false;
 318        }
 974319        if (impulseScalar == Fixed64.Zero)
 320        {
 914321            result = Zero(normalVelocity);
 914322            return true;
 323        }
 324
 60325        bool linearAResolved = TryComputeLinearVelocityDelta(
 60326            bodyA,
 60327            -normal,
 60328            impulseScalar,
 60329            out Vector2d linearA);
 60330        bool angularAResolved = TryComputeAngularVelocityDelta(
 60331            bodyA,
 60332            relativeContactPointA,
 60333            -normal,
 60334            impulseScalar,
 60335            out Fixed64 angularA);
 60336        bool linearBResolved = TryComputeLinearVelocityDelta(
 60337            bodyB,
 60338            normal,
 60339            impulseScalar,
 60340            out Vector2d linearB);
 60341        bool angularBResolved = TryComputeAngularVelocityDelta(
 60342            bodyB,
 60343            relativeContactPointB,
 60344            normal,
 60345            impulseScalar,
 60346            out Fixed64 angularB);
 60347        if (!(linearAResolved
 60348            & angularAResolved
 60349            & linearBResolved
 60350            & angularBResolved))
 351        {
 1352            return false;
 353        }
 59354        result = new ContactNormalImpulseResult2D(
 59355            normalVelocity,
 59356            impulseScalar,
 59357            linearA,
 59358            angularA,
 59359            linearB,
 59360            angularB);
 59361        return true;
 362    }
 363
 364    internal static bool TryCalculateVelocityDeltasExact(
 365        SolidBody2D? bodyA,
 366        Vector2d linearVelocityA,
 367        Fixed64 angularVelocityA,
 368        in ExactLever3D relativeContactPointA,
 369        SolidBody2D? bodyB,
 370        Vector2d linearVelocityB,
 371        Fixed64 angularVelocityB,
 372        in ExactLever3D relativeContactPointB,
 373        Vector2d normal,
 374        Fixed64 restitution,
 375        Fixed64 restitutionVelocityThreshold,
 376        out ContactNormalVelocityDeltaResult2D result)
 377    {
 26378        result = default;
 26379        if (!ExactContactLever2D.TryGetNormalResponse(
 26380                bodyA,
 26381                linearVelocityA,
 26382                angularVelocityA,
 26383                relativeContactPointA,
 26384                bodyB,
 26385                linearVelocityB,
 26386                angularVelocityB,
 26387                relativeContactPointB,
 26388                normal,
 26389                restitution,
 26390                restitutionVelocityThreshold,
 26391                out ExactNormalResponse3D response))
 392        {
 4393            return false;
 394        }
 395
 22396        bool hasNormalVelocity =
 22397            response.TryGetNormalVelocity(out Fixed64 normalVelocity);
 22398        result = new ContactNormalVelocityDeltaResult2D(
 22399            normalVelocity,
 22400            ExactContactLever2D.ToPlanar(response.FirstLinearVelocityDelta),
 22401            ExactContactLever2D.ToPlanarAngular(response.FirstAngularVelocityDelta),
 22402            ExactContactLever2D.ToPlanar(response.SecondLinearVelocityDelta),
 22403            ExactContactLever2D.ToPlanarAngular(response.SecondAngularVelocityDelta),
 22404            response.IsClosing,
 22405            hasNormalVelocity);
 22406        return true;
 407    }
 408
 409    internal static bool TryCalculateAccumulatedDeltaExact(
 410        SolidBody2D? bodyA,
 411        Vector2d linearVelocityA,
 412        Fixed64 angularVelocityA,
 413        in ExactLever3D relativeContactPointA,
 414        SolidBody2D? bodyB,
 415        Vector2d linearVelocityB,
 416        Fixed64 angularVelocityB,
 417        in ExactLever3D relativeContactPointB,
 418        Vector2d normal,
 419        Fixed64 restitution,
 420        Fixed64 restitutionVelocityThreshold,
 421        Fixed64 accumulatedImpulse,
 422        Fixed64 positiveImpulseScale,
 423        Fixed64 negativeImpulseScale,
 424        out ContactNormalImpulseResult2D result)
 425    {
 38426        result = default;
 38427        if (!ExactContactLever2D.TryGetAccumulatedNormalResponse(
 38428                bodyA,
 38429                linearVelocityA,
 38430                angularVelocityA,
 38431                relativeContactPointA,
 38432                bodyB,
 38433                linearVelocityB,
 38434                angularVelocityB,
 38435                relativeContactPointB,
 38436                normal,
 38437                restitution,
 38438                restitutionVelocityThreshold,
 38439                accumulatedImpulse,
 38440                positiveImpulseScale,
 38441                negativeImpulseScale,
 38442                out ExactNormalResponse3D response))
 443        {
 3444            return false;
 445        }
 446
 35447        bool hasNormalVelocity =
 35448            response.TryGetNormalVelocity(out Fixed64 normalVelocity);
 35449        bool hasAppliedImpulse =
 35450            response.TryGetAppliedImpulse(out Fixed64 appliedImpulse);
 35451        bool hasAccumulatedImpulse =
 35452            response.TryGetAccumulatedImpulse(
 35453                out Fixed64 newAccumulatedImpulse);
 35454        Fixed64 impulseScalar = hasAccumulatedImpulse
 35455            ? newAccumulatedImpulse - accumulatedImpulse
 35456            : -accumulatedImpulse;
 35457        result = new ContactNormalImpulseResult2D(
 35458            normalVelocity,
 35459            impulseScalar,
 35460            appliedImpulse,
 35461            ExactContactLever2D.ToPlanar(response.FirstLinearVelocityDelta),
 35462            ExactContactLever2D.ToPlanarAngular(response.FirstAngularVelocityDelta),
 35463            ExactContactLever2D.ToPlanar(response.SecondLinearVelocityDelta),
 35464            ExactContactLever2D.ToPlanarAngular(response.SecondAngularVelocityDelta),
 35465            hasNormalVelocity,
 35466            hasAppliedImpulse,
 35467            hasAccumulatedImpulse);
 35468        return true;
 469    }
 470
 471    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 472    private static bool TryComputeNormalVelocity(
 473        Vector2d linearVelocityA,
 474        Fixed64 angularVelocityA,
 475        Vector2d relativeContactPointA,
 476        Vector2d linearVelocityB,
 477        Fixed64 angularVelocityB,
 478        Vector2d relativeContactPointB,
 479        Vector2d normal,
 480        out Fixed64 normalVelocity)
 481    {
 1031482        bool angularAResolved = ContactResponseArithmetic3D.TryCross(
 1031483            new Vector3d(
 1031484                Fixed64.Zero,
 1031485                -angularVelocityA,
 1031486                Fixed64.Zero),
 1031487            ExactContactLever2D.ToSpatial(relativeContactPointA),
 1031488            out Vector3d angularA);
 1031489        bool angularBResolved = ContactResponseArithmetic3D.TryCross(
 1031490            new Vector3d(
 1031491                Fixed64.Zero,
 1031492                -angularVelocityB,
 1031493                Fixed64.Zero),
 1031494            ExactContactLever2D.ToSpatial(relativeContactPointB),
 1031495            out Vector3d angularB);
 1031496        bool relativeResolved = Vector3d.TrySubtractSums(
 1031497            ExactContactLever2D.ToSpatial(linearVelocityB),
 1031498            angularB,
 1031499            ExactContactLever2D.ToSpatial(linearVelocityA),
 1031500            angularA,
 1031501            out Vector3d relative);
 1031502        bool projectionResolved = ContactResponseArithmetic3D.TryDot(
 1031503            relative,
 1031504            ExactContactLever2D.ToSpatial(normal),
 1031505            out normalVelocity);
 1031506        if (!(angularAResolved
 1031507            & angularBResolved
 1031508            & relativeResolved
 1031509            & projectionResolved))
 510        {
 4511            normalVelocity = default;
 4512            return false;
 513        }
 1027514        return true;
 515    }
 516
 517    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 518    internal static bool TryComputeDenominator(
 519        SolidBody2D? bodyA,
 520        Vector2d relativeContactPointA,
 521        SolidBody2D? bodyB,
 522        Vector2d relativeContactPointB,
 523        Vector2d normal,
 524        out Fixed64 denominator)
 525    {
 1081526        bool angularAResolved = TryComputeAngularDenominator(
 1081527            bodyA,
 1081528            relativeContactPointA,
 1081529            normal,
 1081530            out Fixed64 angularA);
 1081531        bool angularBResolved = TryComputeAngularDenominator(
 1081532            bodyB,
 1081533            relativeContactPointB,
 1081534            normal,
 1081535            out Fixed64 angularB);
 1081536        bool linearAResolved = TryGetConstrainedInverseMass(
 1081537            bodyA,
 1081538            normal,
 1081539            out Fixed64 linearA);
 1081540        bool linearBResolved = TryGetConstrainedInverseMass(
 1081541            bodyB,
 1081542            normal,
 1081543            out Fixed64 linearB);
 1081544        bool sumResolved = Fixed64.TryAdd(
 1081545                linearA,
 1081546                linearB,
 1081547                out Fixed64 linear)
 1081548            & Fixed64.TryAdd(
 1081549                linear,
 1081550                angularA,
 1081551                out Fixed64 first)
 1081552            & Fixed64.TryAdd(
 1081553                first,
 1081554                angularB,
 1081555                out denominator);
 1081556        if (!(linearAResolved
 1081557            & linearBResolved
 1081558            & angularAResolved
 1081559            & angularBResolved
 1081560            & sumResolved))
 561        {
 13562            denominator = default;
 13563            return false;
 564        }
 1068565        return true;
 566    }
 567
 568    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 569    internal static bool TryComputeAngularDenominator(
 570        SolidBody2D? body,
 571        Vector2d relativeContactPoint,
 572        Vector2d axis,
 573        out Fixed64 denominator)
 574    {
 2500575        if (body?.CanRotate != true)
 576        {
 1119577            denominator = Fixed64.Zero;
 1119578            return true;
 579        }
 580
 1381581        denominator = default;
 1381582        bool crossResolved = ContactResponseArithmetic3D.TryCross(
 1381583                ExactContactLever2D.ToSpatial(relativeContactPoint),
 1381584                ExactContactLever2D.ToSpatial(axis),
 1381585                out Vector3d cross);
 1381586        bool denominatorResolved = crossResolved
 1381587            && Fixed64.TryMultiplyDivide(
 1381588                cross.Y,
 1381589                cross.Y,
 1381590                body.EffectiveInverseMomentOfInertia,
 1381591                Fixed64.One,
 1381592                out denominator);
 1381593        if (!denominatorResolved
 1381594            || (denominator == Fixed64.Zero
 1381595                && cross.Y != Fixed64.Zero
 1381596                && body.EffectiveInverseMomentOfInertia
 1381597                    != Fixed64.Zero))
 598        {
 10599            denominator = default;
 10600            return false;
 601        }
 602
 1371603        return true;
 604    }
 605
 606    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 607    internal static bool TryGetConstrainedInverseMass(
 608        SolidBody2D? body,
 609        Vector2d axis,
 610        out Fixed64 inverseMass)
 611    {
 2182612        if (body == null)
 613        {
 54614            inverseMass = Fixed64.Zero;
 54615            return true;
 616        }
 617
 2128618        inverseMass = body.GetConstrainedInverseMass(axis);
 2128619        return inverseMass != Fixed64.Zero
 2128620            || body.EffectiveInverseMass == Fixed64.Zero
 2128621            || body.ProjectLinearMotion(axis) == Vector2d.Zero;
 622    }
 623
 624    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 625    internal static bool TryComputeLinearVelocityDelta(
 626        SolidBody2D? body,
 627        Vector2d signedNormal,
 628        Fixed64 impulseScalar,
 629        out Vector2d velocityDelta) =>
 223630        ContinuousCollisionImpulsePolicy.TryResolveVelocityDelta(
 223631            body?.ProjectLinearMotion(signedNormal) ?? Vector2d.Zero,
 223632            impulseScalar,
 223633            body?.EffectiveInverseMass ?? Fixed64.Zero,
 223634            Fixed64.One,
 223635            out velocityDelta);
 636
 637    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 638    internal static bool TryComputeAngularVelocityDelta(
 639        SolidBody2D? body,
 640        Vector2d relativeContactPoint,
 641        Vector2d signedNormal,
 642        Fixed64 impulseScalar,
 643        out Fixed64 velocityDelta)
 644    {
 226645        velocityDelta = Fixed64.Zero;
 226646        if (body?.CanRotate != true)
 80647            return true;
 648
 146649        Vector3d spatialPoint =
 146650            ExactContactLever2D.ToSpatial(relativeContactPoint);
 146651        Vector3d spatialNormal =
 146652            ExactContactLever2D.ToSpatial(signedNormal);
 146653        return ContactResponseArithmetic3D.TryCross(
 146654                spatialPoint,
 146655                spatialNormal,
 146656                out Vector3d cross)
 146657            && Fixed64.TryMultiplyDivide(
 146658                -cross.Y,
 146659                impulseScalar,
 146660                body.EffectiveInverseMomentOfInertia,
 146661                Fixed64.One,
 146662                out velocityDelta);
 663    }
 664
 665    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 666    private static bool TryResolveAngularVelocityDelta(
 667        SolidBody2D? body,
 668        Vector2d relativeContactPoint,
 669        Vector2d signedNormal,
 670        Fixed64 normalVelocity,
 671        Fixed64 responseFactor,
 672        Fixed64 denominator,
 673        out Fixed64 velocityDelta)
 674    {
 60675        velocityDelta = Fixed64.Zero;
 60676        if (body?.CanRotate != true)
 20677            return true;
 678
 40679        Fixed64 torqueScale = Vector2d.CrossProduct(relativeContactPoint, signedNormal);
 40680        if (torqueScale == Fixed64.Zero)
 28681            return true;
 682
 12683        bool angularScaleResolved = Fixed64.TryMultiplyDivide(
 12684            normalVelocity,
 12685            responseFactor,
 12686            body.EffectiveInverseMomentOfInertia,
 12687            denominator,
 12688            out Fixed64 angularScale);
 12689        bool velocityDeltaResolved = Fixed64.TryMultiplyDivide(
 12690            torqueScale,
 12691            angularScale,
 12692            Fixed64.One,
 12693            out velocityDelta);
 12694        return angularScaleResolved
 12695            & (angularScale != Fixed64.Zero | torqueScale.Abs() <= Fixed64.One)
 12696            & velocityDeltaResolved;
 697    }
 698
 699    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 700    private static ContactNormalImpulseResult2D Zero(Fixed64 normalVelocity) =>
 915701        new(
 915702            normalVelocity,
 915703            Fixed64.Zero,
 915704            Vector2d.Zero,
 915705            Fixed64.Zero,
 915706            Vector2d.Zero,
 915707            Fixed64.Zero);
 708
 709    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 710    private static ContactNormalVelocityDeltaResult2D ZeroVelocityDelta(Fixed64 normalVelocity) =>
 1711        new(
 1712            normalVelocity,
 1713            Vector2d.Zero,
 1714            Fixed64.Zero,
 1715            Vector2d.Zero,
 1716            Fixed64.Zero);
 717}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/2D/ExactContactLever2D.cs

#LineLine coverage
 1//=======================================================================
 2// ExactContactLever2D.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;
 10
 11namespace Gravitas.CollisionHandling;
 12
 13/// <summary>
 14/// Adapts planar body mobility and signed yaw to the shared exact 3D response
 15/// kernel.
 16/// </summary>
 17internal static class ExactContactLever2D
 18{
 19    internal static bool CanUseCompactResponse(
 20        SolidBody2D? bodyA,
 21        Vector2d linearVelocityA,
 22        Fixed64 angularVelocityA,
 23        Vector2d relativeContactPointA,
 24        SolidBody2D? bodyB,
 25        Vector2d linearVelocityB,
 26        Fixed64 angularVelocityB,
 27        Vector2d relativeContactPointB,
 28        Vector2d axis)
 29    {
 201130        Vector3d spatialAxis = ToSpatial(axis);
 201131        Vector3d leverA = ToSpatial(relativeContactPointA);
 201132        Vector3d leverB = ToSpatial(relativeContactPointB);
 201133        return ContactResponseArithmetic3D.CanUseFastPointVelocity(
 201134                ToSpatial(linearVelocityA),
 201135                new Vector3d(
 201136                    Fixed64.Zero,
 201137                    -angularVelocityA,
 201138                    Fixed64.Zero),
 201139                leverA,
 201140                ToSpatial(linearVelocityB),
 201141                new Vector3d(
 201142                    Fixed64.Zero,
 201143                    -angularVelocityB,
 201144                    Fixed64.Zero),
 201145                leverB,
 201146                spatialAxis)
 201147            && ContactResponseArithmetic3D.CanUseFastAngularResponse(
 201148                leverA,
 201149                spatialAxis,
 201150                CreateInverseInertia(bodyA))
 201151            && ContactResponseArithmetic3D.CanUseFastAngularResponse(
 201152                leverB,
 201153                spatialAxis,
 201154                CreateInverseInertia(bodyB));
 55    }
 56
 57    internal static ExactContactResponseOperand3D CreateResponseOperand(
 58        SolidBody2D? body,
 59        Vector2d linearVelocity,
 60        Fixed64 angularVelocity,
 61        in ExactLever3D lever,
 62        Vector3d signedAxis) =>
 46063        new(
 46064            lever,
 46065            ToSpatial(linearVelocity),
 46066            new Vector3d(
 46067                Fixed64.Zero,
 46068                -angularVelocity,
 46069                Fixed64.Zero),
 46070            body == null
 46071                ? Vector3d.Zero
 46072                : ToSpatial(body.ProjectLinearMotion(ToPlanar(signedAxis))),
 46073            body?.EffectiveInverseMass ?? Fixed64.Zero,
 46074            CreateInverseInertia(body));
 75
 76    internal static bool TryGetNormalResponse(
 77        SolidBody2D? bodyA,
 78        Vector2d linearVelocityA,
 79        Fixed64 angularVelocityA,
 80        in ExactLever3D relativeContactPointA,
 81        SolidBody2D? bodyB,
 82        Vector2d linearVelocityB,
 83        Fixed64 angularVelocityB,
 84        in ExactLever3D relativeContactPointB,
 85        Vector2d normal,
 86        Fixed64 restitution,
 87        Fixed64 restitutionVelocityThreshold,
 88        out ExactNormalResponse3D response)
 89    {
 2690        Vector3d spatialNormal = ToSpatial(normal);
 2691        ExactContactResponseOperand3D first = CreateResponseOperand(
 2692            bodyA,
 2693            linearVelocityA,
 2694            angularVelocityA,
 2695            relativeContactPointA,
 2696            -spatialNormal);
 2697        ExactContactResponseOperand3D second = CreateResponseOperand(
 2698            bodyB,
 2699            linearVelocityB,
 26100            angularVelocityB,
 26101            relativeContactPointB,
 26102            spatialNormal);
 26103        return ExactContactResponseKernel.TryGetNormalResponse(
 26104            first,
 26105            second,
 26106            spatialNormal,
 26107            restitution,
 26108            restitutionVelocityThreshold,
 26109            out response);
 110    }
 111
 112    internal static bool TryGetAccumulatedNormalResponse(
 113        SolidBody2D? bodyA,
 114        Vector2d linearVelocityA,
 115        Fixed64 angularVelocityA,
 116        in ExactLever3D relativeContactPointA,
 117        SolidBody2D? bodyB,
 118        Vector2d linearVelocityB,
 119        Fixed64 angularVelocityB,
 120        in ExactLever3D relativeContactPointB,
 121        Vector2d normal,
 122        Fixed64 restitution,
 123        Fixed64 restitutionVelocityThreshold,
 124        Fixed64 accumulatedImpulse,
 125        Fixed64 positiveImpulseScale,
 126        Fixed64 negativeImpulseScale,
 127        out ExactNormalResponse3D response)
 128    {
 38129        Vector3d spatialNormal = ToSpatial(normal);
 38130        ExactContactResponseOperand3D first = CreateResponseOperand(
 38131            bodyA,
 38132            linearVelocityA,
 38133            angularVelocityA,
 38134            relativeContactPointA,
 38135            -spatialNormal);
 38136        ExactContactResponseOperand3D second = CreateResponseOperand(
 38137            bodyB,
 38138            linearVelocityB,
 38139            angularVelocityB,
 38140            relativeContactPointB,
 38141            spatialNormal);
 38142        return ExactContactResponseKernel.TryGetAccumulatedNormalResponse(
 38143            first,
 38144            second,
 38145            spatialNormal,
 38146            restitution,
 38147            restitutionVelocityThreshold,
 38148            accumulatedImpulse,
 38149            positiveImpulseScale,
 38150            negativeImpulseScale,
 38151            out response);
 152    }
 153
 154    internal static Vector3d ToSpatial(Vector2d vector) =>
 21145155        new(vector.X, Fixed64.Zero, vector.Y);
 156
 157    internal static Vector2d ToPlanar(Vector3d vector) =>
 700158        new(vector.X, vector.Z);
 159
 160    internal static Fixed64 ToPlanarAngular(Vector3d vector) =>
 306161        -vector.Y;
 162
 163    internal static bool TryGetImpulseVelocityDeltas(
 164        SolidBody2D? bodyA,
 165        in ExactLever3D relativeContactPointA,
 166        SolidBody2D? bodyB,
 167        in ExactLever3D relativeContactPointB,
 168        Vector2d firstAxis,
 169        Fixed64 firstScale,
 170        Vector2d secondAxis,
 171        Fixed64 secondScale,
 172        out Vector2d linearVelocityDeltaA,
 173        out Fixed64 angularVelocityDeltaA,
 174        out Vector2d linearVelocityDeltaB,
 175        out Fixed64 angularVelocityDeltaB)
 176    {
 3177        Vector3d first = ToSpatial(firstAxis);
 3178        Vector3d second = ToSpatial(secondAxis);
 3179        bool firstResolved = TryGetParticipantVelocityDeltas(
 3180            bodyA,
 3181            relativeContactPointA,
 3182            -first,
 3183            firstScale,
 3184            -second,
 3185            secondScale,
 3186            out linearVelocityDeltaA,
 3187            out angularVelocityDeltaA);
 3188        bool secondResolved = TryGetParticipantVelocityDeltas(
 3189            bodyB,
 3190            relativeContactPointB,
 3191            first,
 3192            firstScale,
 3193            second,
 3194            secondScale,
 3195            out linearVelocityDeltaB,
 3196            out angularVelocityDeltaB);
 3197        return firstResolved & secondResolved;
 198    }
 199
 200    internal static bool TryGetParticipantVelocityDeltas(
 201        SolidBody2D? body,
 202        in ExactLever3D lever,
 203        Vector3d firstAxis,
 204        Fixed64 firstScale,
 205        Vector3d secondAxis,
 206        Fixed64 secondScale,
 207        out Vector2d linearVelocityDelta,
 208        out Fixed64 angularVelocityDelta)
 209    {
 10210        linearVelocityDelta = Vector2d.Zero;
 10211        angularVelocityDelta = Fixed64.Zero;
 10212        if (body?.HasSolverMobility != true)
 5213            return true;
 214
 5215        Vector3d spatialLinear = Vector3d.Zero;
 5216        bool linearResolved = !body.CanTranslate
 5217            || Vector3d.TryScaledLinearCombination(
 5218                ToSpatial(body.ProjectLinearMotion(ToPlanar(firstAxis))),
 5219                firstScale,
 5220                ToSpatial(body.ProjectLinearMotion(ToPlanar(secondAxis))),
 5221                secondScale,
 5222                Vector3d.Zero,
 5223                Fixed64.Zero,
 5224                body.EffectiveInverseMass,
 5225                out spatialLinear);
 5226        Vector3d spatialAngular = Vector3d.Zero;
 5227        bool angularResolved = !body.CanRotate
 5228            || ExactLever3D.TryGetTransformedWeightedCrossProduct(
 5229                lever,
 5230                firstAxis,
 5231                firstScale,
 5232                secondAxis,
 5233                secondScale,
 5234                Vector3d.Zero,
 5235                Fixed64.Zero,
 5236                CreateInverseInertia(body),
 5237                out spatialAngular);
 5238        linearVelocityDelta = ToPlanar(spatialLinear);
 5239        angularVelocityDelta = ToPlanarAngular(spatialAngular);
 5240        return linearResolved & angularResolved;
 241    }
 242
 243    internal static Fixed3x3 CreateInverseInertia(SolidBody2D? body) =>
 4805244        body?.CanRotate == true
 4805245            ? new Fixed3x3(
 4805246                Fixed64.Zero, Fixed64.Zero, Fixed64.Zero,
 4805247                Fixed64.Zero, body.EffectiveInverseMomentOfInertia, Fixed64.Zero,
 4805248                Fixed64.Zero, Fixed64.Zero, Fixed64.Zero)
 4805249            : Fixed3x3.Zero;
 250}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/2D/ResponseBody2D.cs

#LineLine coverage
 1//=======================================================================
 2// ResponseBody2D.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;
 10
 11namespace Gravitas.CollisionHandling;
 12
 13/// <summary>
 14/// Scalar pure 2D response body data for one collider participant.
 15/// </summary>
 16internal readonly struct ResponseBody2D
 17{
 18    private ResponseBody2D(SolidBody2D? body, Fixed64 inverseMass, Fixed64 inverseMoment)
 19    {
 196420        Body = body;
 196421        InverseMass = inverseMass;
 196422        InverseMoment = inverseMoment;
 196423    }
 24
 25    public SolidBody2D? Body { get; }
 26
 27    public Fixed64 InverseMass { get; }
 28
 29    public Fixed64 InverseMoment { get; }
 30
 109031    public bool CanTranslate => Body?.CanTranslate == true;
 32
 21633    public bool CanRotate => Body?.CanRotate == true;
 34
 161435    public bool HasSolverMobility => Body?.HasSolverMobility == true;
 36
 37    public Fixed64 GetConstrainedInverseMass(Vector2d axis) =>
 87638        Body?.GetConstrainedInverseMass(axis) ?? Fixed64.Zero;
 39
 40    public static ResponseBody2D Create(LSCollider2D collider)
 41    {
 199042        SolidBody2D? body = collider.Body;
 199043        if (body == null)
 2644            return default;
 45
 196446        return new ResponseBody2D(
 196447            body,
 196448            body.EffectiveInverseMass,
 196449            body.EffectiveInverseMomentOfInertia);
 50    }
 51}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/2D/SolverContact2D.cs

#LineLine coverage
 1//=======================================================================
 2// SolverContact2D.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.Materials;
 10
 11namespace Gravitas.CollisionHandling;
 12
 13internal readonly struct ContactLever2D
 14{
 15    private ContactLever2D(Vector2d vector, bool isExact)
 16    {
 210217        Vector = vector;
 210218        IsExact = isExact;
 210219    }
 20
 21    internal bool IsExact { get; }
 22
 23    internal Vector2d Vector { get; }
 24
 25    internal static ContactLever2D Zero =>
 2526        new(Vector2d.Zero, isExact: false);
 27
 28    internal static ContactLever2D Create(
 29        in ContactAnchor2D point,
 30        in ContactAnchor2D origin)
 31    {
 207732        if (point.TryGetOffsetFrom(origin, out Vector2d vector))
 205633            return new ContactLever2D(vector, isExact: false);
 34
 2135        return new ContactLever2D(default, isExact: true);
 36    }
 37}
 38
 39/// <summary>
 40/// Solver-ready scalar pure 2D contact data.
 41/// </summary>
 42internal readonly struct SolverContact2D
 43{
 44    public SolverContact2D(
 45        int manifoldIndex,
 46        ulong contactId,
 47        ResponseBody2D bodyA,
 48        ResponseBody2D bodyB,
 49        ContactLever2D relativeA,
 50        ContactLever2D relativeB,
 51        Fixed64 depth,
 52        Vector2d normal,
 53        PhysicsMaterial materialA,
 54        PhysicsMaterial materialB,
 55        Fixed64 cachedNormalImpulse,
 56        Fixed64 cachedTangentImpulse)
 57    {
 101158        ManifoldIndex = manifoldIndex;
 101159        ContactId = contactId;
 101160        A = bodyA;
 101161        B = bodyB;
 101162        RelativeA = relativeA;
 101163        RelativeB = relativeB;
 101164        Depth = depth;
 101165        Normal = normal;
 101166        MaterialA = materialA;
 101167        MaterialB = materialB;
 101168        PhysicsMaterial.CombineFriction(materialA, materialB, out Fixed64 staticFriction, out Fixed64 dynamicFriction);
 101169        StaticFriction = staticFriction;
 101170        DynamicFriction = dynamicFriction;
 101171        Restitution = PhysicsMaterial.CombineRestitution(materialA, materialB);
 101172        Tangent = normal.RightHandNormal;
 101173        CachedNormalImpulse = cachedNormalImpulse;
 101174        CachedTangentImpulse = cachedTangentImpulse;
 101175    }
 76
 77    public int ManifoldIndex { get; }
 78
 79    public ulong ContactId { get; }
 80
 81    public ResponseBody2D A { get; }
 82
 83    public ResponseBody2D B { get; }
 84
 85    public ContactLever2D RelativeA { get; }
 86
 87    public ContactLever2D RelativeB { get; }
 88
 89    public Fixed64 Depth { get; }
 90
 91    public Vector2d Normal { get; }
 92
 93    public PhysicsMaterial MaterialA { get; }
 94
 95    public PhysicsMaterial MaterialB { get; }
 96
 97    public Fixed64 StaticFriction { get; }
 98
 99    public Fixed64 DynamicFriction { get; }
 100
 101    public Fixed64 Restitution { get; }
 102
 103    public Vector2d Tangent { get; }
 104
 105    public Fixed64 CachedNormalImpulse { get; }
 106
 107    public Fixed64 CachedTangentImpulse { get; }
 108
 109}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/2D/SolverContactBuffer2D.cs

#LineLine coverage
 1//=======================================================================
 2// SolverContactBuffer2D.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.CollisionHandling;
 12
 13/// <summary>
 14/// Fixed-capacity pure 2D solver contact buffer.
 15/// </summary>
 16internal struct SolverContactBuffer2D
 17{
 18    private SolverContact2D _contact0;
 19    private SolverContact2D _contact1;
 20    private Fixed64 _normalImpulse0;
 21    private Fixed64 _normalImpulse1;
 22    private ContactNormalImpulseResult2D _normalResult0;
 23    private ContactNormalImpulseResult2D _normalResult1;
 24    private Fixed64 _tangentImpulse0;
 25    private Fixed64 _tangentImpulse1;
 26
 27    public int Count { get; private set; }
 28
 29    public void Add(SolverContact2D contact)
 30    {
 101131        if (Count == 0)
 99132            _contact0 = contact;
 33        else
 2034            _contact1 = contact;
 35
 101136        Count++;
 101137    }
 38
 39    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 510740    public SolverContact2D GetContact(int index) => index == 0 ? _contact0 : _contact1;
 41
 42    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 200843    public Fixed64 GetNormalImpulse(int index) => index == 0 ? _normalImpulse0 : _normalImpulse1;
 44
 45    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 46    public ContactNormalImpulseResult2D GetNormalResult(int index) =>
 201147        index == 0 ? _normalResult0 : _normalResult1;
 48
 49    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 100350    public Fixed64 GetTangentImpulse(int index) => index == 0 ? _tangentImpulse0 : _tangentImpulse1;
 51
 52    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 53    public void SetNormalImpulse(
 54        int index,
 55        Fixed64 impulse,
 56        ContactNormalImpulseResult2D normalResult)
 57    {
 100658        if (index == 0)
 59        {
 98760            _normalImpulse0 = impulse;
 98761            _normalResult0 = normalResult;
 98762            return;
 63        }
 64
 1965        _normalImpulse1 = impulse;
 1966        _normalResult1 = normalResult;
 1967    }
 68
 69    public void SetTangentImpulse(int index, Fixed64 impulse)
 70    {
 100371        if (index == 0)
 72        {
 98473            _tangentImpulse0 = impulse;
 98474            return;
 75        }
 76
 1977        _tangentImpulse1 = impulse;
 1978    }
 79}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/3D/CollisionResponse.cs

#LineLine coverage
 1//=======================================================================
 2// CollisionResponse.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.Materials;
 11using System.Runtime.CompilerServices;
 12
 13namespace Gravitas.CollisionHandling;
 14
 15/// <summary>
 16/// Solves deterministic contact response for one collision pair manifold.
 17/// </summary>
 18public static class CollisionResponse
 19{
 20    /// <summary>
 21    /// Penetration depth below this value is treated as contact slop and does not
 22    /// produce positional correction.
 23    /// </summary>
 124    public static readonly Fixed64 PenetrationSlop = (Fixed64)0.01f;
 25
 26    /// <summary>
 27    /// Fraction of penetration above slop corrected per solver call.
 28    /// </summary>
 129    public static readonly Fixed64 PenetrationCorrectionPercent = (Fixed64)0.8f;
 30
 131    private static readonly Fixed64 WarmStartNormalCompatibilityThreshold = Fixed64.FromFraction(63, 64);
 32
 33    /// <summary>
 34    /// Applies positional correction, normal impulses, and Coulomb friction for
 35    /// the collision pair's current deterministic contact manifold.
 36    /// </summary>
 37    public static void CalculateImpulse(CollisionPair pair) =>
 15538        CalculateImpulse(
 15539            pair,
 15540            applyCachedImpulse: true,
 15541            applyPositionCorrection: true);
 42
 43    internal static void CalculateImpulse(
 44        CollisionPair pair,
 45        bool applyCachedImpulse,
 46        bool applyPositionCorrection)
 47    {
 884248        if (!TryCreateBodyPair(pair, out ResponseBody bodyA, out ResponseBody bodyB))
 449            return;
 50
 883851        ContactAnchor responseCenterA = bodyA.Body.GetCenterOfMassAnchor();
 883852        ContactAnchor responseCenterB = bodyB.Body.GetCenterOfMassAnchor();
 883853        SolverContactBuffer contacts = BuildContactBuffer(
 883854            pair,
 883855            bodyA,
 883856            bodyB,
 883857            responseCenterA,
 883858            responseCenterB);
 883859        if (contacts.Count == 0)
 160            return;
 61
 883762        Vector3d responsePositionA = bodyA.Body.Position3d;
 883763        Vector3d responsePositionB = bodyB.Body.Position3d;
 883764        byte failedResponseMask = 0;
 883765        Fixed64 contactShare = Fixed64.One / (Fixed64)contacts.Count;
 883766        if (applyPositionCorrection)
 67        {
 370068            for (int i = 0; i < contacts.Count; i++)
 97069                ApplyPositionCorrection(contacts.GetContact(i), contactShare);
 70        }
 71
 883772        if (applyCachedImpulse)
 73        {
 88874            bool rebuildContacts = false;
 373275            for (int i = 0; i < contacts.Count; i++)
 76            {
 97877                SolverContact contact = contacts.GetContact(i);
 97878                if (!TryApplyCachedImpulse(
 97879                        pair,
 97880                        contact,
 97881                        responsePositionA,
 97882                        responsePositionB))
 83                {
 784                    ClearWarmStartImpulse(pair, contact);
 785                    rebuildContacts = true;
 86                }
 87            }
 88
 88889            if (rebuildContacts)
 90            {
 791                contacts = BuildContactBuffer(
 792                    pair,
 793                    bodyA,
 794                    bodyB,
 795                    responseCenterA,
 796                    responseCenterB);
 97            }
 98        }
 99
 8837100        Fixed64 restitutionVelocityThreshold = pair.Context.Settings.RestitutionVelocityThreshold;
 35828101        for (int i = 0; i < contacts.Count; i++)
 102        {
 9077103            SolverContact contact = contacts.GetContact(i);
 104            bool normalResolved;
 105            ContactNormalImpulseResult3D normalResult;
 9077106            if (contact.RelativeA.IsExact || contact.RelativeB.IsExact)
 107            {
 34108                normalResolved = TryCalculateExactNormalResult(
 34109                    pair,
 34110                    contact,
 34111                    responsePositionA,
 34112                    responsePositionB,
 34113                    restitutionVelocityThreshold,
 34114                    contactShare,
 34115                    out normalResult);
 116            }
 117            else
 118            {
 9043119                normalResolved = ContactNormalImpulse3D.TryCalculateAccumulatedDelta(
 9043120                    contact.A.Body,
 9043121                    ResolveLinearVelocity(contact.A.Body),
 9043122                    ResolveAngularVelocity(contact.A.Body),
 9043123                    contact.RelativeA.Vector,
 9043124                    contact.B.Body,
 9043125                    ResolveLinearVelocity(contact.B.Body),
 9043126                    ResolveAngularVelocity(contact.B.Body),
 9043127                    contact.RelativeB.Vector,
 9043128                    contact.Normal,
 9043129                    contact.Restitution,
 9043130                    restitutionVelocityThreshold,
 9043131                    contact.CachedNormalImpulse,
 9043132                    contactShare,
 9043133                    Fixed64.One,
 9043134                    out normalResult);
 9043135                if (!normalResolved)
 136                {
 1353137                    normalResolved = TryCalculateExactNormalResult(
 1353138                        pair,
 1353139                        contact,
 1353140                        responsePositionA,
 1353141                        responsePositionB,
 1353142                        restitutionVelocityThreshold,
 1353143                        contactShare,
 1353144                        out normalResult);
 145                }
 146            }
 147
 9077148            if (!normalResolved)
 149            {
 2150                RejectResponse(pair, contact, i, ref failedResponseMask);
 2151                continue;
 152            }
 153
 154            // The kernel preflights this same cache-plus-delta sum.
 9075155            Fixed64 normalImpulse =
 9075156                contact.CachedNormalImpulse
 9075157                + normalResult.ImpulseScalar;
 9075158            contacts.SetNormalImpulse(i, normalImpulse, normalResult);
 159        }
 160
 35828161        for (int i = 0; i < contacts.Count; i++)
 162        {
 9077163            if (HasFailedResponse(failedResponseMask, i))
 164                continue;
 165
 9075166            SolverContact contact = contacts.GetContact(i);
 9075167            if (!TryApplyNormalImpulse(
 9075168                    pair,
 9075169                    contact,
 9075170                    contacts.GetNormalResult(i)))
 171            {
 1172                RejectResponse(pair, contact, i, ref failedResponseMask);
 173            }
 174        }
 175
 35828176        for (int i = 0; i < contacts.Count; i++)
 177        {
 9077178            if (HasFailedResponse(failedResponseMask, i))
 179                continue;
 180
 9074181            SolverContact contact = contacts.GetContact(i);
 9074182            if (!TrySolveFrictionImpulse(
 9074183                    pair,
 9074184                    contact,
 9074185                    responsePositionA,
 9074186                    responsePositionB,
 9074187                    FixedMath.Max(contacts.GetNormalImpulse(i), contact.CachedNormalImpulse),
 9074188                    out Fixed64 tangentImpulse,
 9074189                    out Fixed64 secondaryTangentImpulse))
 190            {
 1191                RejectResponse(pair, contact, i, ref failedResponseMask);
 1192                continue;
 193            }
 194
 9073195            contacts.SetTangentImpulse(i, tangentImpulse, secondaryTangentImpulse);
 196        }
 197
 35828198        for (int i = 0; i < contacts.Count; i++)
 199        {
 9077200            if (HasFailedResponse(failedResponseMask, i))
 201                continue;
 202
 9073203            SolverContact contact = contacts.GetContact(i);
 9073204            pair.StoreWarmStartImpulse(
 9073205                contact.ContactId,
 9073206                contact.Normal,
 9073207                contacts.GetNormalImpulse(i),
 9073208                contacts.GetTangentImpulse(i),
 9073209                contacts.GetSecondaryTangentImpulse(i));
 210        }
 8837211    }
 212
 213    private static bool TryCreateBodyPair(CollisionPair pair, out ResponseBody bodyA, out ResponseBody bodyB)
 214    {
 8842215        bodyA = default;
 8842216        bodyB = default;
 217
 8842218        if (pair.ColliderA.IsTrigger || pair.ColliderB.IsTrigger)
 1219            return false;
 220
 8841221        if (pair.ColliderA.Body == null || pair.ColliderB.Body == null)
 1222            return false;
 223
 8840224        if (!pair.Manifold.HasContact)
 1225            return false;
 226
 8839227        bodyA = ResponseBody.Create(pair.ColliderA);
 8839228        bodyB = ResponseBody.Create(pair.ColliderB);
 8839229        return bodyA.HasSolverMobility || bodyB.HasSolverMobility;
 230    }
 231
 232    private static SolverContactBuffer BuildContactBuffer(
 233        CollisionPair pair,
 234        ResponseBody bodyA,
 235        ResponseBody bodyB,
 236        in ContactAnchor responseCenterA,
 237        in ContactAnchor responseCenterB)
 238    {
 8845239        SolverContactBuffer contacts = default;
 35860240        for (int i = 0; i < pair.Manifold.Count; i++)
 241        {
 9085242            if (TryCreateContact(
 9085243                    pair,
 9085244                    bodyA,
 9085245                    bodyB,
 9085246                    responseCenterA,
 9085247                    responseCenterB,
 9085248                    i,
 9085249                    out SolverContact contact))
 250            {
 9084251                contacts.Add(contact);
 252            }
 253        }
 254
 8845255        return contacts;
 256    }
 257
 258    private static bool TryCreateContact(
 259        CollisionPair pair,
 260        ResponseBody bodyA,
 261        ResponseBody bodyB,
 262        in ContactAnchor responseCenterA,
 263        in ContactAnchor responseCenterB,
 264        int contactIndex,
 265        out SolverContact contact)
 266    {
 9085267        contact = default;
 9085268        ManifoldContact manifoldContact = pair.Manifold[contactIndex];
 9085269        Vector3d normal = ResolveContactNormal(manifoldContact.Normal, pair.ColliderB.Center - pair.ColliderA.Center);
 9085270        if (normal == Vector3d.Zero)
 1271            return false;
 9084272        ContactLever3D relativeA = ContactLever3D.Create(
 9084273            manifoldContact.AnchorA,
 9084274            responseCenterA);
 9084275        ContactLever3D relativeB = ContactLever3D.Create(
 9084276            manifoldContact.AnchorB,
 9084277            responseCenterB);
 9084278        PhysicsMaterial materialA = manifoldContact.HasMaterialOverride
 9084279            ? manifoldContact.MaterialA
 9084280            : pair.ColliderA.Material;
 9084281        PhysicsMaterial materialB = manifoldContact.HasMaterialOverride
 9084282            ? manifoldContact.MaterialB
 9084283            : pair.ColliderB.Material;
 284
 9084285        Fixed64 cachedNormalImpulse = Fixed64.Zero;
 9084286        Fixed64 cachedTangentImpulse = Fixed64.Zero;
 9084287        Fixed64 cachedSecondaryTangentImpulse = Fixed64.Zero;
 9084288        if (pair.TryGetWarmStartImpulse(manifoldContact.ContactId, out ContactWarmStartImpulse cached))
 289        {
 8053290            if (IsWarmStartCompatible(cached.Normal, normal))
 291            {
 8050292                cachedNormalImpulse = cached.NormalImpulse;
 8050293                cachedTangentImpulse = cached.TangentImpulse;
 8050294                cachedSecondaryTangentImpulse = cached.SecondaryTangentImpulse;
 295            }
 296        }
 297
 9084298        contact = new SolverContact(
 9084299            contactIndex,
 9084300            manifoldContact.ContactId,
 9084301            bodyA,
 9084302            bodyB,
 9084303            relativeA,
 9084304            relativeB,
 9084305            manifoldContact.Depth,
 9084306            normal,
 9084307            materialA,
 9084308            materialB,
 9084309            cachedNormalImpulse,
 9084310            cachedTangentImpulse,
 9084311            cachedSecondaryTangentImpulse);
 9084312        return true;
 313    }
 314
 315    [MethodImpl(MethodImplOptions.NoInlining)]
 316    private static bool TryCalculateExactNormalResult(
 317        CollisionPair pair,
 318        SolverContact contact,
 319        Vector3d responsePositionA,
 320        Vector3d responsePositionB,
 321        Fixed64 restitutionVelocityThreshold,
 322        Fixed64 contactShare,
 323        out ContactNormalImpulseResult3D result)
 324    {
 1387325        result = default;
 1387326        GetExactLevers(
 1387327            pair,
 1387328            contact,
 1387329            responsePositionA,
 1387330            responsePositionB,
 1387331            out ExactLever3D exactA,
 1387332            out ExactLever3D exactB);
 1387333        return ContactNormalImpulse3D.TryCalculateAccumulatedDeltaExact(
 1387334            contact.A.Body,
 1387335            ResolveLinearVelocity(contact.A.Body),
 1387336            ResolveAngularVelocity(contact.A.Body),
 1387337            exactA,
 1387338            contact.B.Body,
 1387339            ResolveLinearVelocity(contact.B.Body),
 1387340            ResolveAngularVelocity(contact.B.Body),
 1387341            exactB,
 1387342            contact.Normal,
 1387343            contact.Restitution,
 1387344            restitutionVelocityThreshold,
 1387345            contact.CachedNormalImpulse,
 1387346            contactShare,
 1387347            Fixed64.One,
 1387348            out result);
 349    }
 350
 351    private static void ApplyPositionCorrection(SolverContact contact, Fixed64 contactShare)
 352    {
 970353        Fixed64 correctionDepth = contact.Depth - PenetrationSlop;
 970354        if (correctionDepth <= Fixed64.Zero)
 436355            return;
 356
 534357        Fixed64 inverseMassA = contact.A.GetConstrainedInverseMass(contact.Normal);
 534358        Fixed64 inverseMassB = contact.B.GetConstrainedInverseMass(contact.Normal);
 534359        Fixed64 totalInverseMass = inverseMassA + inverseMassB;
 534360        if (totalInverseMass <= Fixed64.Zero)
 1361            return;
 362
 533363        Vector3d correction = contact.Normal
 533364            * (correctionDepth * PenetrationCorrectionPercent * contactShare / totalInverseMass);
 533365        contact.A.Body.ApplyCollisionPositionCorrection(-correction * inverseMassA);
 533366        contact.B.Body.ApplyCollisionPositionCorrection(correction * inverseMassB);
 533367    }
 368
 369    private static bool TryApplyCachedImpulse(
 370        CollisionPair pair,
 371        SolverContact contact,
 372        Vector3d responsePositionA,
 373        Vector3d responsePositionB)
 374    {
 978375        if (contact.CachedNormalImpulse == Fixed64.Zero
 978376            && contact.CachedTangentImpulse == Fixed64.Zero
 978377            && contact.CachedSecondaryTangentImpulse == Fixed64.Zero)
 378        {
 956379            return true;
 380        }
 381
 22382        return TryApplyContactImpulseCombination(
 22383            pair,
 22384            contact,
 22385            responsePositionA,
 22386            responsePositionB,
 22387            contact.Normal,
 22388            contact.CachedNormalImpulse,
 22389            contact.Tangent,
 22390            contact.CachedTangentImpulse,
 22391            contact.SecondaryTangent,
 22392            contact.CachedSecondaryTangentImpulse);
 393    }
 394
 395    private static bool TryApplyContactImpulseCombination(
 396        CollisionPair pair,
 397        SolverContact contact,
 398        Vector3d responsePositionA,
 399        Vector3d responsePositionB,
 400        Vector3d firstAxis,
 401        Fixed64 firstScale,
 402        Vector3d secondAxis,
 403        Fixed64 secondScale,
 404        Vector3d thirdAxis,
 405        Fixed64 thirdScale)
 406    {
 6173407        if (ContactResponseArithmetic3D.TryLinearCombination(
 6173408                firstAxis,
 6173409                firstScale,
 6173410                secondAxis,
 6173411                secondScale,
 6173412                thirdAxis,
 6173413                thirdScale,
 6173414                out Vector3d impulse)
 6173415            && CanNegate(impulse))
 416        {
 6168417            return TryApplyContactImpulse(
 6168418                pair,
 6168419                contact,
 6168420                responsePositionA,
 6168421                responsePositionB,
 6168422                impulse);
 423        }
 424
 5425        return TryApplyContactImpulseCombinationExact(
 5426            pair,
 5427            contact,
 5428            responsePositionA,
 5429            responsePositionB,
 5430            firstAxis,
 5431            firstScale,
 5432            secondAxis,
 5433            secondScale,
 5434            thirdAxis,
 5435            thirdScale);
 436    }
 437
 438    private static bool TryApplyContactImpulse(
 439        CollisionPair pair,
 440        SolverContact contact,
 441        Vector3d responsePositionA,
 442        Vector3d responsePositionB,
 443        Vector3d impulse)
 444    {
 6168445        if (!contact.RelativeA.IsExact
 6168446            && !contact.RelativeB.IsExact
 6168447            && TryApplyCompactImpulse(contact, impulse))
 448        {
 6120449            return true;
 450        }
 451
 48452        return TryApplyContactImpulseExact(
 48453            pair,
 48454            contact,
 48455            responsePositionA,
 48456            responsePositionB,
 48457            impulse);
 458    }
 459
 460    [MethodImpl(MethodImplOptions.NoInlining)]
 461    private static bool TryApplyContactImpulseExact(
 462        CollisionPair pair,
 463        SolverContact contact,
 464        Vector3d responsePositionA,
 465        Vector3d responsePositionB,
 466        Vector3d impulse)
 467    {
 48468        GetExactLevers(
 48469            pair,
 48470            contact,
 48471            responsePositionA,
 48472            responsePositionB,
 48473            out ExactLever3D exactA,
 48474            out ExactLever3D exactB);
 48475        return TryApplyExactImpulse(
 48476            contact,
 48477            exactA,
 48478            exactB,
 48479            impulse);
 480    }
 481
 482    [MethodImpl(MethodImplOptions.NoInlining)]
 483    private static bool TryApplyContactImpulseCombinationExact(
 484        CollisionPair pair,
 485        SolverContact contact,
 486        Vector3d responsePositionA,
 487        Vector3d responsePositionB,
 488        Vector3d firstAxis,
 489        Fixed64 firstScale,
 490        Vector3d secondAxis,
 491        Fixed64 secondScale,
 492        Vector3d thirdAxis,
 493        Fixed64 thirdScale)
 494    {
 5495        GetExactLevers(
 5496            pair,
 5497            contact,
 5498            responsePositionA,
 5499            responsePositionB,
 5500            out ExactLever3D exactA,
 5501            out ExactLever3D exactB);
 5502        if (!ExactContactLever3D
 5503                .TryGetImpulseCombinationVelocityDeltas(
 5504                    contact.A.Body,
 5505                    exactA,
 5506                    contact.B.Body,
 5507                    exactB,
 5508                    firstAxis,
 5509                    firstScale,
 5510                    secondAxis,
 5511                    secondScale,
 5512                    thirdAxis,
 5513                    thirdScale,
 5514                    out Vector3d linearA,
 5515                    out Vector3d angularA,
 5516                    out Vector3d linearB,
 5517                    out Vector3d angularB))
 518        {
 3519            return false;
 520        }
 521
 2522        return TryApplyVelocityDeltas(
 2523            contact,
 2524            linearA,
 2525            angularA,
 2526            linearB,
 2527            angularB);
 528    }
 529
 530    private static bool TryApplyNormalImpulse(
 531        CollisionPair pair,
 532        SolverContact contact,
 533        ContactNormalImpulseResult3D result)
 534    {
 9075535        if (result.LinearVelocityDeltaA == Vector3d.Zero
 9075536            && result.AngularVelocityDeltaA == Vector3d.Zero
 9075537            && result.LinearVelocityDeltaB == Vector3d.Zero
 9075538            && result.AngularVelocityDeltaB == Vector3d.Zero)
 539        {
 2243540            return true;
 541        }
 542
 6832543        if (!TryPrepareVelocityStates(
 6832544                contact,
 6832545                result.LinearVelocityDeltaA,
 6832546                result.AngularVelocityDeltaA,
 6832547                result.LinearVelocityDeltaB,
 6832548                result.AngularVelocityDeltaB,
 6832549                out Vector3d linearA,
 6832550                out Vector3d angularA,
 6832551                out Vector3d linearB,
 6832552                out Vector3d angularB))
 553        {
 1554            return false;
 555        }
 556
 6831557        if (result.HasRepresentableAppliedImpulse
 6831558            && result.HasRepresentableNormalVelocity
 6831559            && result.AppliedImpulseScalar != Fixed64.Zero)
 560        {
 6826561            Vector3d impulse =
 6826562                contact.Normal * result.AppliedImpulseScalar;
 6826563            pair.Context.Diagnostics.EmitResponseImpulse(
 6826564                pair,
 6826565                impulse,
 6826566                result.NormalVelocity);
 567        }
 6831568        ApplyVelocityStates(
 6831569            contact,
 6831570            linearA,
 6831571            angularA,
 6831572            linearB,
 6831573            angularB);
 6831574        return true;
 575    }
 576
 577    private static bool TrySolveFrictionImpulse(
 578        CollisionPair pair,
 579        SolverContact contact,
 580        Vector3d responsePositionA,
 581        Vector3d responsePositionB,
 582        Fixed64 normalImpulseScalar,
 583        out Fixed64 tangentImpulse,
 584        out Fixed64 secondaryTangentImpulse)
 585    {
 9074586        if (!contact.RelativeA.IsExact
 9074587            && !contact.RelativeB.IsExact
 9074588            && TryGetCompactFrictionResponse(
 9074589                contact,
 9074590                normalImpulseScalar,
 9074591                out tangentImpulse,
 9074592                out secondaryTangentImpulse,
 9074593                out Fixed64 tangentDelta,
 9074594                out Fixed64 secondaryTangentDelta)
 9074595            && ((tangentDelta == Fixed64.Zero
 9074596                    && secondaryTangentDelta == Fixed64.Zero)
 9074597                || TryApplyContactImpulseCombination(
 9074598                    pair,
 9074599                    contact,
 9074600                    responsePositionA,
 9074601                    responsePositionB,
 9074602                    contact.Normal,
 9074603                    Fixed64.Zero,
 9074604                    contact.Tangent,
 9074605                    tangentDelta,
 9074606                    contact.SecondaryTangent,
 9074607                    secondaryTangentDelta)))
 608        {
 8690609            return true;
 610        }
 611
 384612        return TrySolveFrictionImpulseExact(
 384613            pair,
 384614            contact,
 384615            responsePositionA,
 384616            responsePositionB,
 384617            normalImpulseScalar,
 384618            out tangentImpulse,
 384619            out secondaryTangentImpulse);
 620    }
 621
 622    private static bool TryGetCompactFrictionResponse(
 623        SolverContact contact,
 624        Fixed64 normalImpulseScalar,
 625        out Fixed64 tangentImpulse,
 626        out Fixed64 secondaryTangentImpulse,
 627        out Fixed64 tangentDelta,
 628        out Fixed64 secondaryTangentDelta)
 629    {
 9042630        tangentImpulse = default;
 9042631        secondaryTangentImpulse = default;
 9042632        tangentDelta = default;
 9042633        secondaryTangentDelta = default;
 9042634        bool limitsResolved = TryGetFrictionLimit(
 9042635            normalImpulseScalar,
 9042636            contact.StaticFriction,
 9042637            out Fixed64 staticFrictionLimit);
 9042638        limitsResolved &= TryGetFrictionLimit(
 9042639            normalImpulseScalar,
 9042640            contact.DynamicFriction,
 9042641            out Fixed64 dynamicFrictionLimit);
 9042642        if (!limitsResolved)
 2643            return false;
 644
 9040645        if (staticFrictionLimit == Fixed64.Zero
 9040646            && dynamicFrictionLimit == Fixed64.Zero)
 647        {
 2207648            return Fixed64.TrySubtract(
 2207649                    Fixed64.Zero,
 2207650                    contact.CachedTangentImpulse,
 2207651                    out tangentDelta)
 2207652                & Fixed64.TrySubtract(
 2207653                    Fixed64.Zero,
 2207654                    contact.CachedSecondaryTangentImpulse,
 2207655                    out secondaryTangentDelta);
 656        }
 657
 6833658        Vector3d linearA = ResolveLinearVelocity(contact.A.Body);
 6833659        Vector3d angularA = ResolveAngularVelocity(contact.A.Body);
 6833660        Vector3d linearB = ResolveLinearVelocity(contact.B.Body);
 6833661        Vector3d angularB = ResolveAngularVelocity(contact.B.Body);
 6833662        if (!ContactResponseArithmetic3D.TryGetRelativePointVelocity(
 6833663                linearA,
 6833664                angularA,
 6833665                contact.RelativeA.Vector,
 6833666                linearB,
 6833667                angularB,
 6833668                contact.RelativeB.Vector,
 6833669                contact.Tangent,
 6833670                out Vector3d relativeVelocity))
 671        {
 338672            return false;
 673        }
 674
 6495675        bool deltasResolved = TryGetCompactTangentImpulseDelta(
 6495676            contact,
 6495677            relativeVelocity,
 6495678            contact.Tangent,
 6495679            out Fixed64 desiredTangentDelta);
 6495680        deltasResolved &= TryGetCompactTangentImpulseDelta(
 6495681            contact,
 6495682            relativeVelocity,
 6495683            contact.SecondaryTangent,
 6495684            out Fixed64 desiredSecondaryTangentDelta);
 6495685        bool diskResolved = Fixed64.TryAdd(
 6495686                contact.CachedTangentImpulse,
 6495687                desiredTangentDelta,
 6495688                out tangentImpulse)
 6495689            & Fixed64.TryAdd(
 6495690                contact.CachedSecondaryTangentImpulse,
 6495691                desiredSecondaryTangentDelta,
 6495692                out secondaryTangentImpulse)
 6495693            & TryGetMagnitudeSquared(
 6495694                tangentImpulse,
 6495695                secondaryTangentImpulse,
 6495696                out Fixed64 desiredMagnitudeSquared)
 6495697            & TryGetSquare(
 6495698                staticFrictionLimit,
 6495699                out Fixed64 staticLimitSquared);
 6495700        if (!(deltasResolved & diskResolved))
 701        {
 9702            return false;
 703        }
 704
 6486705        if (desiredMagnitudeSquared > staticLimitSquared)
 706        {
 4358707            Fixed64 magnitude = FixedMath.Sqrt(desiredMagnitudeSquared);
 4358708            Fixed64 scale = dynamicFrictionLimit / magnitude;
 4358709            if (dynamicFrictionLimit != Fixed64.Zero
 4358710                && scale == Fixed64.Zero)
 711            {
 1712                return false;
 713            }
 714
 4357715            Fixed64 desiredTangentImpulse = tangentImpulse;
 4357716            Fixed64 desiredSecondaryTangentImpulse =
 4357717                secondaryTangentImpulse;
 4357718            tangentImpulse *= scale;
 4357719            secondaryTangentImpulse *= scale;
 4357720            if (dynamicFrictionLimit != Fixed64.Zero
 4357721                && ((desiredTangentImpulse != Fixed64.Zero
 4357722                        && tangentImpulse == Fixed64.Zero)
 4357723                    || (desiredSecondaryTangentImpulse != Fixed64.Zero
 4357724                        && secondaryTangentImpulse == Fixed64.Zero)))
 725            {
 1726                return false;
 727            }
 728        }
 729
 6484730        return Fixed64.TrySubtract(
 6484731                tangentImpulse,
 6484732                contact.CachedTangentImpulse,
 6484733                out tangentDelta)
 6484734            & Fixed64.TrySubtract(
 6484735                secondaryTangentImpulse,
 6484736                contact.CachedSecondaryTangentImpulse,
 6484737                out secondaryTangentDelta);
 738    }
 739
 740    [MethodImpl(MethodImplOptions.NoInlining)]
 741    private static bool TrySolveFrictionImpulseExact(
 742        CollisionPair pair,
 743        SolverContact contact,
 744        Vector3d responsePositionA,
 745        Vector3d responsePositionB,
 746        Fixed64 normalImpulseScalar,
 747        out Fixed64 tangentImpulse,
 748        out Fixed64 secondaryTangentImpulse)
 749    {
 384750        GetExactLevers(
 384751            pair,
 384752            contact,
 384753            responsePositionA,
 384754            responsePositionB,
 384755            out ExactLever3D exactA,
 384756            out ExactLever3D exactB);
 384757        Vector3d linearA = ResolveLinearVelocity(contact.A.Body);
 384758        Vector3d angularA = ResolveAngularVelocity(contact.A.Body);
 384759        Vector3d linearB = ResolveLinearVelocity(contact.B.Body);
 384760        Vector3d angularB = ResolveAngularVelocity(contact.B.Body);
 384761        ExactContactResponseOperand3D primaryFirst =
 384762            ExactContactLever3D.CreateResponseOperand(
 384763                contact.A.Body,
 384764                linearA,
 384765                angularA,
 384766                exactA,
 384767                -contact.Tangent);
 384768        ExactContactResponseOperand3D primarySecond =
 384769            ExactContactLever3D.CreateResponseOperand(
 384770                contact.B.Body,
 384771                linearB,
 384772                angularB,
 384773                exactB,
 384774                contact.Tangent);
 384775        ExactContactResponseOperand3D secondaryFirst =
 384776            ExactContactLever3D.CreateResponseOperand(
 384777                contact.A.Body,
 384778                linearA,
 384779                angularA,
 384780                exactA,
 384781                -contact.SecondaryTangent);
 384782        ExactContactResponseOperand3D secondarySecond =
 384783            ExactContactLever3D.CreateResponseOperand(
 384784                contact.B.Body,
 384785                linearB,
 384786                angularB,
 384787                exactB,
 384788                contact.SecondaryTangent);
 384789        if (!ExactContactResponseKernel.TryGetCoulombDiskResponse(
 384790                contact.Normal,
 384791                normalImpulseScalar,
 384792                primaryFirst,
 384793                primarySecond,
 384794                contact.Tangent,
 384795                contact.CachedTangentImpulse,
 384796                secondaryFirst,
 384797                secondarySecond,
 384798                contact.SecondaryTangent,
 384799                contact.CachedSecondaryTangentImpulse,
 384800                contact.StaticFriction,
 384801                contact.DynamicFriction,
 384802                out ExactCoulombResponse3D response))
 803        {
 1804            tangentImpulse = default;
 1805            secondaryTangentImpulse = default;
 1806            return false;
 807        }
 808
 383809        _ = response.TryGetPrimaryAccumulatedImpulse(out tangentImpulse);
 383810        _ = response.TryGetSecondaryAccumulatedImpulse(
 383811            out secondaryTangentImpulse);
 383812        return !response.HasAppliedImpulse
 383813            || TryApplyVelocityDeltas(
 383814                contact,
 383815                response.FirstLinearVelocityDelta,
 383816                response.FirstAngularVelocityDelta,
 383817                response.SecondLinearVelocityDelta,
 383818                response.SecondAngularVelocityDelta);
 819    }
 820
 821    private static bool TryGetCompactTangentImpulseDelta(
 822        SolverContact contact,
 823        Vector3d relativeVelocity,
 824        Vector3d tangent,
 825        out Fixed64 impulseDelta)
 826    {
 12990827        impulseDelta = Fixed64.Zero;
 12990828        if (!ContactResponseArithmetic3D.TryDot(
 12990829                relativeVelocity,
 12990830                tangent,
 12990831                out Fixed64 tangentVelocity))
 832        {
 1833            return false;
 834        }
 835
 12989836        if (tangentVelocity >= -Fixed64.Epsilon
 12989837            && tangentVelocity <= Fixed64.Epsilon)
 838        {
 6768839            return true;
 840        }
 841
 6221842        bool denominatorsResolved =
 6221843            ContactNormalImpulse3D.TryComputeAngularDenominator(
 6221844                contact.A.Body,
 6221845                contact.RelativeA.Vector,
 6221846                tangent,
 6221847                out Fixed64 angularA);
 6221848        denominatorsResolved &=
 6221849            ContactNormalImpulse3D.TryComputeAngularDenominator(
 6221850                contact.B.Body,
 6221851                contact.RelativeB.Vector,
 6221852                tangent,
 6221853                out Fixed64 angularB);
 6221854        denominatorsResolved &= TryGetCompactConstrainedInverseMass(
 6221855            contact.A,
 6221856            tangent,
 6221857            out Fixed64 linearA);
 6221858        denominatorsResolved &= TryGetCompactConstrainedInverseMass(
 6221859            contact.B,
 6221860            tangent,
 6221861            out Fixed64 linearB);
 6221862        var denominatorTerms = new ContactEffectiveMassTerms3D(
 6221863            linearA,
 6221864            linearB,
 6221865            angularA,
 6221866            angularB);
 6221867        bool denominatorResolved = denominatorsResolved
 6221868            & denominatorTerms.TryGetValue(out Fixed64 denominator);
 6221869        if (!denominatorResolved)
 870        {
 2871            return false;
 872        }
 873
 6219874        if (denominator <= Fixed64.Epsilon)
 1875            return true;
 876
 6218877        return Fixed64.TryMultiplyDivide(
 6218878                tangentVelocity,
 6218879                -Fixed64.One,
 6218880                denominator,
 6218881                out impulseDelta)
 6218882            && impulseDelta != Fixed64.Zero;
 883    }
 884
 885    private static bool TryGetCompactConstrainedInverseMass(
 886        ResponseBody body,
 887        Vector3d axis,
 888        out Fixed64 inverseMass)
 889    {
 12442890        inverseMass = body.GetConstrainedInverseMass(axis);
 12442891        return inverseMass != Fixed64.Zero
 12442892            || body.InverseMass == Fixed64.Zero
 12442893            || body.Body.ProjectLinearMotion(axis) == Vector3d.Zero;
 894    }
 895
 896    private static bool TryGetFrictionLimit(
 897        Fixed64 normalImpulse,
 898        Fixed64 friction,
 899        out Fixed64 limit)
 900    {
 18084901        if (normalImpulse <= Fixed64.Zero || friction <= Fixed64.Zero)
 902        {
 4415903            limit = Fixed64.Zero;
 4415904            return true;
 905        }
 906
 13669907        return Fixed64.TryMultiplyDivide(
 13669908                normalImpulse,
 13669909                friction,
 13669910                Fixed64.One,
 13669911                out limit)
 13669912            && limit != Fixed64.Zero;
 913    }
 914
 915    private static bool TryGetMagnitudeSquared(
 916        Fixed64 first,
 917        Fixed64 second,
 918        out Fixed64 result)
 919    {
 6495920        bool resolved = TryGetSquare(first, out Fixed64 firstSquared)
 6495921            & TryGetSquare(second, out Fixed64 secondSquared);
 6495922        if (!resolved)
 923        {
 3924            result = default;
 3925            return false;
 926        }
 927
 6492928        return Fixed64.TryAdd(
 6492929            firstSquared,
 6492930            secondSquared,
 6492931            out result);
 932    }
 933
 934    private static bool TryGetSquare(Fixed64 value, out Fixed64 square) =>
 19485935        Fixed64.TryMultiplyDivide(
 19485936            value,
 19485937            value,
 19485938            Fixed64.One,
 19485939            out square)
 19485940        && (value == Fixed64.Zero || square != Fixed64.Zero);
 941
 942    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 943    private static Vector3d ResolveLinearVelocity(SolidBody body) =>
 35294944        body.ProjectLinearMotion(
 35294945            body.IsKinematic
 35294946                ? body.SampleContinuousCollisionLinearVelocity(Fixed64.One)
 35294947                : body.LinearVelocity);
 948
 949    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 950    private static Vector3d ResolveAngularVelocity(SolidBody body) =>
 35294951        body.ProjectAngularMotion(
 35294952            body.IsKinematic
 35294953                ? body.SampleContinuousCollisionAngularVelocity(Fixed64.One)
 35294954                : body.AngularVelocity);
 955
 956    private static bool TryApplyExactImpulse(
 957        SolverContact contact,
 958        in ExactLever3D exactA,
 959        in ExactLever3D exactB,
 960        Vector3d impulseB)
 961    {
 48962        Vector3d impulseA = -impulseB;
 48963        bool linearAResolved = TryGetLinearVelocityDelta(
 48964            contact.A,
 48965            impulseA,
 48966            out Vector3d linearA);
 48967        bool angularAResolved =
 48968            ExactContactLever3D.TryGetAngularVelocityDelta(
 48969            contact.A.Body,
 48970            exactA,
 48971            impulseA,
 48972            out Vector3d angularA);
 48973        bool linearBResolved = TryGetLinearVelocityDelta(
 48974            contact.B,
 48975            impulseB,
 48976            out Vector3d linearB);
 48977        bool angularBResolved =
 48978            ExactContactLever3D.TryGetAngularVelocityDelta(
 48979            contact.B.Body,
 48980            exactB,
 48981            impulseB,
 48982            out Vector3d angularB);
 48983        if (!(linearAResolved
 48984            & angularAResolved
 48985            & linearBResolved
 48986            & angularBResolved))
 987        {
 4988            return false;
 989        }
 990
 44991        return TryApplyVelocityDeltas(
 44992            contact,
 44993            linearA,
 44994            angularA,
 44995            linearB,
 44996            angularB);
 997    }
 998
 999    private static bool TryApplyCompactImpulse(
 1000        SolverContact contact,
 1001        Vector3d impulseB)
 1002    {
 61651003        Vector3d impulseA = -impulseB;
 61651004        bool linearAResolved = TryGetLinearVelocityDelta(
 61651005            contact.A,
 61651006            impulseA,
 61651007            out Vector3d linearA);
 61651008        bool angularAResolved = TryGetAngularVelocityDelta(
 61651009            contact.A,
 61651010            contact.RelativeA.Vector,
 61651011            impulseA,
 61651012            out Vector3d angularA);
 61651013        bool linearBResolved = TryGetLinearVelocityDelta(
 61651014            contact.B,
 61651015            impulseB,
 61651016            out Vector3d linearB);
 61651017        bool angularBResolved = TryGetAngularVelocityDelta(
 61651018            contact.B,
 61651019            contact.RelativeB.Vector,
 61651020            impulseB,
 61651021            out Vector3d angularB);
 61651022        if (!(linearAResolved
 61651023            & angularAResolved
 61651024            & linearBResolved
 61651025            & angularBResolved))
 1026        {
 451027            return false;
 1028        }
 1029
 61201030        return TryApplyVelocityDeltas(
 61201031            contact,
 61201032            linearA,
 61201033            angularA,
 61201034            linearB,
 61201035            angularB);
 1036    }
 1037
 1038    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1039    private static bool CanNegate(Vector3d value) =>
 61691040        value.X != Fixed64.MinValue
 61691041        && value.Y != Fixed64.MinValue
 61691042        && value.Z != Fixed64.MinValue;
 1043
 1044    private static bool TryGetLinearVelocityDelta(
 1045        ResponseBody body,
 1046        Vector3d impulse,
 1047        out Vector3d velocityDelta)
 1048    {
 124261049        if (!body.HasSolverMobility || !body.Body.CanTranslate)
 1050        {
 301051            velocityDelta = Vector3d.Zero;
 301052            return true;
 1053        }
 1054
 123961055        return ContinuousCollisionImpulsePolicy.TryResolveVelocityDelta(
 123961056            body.Body.ProjectLinearMotion(impulse),
 123961057            Fixed64.One,
 123961058            body.InverseMass,
 123961059            Fixed64.One,
 123961060            out velocityDelta);
 1061    }
 1062
 1063    private static bool TryGetAngularVelocityDelta(
 1064        ResponseBody body,
 1065        Vector3d relativeContactPoint,
 1066        Vector3d impulse,
 1067        out Vector3d velocityDelta)
 1068    {
 123301069        velocityDelta = Vector3d.Zero;
 123301070        if (!body.HasSolverMobility || !body.CanRotate)
 411071            return true;
 1072
 122891073        Fixed3x3 inverseInertia =
 122891074            body.Body.GetConstrainedInverseInertiaTensor();
 122891075        if (ContactResponseArithmetic3D.CanUseFastAngularResponse(
 122891076                relativeContactPoint,
 122891077                impulse,
 122891078                inverseInertia))
 1079        {
 122851080            Vector3d fastTorqueAxis =
 122851081                Vector3d.Cross(relativeContactPoint, impulse);
 122851082            velocityDelta = Fixed3x3.TransformDirection(
 122851083                inverseInertia,
 122851084                fastTorqueAxis);
 122851085            return ContactResponseArithmetic3D
 122851086                    .PreservesNonzeroCrossProduct(
 122851087                        relativeContactPoint,
 122851088                        impulse,
 122851089                        fastTorqueAxis)
 122851090                && ContactResponseArithmetic3D
 122851091                    .PreservesNonzeroTransformDirection(
 122851092                        inverseInertia,
 122851093                        fastTorqueAxis,
 122851094                        velocityDelta);
 1095        }
 1096
 41097        if (!ContactResponseArithmetic3D.TryCross(
 41098                relativeContactPoint,
 41099                impulse,
 41100                out Vector3d torqueAxis))
 1101        {
 11102            return false;
 1103        }
 1104
 31105        return ContactResponseArithmetic3D.TryTransformDirection(
 31106            inverseInertia,
 31107            torqueAxis,
 31108            out velocityDelta);
 1109    }
 1110
 1111    private static bool TryPrepareVelocityStates(
 1112        SolverContact contact,
 1113        Vector3d linearA,
 1114        Vector3d angularA,
 1115        Vector3d linearB,
 1116        Vector3d angularB,
 1117        out Vector3d preparedLinearA,
 1118        out Vector3d preparedAngularA,
 1119        out Vector3d preparedLinearB,
 1120        out Vector3d preparedAngularB)
 1121    {
 133501122        bool firstPrepared =
 133501123            contact.A.Body.TryPrepareCollisionVelocityState(
 133501124                linearA,
 133501125                angularA,
 133501126                out preparedLinearA,
 133501127                out preparedAngularA);
 133501128        bool secondPrepared =
 133501129            contact.B.Body.TryPrepareCollisionVelocityState(
 133501130                linearB,
 133501131                angularB,
 133501132                out preparedLinearB,
 133501133                out preparedAngularB);
 133501134        return firstPrepared & secondPrepared;
 1135    }
 1136
 1137    private static bool TryApplyVelocityDeltas(
 1138        SolverContact contact,
 1139        Vector3d linearA,
 1140        Vector3d angularA,
 1141        Vector3d linearB,
 1142        Vector3d angularB)
 1143    {
 65181144        if (!TryPrepareVelocityStates(
 65181145                contact,
 65181146                linearA,
 65181147                angularA,
 65181148                linearB,
 65181149                angularB,
 65181150                out Vector3d preparedLinearA,
 65181151                out Vector3d preparedAngularA,
 65181152                out Vector3d preparedLinearB,
 65181153                out Vector3d preparedAngularB))
 1154        {
 11155            return false;
 1156        }
 1157
 65171158        ApplyVelocityStates(
 65171159            contact,
 65171160            preparedLinearA,
 65171161            preparedAngularA,
 65171162            preparedLinearB,
 65171163            preparedAngularB);
 65171164        return true;
 1165    }
 1166
 1167    private static void ApplyVelocityStates(
 1168        SolverContact contact,
 1169        Vector3d linearA,
 1170        Vector3d angularA,
 1171        Vector3d linearB,
 1172        Vector3d angularB)
 1173    {
 133481174        contact.A.Body.ApplyCollisionVelocityState(linearA, angularA);
 133481175        contact.B.Body.ApplyCollisionVelocityState(linearB, angularB);
 133481176    }
 1177
 1178    private static void GetExactLevers(
 1179        CollisionPair pair,
 1180        SolverContact contact,
 1181        Vector3d responsePositionA,
 1182        Vector3d responsePositionB,
 1183        out ExactLever3D exactA,
 1184        out ExactLever3D exactB)
 1185    {
 18241186        ManifoldContact manifoldContact =
 18241187            pair.Manifold[contact.ManifoldIndex];
 18241188        var centerA = new ContactAnchor(
 18241189            responsePositionA,
 18241190            contact.A.Body.Rotation,
 18241191            contact.A.Body.LocalCenterOfMassOffset);
 18241192        var centerB = new ContactAnchor(
 18241193            responsePositionB,
 18241194            contact.B.Body.Rotation,
 18241195            contact.B.Body.LocalCenterOfMassOffset);
 18241196        exactA = manifoldContact.AnchorA.GetLeverFrom(centerA);
 18241197        exactB = manifoldContact.AnchorB.GetLeverFrom(centerB);
 18241198    }
 1199
 1200    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1201    private static bool HasFailedResponse(byte failedResponseMask, int contactIndex) =>
 272311202        (failedResponseMask & (1 << contactIndex)) != 0;
 1203
 1204    private static void RejectResponse(
 1205        CollisionPair pair,
 1206        SolverContact contact,
 1207        int contactIndex,
 1208        ref byte failedResponseMask)
 1209    {
 41210        failedResponseMask |= (byte)(1 << contactIndex);
 41211        ClearWarmStartImpulse(pair, contact);
 41212        GravitasLogger.Channel.Error(
 41213            $"Contact response is outside the representable velocity domain.");
 41214    }
 1215
 1216    private static void ClearWarmStartImpulse(
 1217        CollisionPair pair,
 1218        SolverContact contact) =>
 111219        pair.RemoveWarmStartImpulse(contact.ContactId);
 1220
 1221    private static bool IsWarmStartCompatible(Vector3d cachedNormal, Vector3d normal) =>
 80531222        Vector3d.Dot(cachedNormal, normal) >= WarmStartNormalCompatibilityThreshold;
 1223
 1224    private static Vector3d ResolveContactNormal(Vector3d normal, Vector3d fallbackDirection)
 1225    {
 90851226        Vector3d resolved = normal.MagnitudeSquared > Fixed64.Epsilon
 90851227            ? normal.Normalized
 90851228            : fallbackDirection.MagnitudeSquared > Fixed64.Epsilon
 90851229                ? fallbackDirection.Normalized
 90851230                : Vector3d.Zero;
 1231
 90851232        if (resolved == Vector3d.Zero)
 11233            return resolved;
 1234
 90841235        return fallbackDirection.MagnitudeSquared > Fixed64.Epsilon
 90841236            && Vector3d.Dot(resolved, fallbackDirection) < Fixed64.Zero
 90841237                ? -resolved
 90841238                : resolved;
 1239    }
 1240}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/3D/ContactNormalImpulse3D.cs

#LineLine coverage
 1//=======================================================================
 2// ContactNormalImpulse3D.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 System.Runtime.CompilerServices;
 11
 12namespace Gravitas.CollisionHandling;
 13
 14/// <summary>
 15/// Side-effect-free 3D contact-normal impulse result for two participants.
 16/// </summary>
 17internal readonly struct ContactNormalImpulseResult3D
 18{
 19    public ContactNormalImpulseResult3D(
 20        Fixed64 normalVelocity,
 21        Fixed64 impulseScalar,
 22        Vector3d linearVelocityDeltaA,
 23        Vector3d angularVelocityDeltaA,
 24        Vector3d linearVelocityDeltaB,
 25        Vector3d angularVelocityDeltaB)
 769326        : this(
 769327            normalVelocity,
 769328            impulseScalar,
 769329            impulseScalar,
 769330            linearVelocityDeltaA,
 769331            angularVelocityDeltaA,
 769332            linearVelocityDeltaB,
 769333            angularVelocityDeltaB)
 34    {
 769335    }
 36
 37    public ContactNormalImpulseResult3D(
 38        Fixed64 normalVelocity,
 39        Fixed64 impulseScalar,
 40        Fixed64 appliedImpulseScalar,
 41        Vector3d linearVelocityDeltaA,
 42        Vector3d angularVelocityDeltaA,
 43        Vector3d linearVelocityDeltaB,
 44        Vector3d angularVelocityDeltaB,
 45        bool hasRepresentableNormalVelocity = true,
 46        bool hasRepresentableAppliedImpulse = true)
 47    {
 908248        NormalVelocity = normalVelocity;
 908249        ImpulseScalar = impulseScalar;
 908250        AppliedImpulseScalar = appliedImpulseScalar;
 908251        LinearVelocityDeltaA = linearVelocityDeltaA;
 908252        AngularVelocityDeltaA = angularVelocityDeltaA;
 908253        LinearVelocityDeltaB = linearVelocityDeltaB;
 908254        AngularVelocityDeltaB = angularVelocityDeltaB;
 908255        HasRepresentableNormalVelocity = hasRepresentableNormalVelocity;
 908256        HasRepresentableAppliedImpulse = hasRepresentableAppliedImpulse;
 908257    }
 58
 59    public Fixed64 NormalVelocity { get; }
 60
 61    public Fixed64 ImpulseScalar { get; }
 62
 63    public Fixed64 AppliedImpulseScalar { get; }
 64
 65    public bool HasRepresentableNormalVelocity { get; }
 66
 67    public bool HasRepresentableAppliedImpulse { get; }
 68
 69    public Vector3d LinearVelocityDeltaA { get; }
 70
 71    public Vector3d AngularVelocityDeltaA { get; }
 72
 73    public Vector3d LinearVelocityDeltaB { get; }
 74
 75    public Vector3d AngularVelocityDeltaB { get; }
 76}
 77
 78/// <summary>
 79/// Side-effect-free 3D velocity deltas for a normal response whose impulse
 80/// scalar does not need to be representable.
 81/// </summary>
 82internal readonly struct ContactNormalVelocityDeltaResult3D
 83{
 84    public ContactNormalVelocityDeltaResult3D(
 85        Fixed64 normalVelocity,
 86        Vector3d linearVelocityDeltaA,
 87        Vector3d angularVelocityDeltaA,
 88        Vector3d linearVelocityDeltaB,
 89        Vector3d angularVelocityDeltaB)
 2990        : this(
 2991            normalVelocity,
 2992            linearVelocityDeltaA,
 2993            angularVelocityDeltaA,
 2994            linearVelocityDeltaB,
 2995            angularVelocityDeltaB,
 2996            normalVelocity < Fixed64.Zero,
 2997            hasRepresentableNormalVelocity: true)
 98    {
 2999    }
 100
 101    public ContactNormalVelocityDeltaResult3D(
 102        Fixed64 normalVelocity,
 103        Vector3d linearVelocityDeltaA,
 104        Vector3d angularVelocityDeltaA,
 105        Vector3d linearVelocityDeltaB,
 106        Vector3d angularVelocityDeltaB,
 107        bool isClosing,
 108        bool hasRepresentableNormalVelocity)
 109    {
 81110        NormalVelocity = normalVelocity;
 81111        LinearVelocityDeltaA = linearVelocityDeltaA;
 81112        AngularVelocityDeltaA = angularVelocityDeltaA;
 81113        LinearVelocityDeltaB = linearVelocityDeltaB;
 81114        AngularVelocityDeltaB = angularVelocityDeltaB;
 81115        IsClosing = isClosing;
 81116        HasRepresentableNormalVelocity = hasRepresentableNormalVelocity;
 81117    }
 118
 119    public Fixed64 NormalVelocity { get; }
 120
 121    public bool IsClosing { get; }
 122
 123    public bool HasRepresentableNormalVelocity { get; }
 124
 125    public Vector3d LinearVelocityDeltaA { get; }
 126
 127    public Vector3d AngularVelocityDeltaA { get; }
 128
 129    public Vector3d LinearVelocityDeltaB { get; }
 130
 131    public Vector3d AngularVelocityDeltaB { get; }
 132}
 133
 134internal readonly struct ContactEffectiveMassTerms3D
 135{
 136    internal ContactEffectiveMassTerms3D(
 137        Fixed64 linearA,
 138        Fixed64 linearB,
 139        Fixed64 angularA,
 140        Fixed64 angularB)
 141    {
 13977142        LinearA = linearA;
 13977143        LinearB = linearB;
 13977144        AngularA = angularA;
 13977145        AngularB = angularB;
 13977146    }
 147
 148    internal Fixed64 LinearA { get; }
 149
 150    internal Fixed64 LinearB { get; }
 151
 152    internal Fixed64 AngularA { get; }
 153
 154    internal Fixed64 AngularB { get; }
 155
 156    internal Fixed64 SaturatedSum =>
 7729157        LinearA + LinearB + AngularA + AngularB;
 158
 159    internal bool TryGetValue(out Fixed64 value)
 160    {
 6246161        bool resolved =
 6246162            Fixed64.TryAdd(LinearA, LinearB, out Fixed64 linear);
 6246163        resolved &= Fixed64.TryAdd(
 6246164            linear,
 6246165            AngularA,
 6246166            out Fixed64 first);
 6246167        resolved &= Fixed64.TryAdd(first, AngularB, out value);
 6246168        if (!resolved)
 2169            value = default;
 6246170        return resolved;
 171    }
 172}
 173
 174/// <summary>
 175/// Calculates allocation-free 3D contact-point normal response without mutating either body.
 176/// </summary>
 177internal static class ContactNormalImpulse3D
 178{
 179    internal static bool TryCalculateVelocityDeltas(
 180        SolidBody? bodyA,
 181        Vector3d linearVelocityA,
 182        Vector3d angularVelocityA,
 183        Vector3d relativeContactPointA,
 184        SolidBody? bodyB,
 185        Vector3d linearVelocityB,
 186        Vector3d angularVelocityB,
 187        Vector3d relativeContactPointB,
 188        Vector3d normal,
 189        Fixed64 restitution,
 190        Fixed64 restitutionVelocityThreshold,
 191        out ContactNormalVelocityDeltaResult3D result)
 192    {
 84193        result = default;
 84194        if (!TryComputeNormalVelocity(
 84195                linearVelocityA,
 84196                angularVelocityA,
 84197                relativeContactPointA,
 84198                linearVelocityB,
 84199                angularVelocityB,
 84200                relativeContactPointB,
 84201                normal,
 84202                out Fixed64 normalVelocity))
 203        {
 4204            return false;
 205        }
 206
 80207        if (normalVelocity >= Fixed64.Zero)
 208        {
 3209            result = ZeroVelocityDelta(normalVelocity);
 3210            return true;
 211        }
 212
 77213        if (!TryComputeDenominator(
 77214                bodyA,
 77215                relativeContactPointA,
 77216                bodyB,
 77217                relativeContactPointB,
 77218                normal,
 77219                out ContactEffectiveMassTerms3D denominator)
 77220            || denominator.SaturatedSum <= Fixed64.Zero)
 221        {
 49222            return false;
 223        }
 224
 28225        Fixed64 appliedRestitution = normalVelocity < -restitutionVelocityThreshold
 28226            ? restitution
 28227            : Fixed64.Zero;
 28228        Fixed64 responseFactor = -(Fixed64.One + appliedRestitution);
 28229        bool linearAResolved = TryResolveVelocityDelta(
 28230            bodyA?.ProjectLinearMotion(-normal) ?? Vector3d.Zero,
 28231            normalVelocity,
 28232            responseFactor,
 28233            bodyA?.EffectiveInverseMass ?? Fixed64.Zero,
 28234            denominator,
 28235            out Vector3d linearVelocityDeltaA);
 28236        bool angularAResolved = TryResolveAngularVelocityDelta(
 28237                bodyA,
 28238                relativeContactPointA,
 28239                -normal,
 28240                normalVelocity,
 28241                responseFactor,
 28242                denominator,
 28243                out Vector3d angularVelocityDeltaA);
 28244        bool linearBResolved = TryResolveVelocityDelta(
 28245            bodyB?.ProjectLinearMotion(normal) ?? Vector3d.Zero,
 28246            normalVelocity,
 28247            responseFactor,
 28248            bodyB?.EffectiveInverseMass ?? Fixed64.Zero,
 28249            denominator,
 28250            out Vector3d linearVelocityDeltaB);
 28251        bool angularBResolved = TryResolveAngularVelocityDelta(
 28252                bodyB,
 28253                relativeContactPointB,
 28254                normal,
 28255                normalVelocity,
 28256                responseFactor,
 28257                denominator,
 28258                out Vector3d angularVelocityDeltaB);
 28259        if (!(linearAResolved
 28260                & angularAResolved
 28261                & linearBResolved
 28262                & angularBResolved))
 263        {
 2264            return false;
 265        }
 266
 26267        result = new ContactNormalVelocityDeltaResult3D(
 26268            normalVelocity,
 26269            linearVelocityDeltaA,
 26270            angularVelocityDeltaA,
 26271            linearVelocityDeltaB,
 26272            angularVelocityDeltaB);
 26273        return true;
 274    }
 275
 276    internal static bool TryCalculateVelocityDeltasExact(
 277        SolidBody? bodyA,
 278        Vector3d linearVelocityA,
 279        Vector3d angularVelocityA,
 280        in ExactLever3D relativeContactPointA,
 281        SolidBody? bodyB,
 282        Vector3d linearVelocityB,
 283        Vector3d angularVelocityB,
 284        in ExactLever3D relativeContactPointB,
 285        Vector3d normal,
 286        Fixed64 restitution,
 287        Fixed64 restitutionVelocityThreshold,
 288        out ContactNormalVelocityDeltaResult3D result)
 289    {
 57290        result = default;
 57291        if (!ExactContactLever3D.TryGetNormalResponse(
 57292                bodyA,
 57293                linearVelocityA,
 57294                angularVelocityA,
 57295                relativeContactPointA,
 57296                bodyB,
 57297                linearVelocityB,
 57298                angularVelocityB,
 57299                relativeContactPointB,
 57300                normal,
 57301                restitution,
 57302                restitutionVelocityThreshold,
 57303                out ExactNormalResponse3D response))
 304        {
 5305            return false;
 306        }
 307
 52308        bool hasNormalVelocity =
 52309            response.TryGetNormalVelocity(out Fixed64 normalVelocity);
 52310        result = new ContactNormalVelocityDeltaResult3D(
 52311            normalVelocity,
 52312            response.FirstLinearVelocityDelta,
 52313            response.FirstAngularVelocityDelta,
 52314            response.SecondLinearVelocityDelta,
 52315            response.SecondAngularVelocityDelta,
 52316            response.IsClosing,
 52317            hasNormalVelocity);
 52318        return true;
 319    }
 320
 321    internal static ContactNormalImpulseResult3D CalculateAccumulatedDelta(
 322        SolidBody? bodyA,
 323        Vector3d linearVelocityA,
 324        Vector3d angularVelocityA,
 325        Vector3d relativeContactPointA,
 326        SolidBody? bodyB,
 327        Vector3d linearVelocityB,
 328        Vector3d angularVelocityB,
 329        Vector3d relativeContactPointB,
 330        Vector3d normal,
 331        Fixed64 restitution,
 332        Fixed64 restitutionVelocityThreshold,
 333        Fixed64 accumulatedImpulse,
 334        Fixed64 positiveImpulseScale,
 335        Fixed64 negativeImpulseScale)
 336    {
 1337        _ = TryCalculateAccumulatedDelta(
 1338            bodyA,
 1339            linearVelocityA,
 1340            angularVelocityA,
 1341            relativeContactPointA,
 1342            bodyB,
 1343            linearVelocityB,
 1344            angularVelocityB,
 1345            relativeContactPointB,
 1346            normal,
 1347            restitution,
 1348            restitutionVelocityThreshold,
 1349            accumulatedImpulse,
 1350            positiveImpulseScale,
 1351            negativeImpulseScale,
 1352            out ContactNormalImpulseResult3D result);
 1353        return result;
 354    }
 355
 356    internal static bool TryCalculateAccumulatedDelta(
 357        SolidBody? bodyA,
 358        Vector3d linearVelocityA,
 359        Vector3d angularVelocityA,
 360        Vector3d relativeContactPointA,
 361        SolidBody? bodyB,
 362        Vector3d linearVelocityB,
 363        Vector3d angularVelocityB,
 364        Vector3d relativeContactPointB,
 365        Vector3d normal,
 366        Fixed64 restitution,
 367        Fixed64 restitutionVelocityThreshold,
 368        Fixed64 accumulatedImpulse,
 369        Fixed64 positiveImpulseScale,
 370        Fixed64 negativeImpulseScale,
 371        out ContactNormalImpulseResult3D result)
 372    {
 9052373        result = default;
 9052374        if (!TryComputeNormalVelocity(
 9052375                linearVelocityA,
 9052376                angularVelocityA,
 9052377                relativeContactPointA,
 9052378                linearVelocityB,
 9052379                angularVelocityB,
 9052380                relativeContactPointB,
 9052381                normal,
 9052382                out Fixed64 normalVelocity)
 9052383            || !TryComputeDenominator(
 9052384                bodyA,
 9052385                relativeContactPointA,
 9052386                bodyB,
 9052387                relativeContactPointB,
 9052388                normal,
 9052389                out ContactEffectiveMassTerms3D denominator))
 390        {
 1353391            return false;
 392        }
 393
 7699394        if (denominator.SaturatedSum <= Fixed64.Zero)
 395        {
 2396            result = Zero(normalVelocity);
 2397            return true;
 398        }
 399
 7697400        if (!TryCalculateAccumulatedImpulseDelta(
 7697401                normalVelocity,
 7697402                denominator,
 7697403                restitution,
 7697404                restitutionVelocityThreshold,
 7697405                accumulatedImpulse,
 7697406                positiveImpulseScale,
 7697407                negativeImpulseScale,
 7697408                out Fixed64 impulseScalar))
 409        {
 5410            return false;
 411        }
 412
 7692413        if (impulseScalar == Fixed64.Zero)
 414        {
 1875415            result = Zero(normalVelocity);
 1875416            return true;
 417        }
 418
 5817419        Vector3d impulseB = normal * impulseScalar;
 5817420        Vector3d impulseA = -impulseB;
 5817421        bool linearAResolved = TryComputeLinearVelocityDelta(
 5817422            bodyA,
 5817423            impulseA,
 5817424            out Vector3d linearVelocityDeltaA);
 5817425        bool angularAResolved = TryComputeAngularVelocityDelta(
 5817426            bodyA,
 5817427            relativeContactPointA,
 5817428            impulseA,
 5817429            out Vector3d angularVelocityDeltaA);
 5817430        bool linearBResolved = TryComputeLinearVelocityDelta(
 5817431            bodyB,
 5817432            impulseB,
 5817433            out Vector3d linearVelocityDeltaB);
 5817434        bool angularBResolved = TryComputeAngularVelocityDelta(
 5817435            bodyB,
 5817436            relativeContactPointB,
 5817437            impulseB,
 5817438            out Vector3d angularVelocityDeltaB);
 5817439        if (!(linearAResolved
 5817440            & angularAResolved
 5817441            & linearBResolved
 5817442            & angularBResolved))
 443        {
 1444            return false;
 445        }
 446
 5816447        result = new ContactNormalImpulseResult3D(
 5816448            normalVelocity,
 5816449            impulseScalar,
 5816450            linearVelocityDeltaA,
 5816451            angularVelocityDeltaA,
 5816452            linearVelocityDeltaB,
 5816453            angularVelocityDeltaB);
 5816454        return true;
 455    }
 456
 457    internal static bool TryCalculateAccumulatedDeltaExact(
 458        SolidBody? bodyA,
 459        Vector3d linearVelocityA,
 460        Vector3d angularVelocityA,
 461        in ExactLever3D relativeContactPointA,
 462        SolidBody? bodyB,
 463        Vector3d linearVelocityB,
 464        Vector3d angularVelocityB,
 465        in ExactLever3D relativeContactPointB,
 466        Vector3d normal,
 467        Fixed64 restitution,
 468        Fixed64 restitutionVelocityThreshold,
 469        Fixed64 accumulatedImpulse,
 470        Fixed64 positiveImpulseScale,
 471        Fixed64 negativeImpulseScale,
 472        out ContactNormalImpulseResult3D result)
 473    {
 1394474        result = default;
 1394475        if (!ExactContactLever3D.TryGetAccumulatedNormalResponse(
 1394476                bodyA,
 1394477                linearVelocityA,
 1394478                angularVelocityA,
 1394479                relativeContactPointA,
 1394480                bodyB,
 1394481                linearVelocityB,
 1394482                angularVelocityB,
 1394483                relativeContactPointB,
 1394484                normal,
 1394485                restitution,
 1394486                restitutionVelocityThreshold,
 1394487                accumulatedImpulse,
 1394488                positiveImpulseScale,
 1394489                negativeImpulseScale,
 1394490                out ExactNormalResponse3D response))
 491        {
 5492            return false;
 493        }
 494
 1389495        bool hasNormalVelocity =
 1389496            response.TryGetNormalVelocity(out Fixed64 normalVelocity);
 1389497        bool hasAppliedImpulse =
 1389498            response.TryGetAppliedImpulse(out Fixed64 appliedImpulse);
 499        Fixed64 impulseScalar;
 1389500        if (response.TryGetAccumulatedImpulse(
 1389501                out Fixed64 newAccumulatedImpulse))
 502        {
 503            // Both values are nonnegative, so their difference is always in
 504            // [-Fixed64.MaxValue, Fixed64.MaxValue].
 1385505            impulseScalar = newAccumulatedImpulse - accumulatedImpulse;
 506        }
 507        else
 508        {
 4509            impulseScalar = -accumulatedImpulse;
 510        }
 511
 1389512        result = new ContactNormalImpulseResult3D(
 1389513            normalVelocity,
 1389514            impulseScalar,
 1389515            appliedImpulse,
 1389516            response.FirstLinearVelocityDelta,
 1389517            response.FirstAngularVelocityDelta,
 1389518            response.SecondLinearVelocityDelta,
 1389519            response.SecondAngularVelocityDelta,
 1389520            hasNormalVelocity,
 1389521            hasAppliedImpulse);
 1389522        return true;
 523    }
 524
 525    private static bool TryCalculateAccumulatedImpulseDelta(
 526        Fixed64 normalVelocity,
 527        in ContactEffectiveMassTerms3D denominator,
 528        Fixed64 restitution,
 529        Fixed64 restitutionVelocityThreshold,
 530        Fixed64 accumulatedImpulse,
 531        Fixed64 positiveImpulseScale,
 532        Fixed64 negativeImpulseScale,
 533        out Fixed64 impulseDelta)
 534    {
 7697535        Fixed64 appliedRestitution =
 7697536            normalVelocity < -restitutionVelocityThreshold
 7697537                ? restitution
 7697538                : Fixed64.Zero;
 7697539        Fixed64 responseFactor = -(Fixed64.One + appliedRestitution);
 7697540        Fixed64 impulseScale = normalVelocity < Fixed64.Zero
 7697541            ? positiveImpulseScale
 7697542            : negativeImpulseScale;
 7697543        if (!Fixed64.TryMultiplyDivideBySum(
 7697544                normalVelocity,
 7697545                responseFactor,
 7697546                impulseScale,
 7697547                Fixed64.One,
 7697548                denominator.LinearA,
 7697549                denominator.LinearB,
 7697550                denominator.AngularA,
 7697551                denominator.AngularB,
 7697552                out Fixed64 scaledImpulse))
 553        {
 3554            impulseDelta = default;
 3555            return normalVelocity >= Fixed64.Zero
 3556                && responseFactor <= Fixed64.Zero
 3557                && impulseScale >= Fixed64.Zero
 3558                && accumulatedImpulse >= Fixed64.Zero
 3559                && Fixed64.TrySubtract(
 3560                    Fixed64.Zero,
 3561                    accumulatedImpulse,
 3562                    out impulseDelta);
 563        }
 564
 7694565        if (!Fixed64.TryAdd(
 7694566                accumulatedImpulse,
 7694567                scaledImpulse,
 7694568                out Fixed64 accumulated))
 569        {
 1570            impulseDelta = default;
 1571            return false;
 572        }
 573
 7693574        return Fixed64.TrySubtract(
 7693575            FixedMath.Max(Fixed64.Zero, accumulated),
 7693576            accumulatedImpulse,
 7693577            out impulseDelta);
 578    }
 579
 580    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 581    private static Fixed64 GetConstrainedInverseMass(SolidBody? body, Vector3d axis) =>
 15458582        body?.GetConstrainedInverseMass(axis) ?? Fixed64.Zero;
 583
 584    internal static bool TryComputeNormalVelocity(
 585        Vector3d linearVelocityA,
 586        Vector3d angularVelocityA,
 587        Vector3d relativeContactPointA,
 588        Vector3d linearVelocityB,
 589        Vector3d angularVelocityB,
 590        Vector3d relativeContactPointB,
 591        Vector3d normal,
 592        out Fixed64 normalVelocity)
 593    {
 9455594        if (ContactResponseArithmetic3D.CanUseFastPointVelocity(
 9455595                linearVelocityA,
 9455596                angularVelocityA,
 9455597                relativeContactPointA,
 9455598                linearVelocityB,
 9455599                angularVelocityB,
 9455600                relativeContactPointB,
 9455601                normal))
 602        {
 9425603            Vector3d fastPointVelocityA = linearVelocityA
 9425604                + Vector3d.Cross(
 9425605                    angularVelocityA,
 9425606                    relativeContactPointA);
 9425607            Vector3d fastPointVelocityB = linearVelocityB
 9425608                + Vector3d.Cross(
 9425609                    angularVelocityB,
 9425610                    relativeContactPointB);
 9425611            normalVelocity = Vector3d.Dot(
 9425612                fastPointVelocityB - fastPointVelocityA,
 9425613                normal);
 9425614            return true;
 615        }
 616
 30617        bool resolved = ContactResponseArithmetic3D.TryCross(
 30618                angularVelocityA,
 30619                relativeContactPointA,
 30620                out Vector3d angularA)
 30621            & ContactResponseArithmetic3D.TryCross(
 30622                angularVelocityB,
 30623                relativeContactPointB,
 30624                out Vector3d angularB);
 30625        if (!resolved
 30626            || !Vector3d.TryAdd(
 30627                linearVelocityA,
 30628                angularA,
 30629                out Vector3d pointVelocityA)
 30630            || !Vector3d.TryAdd(
 30631                linearVelocityB,
 30632                angularB,
 30633                out Vector3d pointVelocityB)
 30634            || !Vector3d.TrySubtract(
 30635                pointVelocityB,
 30636                pointVelocityA,
 30637                out Vector3d relativeVelocity))
 638        {
 10639            normalVelocity = default;
 10640            return false;
 641        }
 642
 20643        return ContactResponseArithmetic3D.TryDot(
 20644            relativeVelocity,
 20645            normal,
 20646            out normalVelocity);
 647    }
 648
 649    private static bool TryComputeDenominator(
 650        SolidBody? bodyA,
 651        Vector3d relativeContactPointA,
 652        SolidBody? bodyB,
 653        Vector3d relativeContactPointB,
 654        Vector3d normal,
 655        out ContactEffectiveMassTerms3D denominator)
 656    {
 9125657        denominator = default;
 9125658        bool angularAResolved = TryComputeAngularDenominator(
 9125659            bodyA,
 9125660            relativeContactPointA,
 9125661            normal,
 9125662            out Fixed64 angularA);
 9125663        bool angularBResolved = TryComputeAngularDenominator(
 9125664            bodyB,
 9125665            relativeContactPointB,
 9125666            normal,
 9125667            out Fixed64 angularB);
 9125668        if (!(angularAResolved & angularBResolved))
 669        {
 1396670            return false;
 671        }
 672
 7729673        denominator = new ContactEffectiveMassTerms3D(
 7729674            GetConstrainedInverseMass(bodyA, normal),
 7729675            GetConstrainedInverseMass(bodyB, normal),
 7729676            angularA,
 7729677            angularB);
 7729678        return true;
 679    }
 680
 681    internal static bool TryComputeAngularDenominator(
 682        SolidBody? body,
 683        Vector3d relativeContactPoint,
 684        Vector3d axis,
 685        out Fixed64 denominator)
 686    {
 31035687        denominator = Fixed64.Zero;
 31035688        if (body?.CanRotate != true)
 864689            return true;
 690
 30171691        Fixed3x3 inverseInertia =
 30171692            body.GetConstrainedInverseInertiaTensor();
 30171693        if (ContactResponseArithmetic3D.CanUseFastAngularResponse(
 30171694                relativeContactPoint,
 30171695                axis,
 30171696                inverseInertia))
 697        {
 30115698            Vector3d fastTorqueAxis =
 30115699                Vector3d.Cross(relativeContactPoint, axis);
 30115700            Vector3d fastAngularVelocityDelta =
 30115701                Fixed3x3.TransformDirection(
 30115702                    inverseInertia,
 30115703                    fastTorqueAxis);
 30115704            Vector3d fastAngular = Vector3d.Cross(
 30115705                fastAngularVelocityDelta,
 30115706                relativeContactPoint);
 30115707            denominator = Vector3d.Dot(fastAngular, axis);
 30115708            bool responsePreserved =
 30115709                ContactResponseArithmetic3D.PreservesNonzeroCrossProduct(
 30115710                    relativeContactPoint,
 30115711                    axis,
 30115712                    fastTorqueAxis);
 30115713            responsePreserved &=
 30115714                ContactResponseArithmetic3D
 30115715                .PreservesNonzeroTransformDirection(
 30115716                    inverseInertia,
 30115717                    fastTorqueAxis,
 30115718                    fastAngularVelocityDelta);
 30115719            responsePreserved &=
 30115720                ContactResponseArithmetic3D
 30115721                .PreservesNonzeroCrossProduct(
 30115722                    fastAngularVelocityDelta,
 30115723                    relativeContactPoint,
 30115724                    fastAngular);
 30115725            responsePreserved &=
 30115726                ContactResponseArithmetic3D.PreservesNonzeroDotProduct(
 30115727                    fastAngular,
 30115728                    axis,
 30115729                    denominator);
 30115730            if (!responsePreserved)
 731            {
 1412732                denominator = default;
 1412733                return false;
 734            }
 735
 28703736            denominator = FixedMath.Max(denominator, Fixed64.Zero);
 28703737            return true;
 738        }
 739
 56740        if (!ContactResponseArithmetic3D.TryCross(
 56741                relativeContactPoint,
 56742                axis,
 56743                out Vector3d torqueAxis)
 56744            || !ContactResponseArithmetic3D.TryTransformDirection(
 56745                inverseInertia,
 56746                torqueAxis,
 56747                out Vector3d angularVelocityDelta)
 56748            || !ContactResponseArithmetic3D.TryCross(
 56749                angularVelocityDelta,
 56750                relativeContactPoint,
 56751                out Vector3d angular)
 56752            || !ContactResponseArithmetic3D.TryDot(
 56753                angular,
 56754                axis,
 56755                out denominator))
 756        {
 47757            denominator = default;
 47758            return false;
 759        }
 760
 9761        denominator = FixedMath.Max(denominator, Fixed64.Zero);
 9762        return true;
 763    }
 764
 765    internal static bool TryComputeLinearVelocityDelta(
 766        SolidBody? body,
 767        Vector3d impulse,
 768        out Vector3d velocityDelta)
 769    {
 11742770        if (body != null)
 11738771            impulse = body.ProjectLinearMotion(impulse);
 772
 11742773        if (!ContactResponseArithmetic3D.TryScale(
 11742774                impulse,
 11742775                body?.EffectiveInverseMass ?? Fixed64.Zero,
 11742776                out velocityDelta))
 777        {
 2778            return false;
 779        }
 780
 11740781        return true;
 782    }
 783
 784    internal static bool TryComputeAngularVelocityDelta(
 785        SolidBody? body,
 786        Vector3d relativeContactPoint,
 787        Vector3d impulse,
 788        out Vector3d velocityDelta)
 789    {
 11783790        velocityDelta = Vector3d.Zero;
 11783791        if (body?.CanRotate != true)
 519792            return true;
 793
 11264794        Fixed3x3 inverseInertia =
 11264795            body.GetConstrainedInverseInertiaTensor();
 11264796        if (ContactResponseArithmetic3D.CanUseFastAngularResponse(
 11264797                relativeContactPoint,
 11264798                impulse,
 11264799                inverseInertia))
 800        {
 11257801            if (!ContactResponseArithmetic3D.TryCross(
 11257802                    relativeContactPoint,
 11257803                    impulse,
 11257804                    out Vector3d torqueAxis))
 1805                return false;
 806
 11256807            velocityDelta = Fixed3x3.TransformDirection(
 11256808                inverseInertia,
 11256809                torqueAxis);
 11256810            return ContactResponseArithmetic3D
 11256811                .PreservesNonzeroTransformDirection(
 11256812                    inverseInertia,
 11256813                    torqueAxis,
 11256814                    velocityDelta);
 815        }
 816
 7817        bool torqueResolved = ContactResponseArithmetic3D.TryCross(
 7818                relativeContactPoint,
 7819                impulse,
 7820                out Vector3d checkedTorqueAxis);
 7821        bool transformResolved =
 7822            ContactResponseArithmetic3D.TryTransformDirection(
 7823                inverseInertia,
 7824                checkedTorqueAxis,
 7825                out velocityDelta);
 7826        return torqueResolved & transformResolved;
 827    }
 828
 829    private static bool TryResolveAngularVelocityDelta(
 830        SolidBody? body,
 831        Vector3d relativeContactPoint,
 832        Vector3d signedNormal,
 833        Fixed64 normalVelocity,
 834        Fixed64 responseFactor,
 835        in ContactEffectiveMassTerms3D denominator,
 836        out Vector3d velocityDelta)
 837    {
 56838        velocityDelta = Vector3d.Zero;
 56839        if (body?.CanRotate != true)
 16840            return true;
 841
 40842        bool responseResolved = TryComputeAngularVelocityDelta(
 40843            body,
 40844            relativeContactPoint,
 40845            signedNormal,
 40846            out Vector3d response);
 40847        bool deltaResolved = TryResolveVelocityDelta(
 40848            response,
 40849            normalVelocity,
 40850            responseFactor,
 40851            Fixed64.One,
 40852            denominator,
 40853            out velocityDelta);
 40854        return responseResolved & deltaResolved;
 855    }
 856
 857    private static bool TryResolveVelocityDelta(
 858        Vector3d response,
 859        Fixed64 firstMultiplier,
 860        Fixed64 secondMultiplier,
 861        Fixed64 thirdMultiplier,
 862        in ContactEffectiveMassTerms3D denominator,
 863        out Vector3d velocityDelta)
 864    {
 96865        bool xResolved = Fixed64.TryMultiplyDivideBySum(
 96866            response.X,
 96867            firstMultiplier,
 96868            secondMultiplier,
 96869            thirdMultiplier,
 96870            denominator.LinearA,
 96871            denominator.LinearB,
 96872            denominator.AngularA,
 96873            denominator.AngularB,
 96874            out Fixed64 x);
 96875        bool yResolved = Fixed64.TryMultiplyDivideBySum(
 96876            response.Y,
 96877            firstMultiplier,
 96878            secondMultiplier,
 96879            thirdMultiplier,
 96880            denominator.LinearA,
 96881            denominator.LinearB,
 96882            denominator.AngularA,
 96883            denominator.AngularB,
 96884            out Fixed64 y);
 96885        bool zResolved = Fixed64.TryMultiplyDivideBySum(
 96886            response.Z,
 96887            firstMultiplier,
 96888            secondMultiplier,
 96889            thirdMultiplier,
 96890            denominator.LinearA,
 96891            denominator.LinearB,
 96892            denominator.AngularA,
 96893            denominator.AngularB,
 96894            out Fixed64 z);
 96895        velocityDelta = xResolved & yResolved & zResolved
 96896            ? new Vector3d(x, y, z)
 96897            : default;
 96898        return xResolved & yResolved & zResolved;
 899    }
 900
 901    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 902    private static ContactNormalImpulseResult3D Zero(Fixed64 normalVelocity) =>
 1877903        new(
 1877904            normalVelocity,
 1877905            Fixed64.Zero,
 1877906            Vector3d.Zero,
 1877907            Vector3d.Zero,
 1877908            Vector3d.Zero,
 1877909            Vector3d.Zero);
 910
 911    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 912    private static ContactNormalVelocityDeltaResult3D ZeroVelocityDelta(Fixed64 normalVelocity) =>
 3913        new(
 3914            normalVelocity,
 3915            Vector3d.Zero,
 3916            Vector3d.Zero,
 3917            Vector3d.Zero,
 3918            Vector3d.Zero);
 919}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/3D/ContactResponseArithmetic3D.cs

#LineLine coverage
 1//=======================================================================
 2// ContactResponseArithmetic3D.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 System.Runtime.CompilerServices;
 11
 12namespace Gravitas.CollisionHandling;
 13
 14/// <summary>
 15/// Preserves the existing compact response arithmetic when a conservative
 16/// operand bound proves every intermediate representable, otherwise delegates
 17/// to FixedMathSharp's checked full-domain operations.
 18/// </summary>
 19internal static class ContactResponseArithmetic3D
 20{
 21    // For Q32.32 raw magnitudes below 2^n, one product lands below
 22    // 2^(2n-32). The 46/40/37 bounds leave sign-and-sum headroom for,
 23    // respectively, three products, the point-velocity chain, and the
 24    // cross/matrix/cross/dot angular-response chain.
 25    private const int SafeProductMagnitudeShift = 46;
 26    private const int SafePointVelocityMagnitudeShift = 40;
 27    private const int SafeAngularResponseMagnitudeShift = 37;
 28
 29    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 30    internal static bool CanUseFastPointVelocity(
 31        Vector3d linearA,
 32        Vector3d angularA,
 33        Vector3d leverA,
 34        Vector3d linearB,
 35        Vector3d angularB,
 36        Vector3d leverB,
 37        Vector3d axis) =>
 1882538        IsSafeMagnitude(
 1882539            GetAggregateMagnitude(linearA)
 1882540            | GetAggregateMagnitude(angularA)
 1882541            | GetAggregateMagnitude(leverA)
 1882542            | GetAggregateMagnitude(linearB)
 1882543            | GetAggregateMagnitude(angularB)
 1882544            | GetAggregateMagnitude(leverB)
 1882545            | GetAggregateMagnitude(axis),
 1882546            SafePointVelocityMagnitudeShift);
 47
 48    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 49    internal static bool CanUseFastAngularResponse(
 50        Vector3d lever,
 51        Vector3d vector,
 52        Fixed3x3 inverseInertia) =>
 5844053        IsSafeMagnitude(
 5844054            GetAggregateMagnitude(lever)
 5844055            | GetAggregateMagnitude(vector)
 5844056            | GetAggregateMagnitude(inverseInertia),
 5844057            SafeAngularResponseMagnitudeShift);
 58
 59    internal static bool TryGetRelativePointVelocity(
 60        Vector3d linearA,
 61        Vector3d angularA,
 62        Vector3d leverA,
 63        Vector3d linearB,
 64        Vector3d angularB,
 65        Vector3d leverB,
 66        Vector3d axis,
 67        out Vector3d relativeVelocity)
 68    {
 697569        if (CanUseFastPointVelocity(
 697570                linearA,
 697571                angularA,
 697572                leverA,
 697573                linearB,
 697574                angularB,
 697575                leverB,
 697576                axis))
 77        {
 696778            Vector3d fastAngularVelocityA =
 696779                Vector3d.Cross(angularA, leverA);
 696780            Vector3d fastAngularVelocityB =
 696781                Vector3d.Cross(angularB, leverB);
 696782            if (!PreservesNonzeroCrossProduct(
 696783                    angularA,
 696784                    leverA,
 696785                    fastAngularVelocityA)
 696786                || !PreservesNonzeroCrossProduct(
 696787                    angularB,
 696788                    leverB,
 696789                    fastAngularVelocityB))
 90            {
 33791                relativeVelocity = default;
 33792                return false;
 93            }
 94
 663095            Vector3d fastPointVelocityA =
 663096                linearA + fastAngularVelocityA;
 663097            Vector3d fastPointVelocityB =
 663098                linearB + fastAngularVelocityB;
 663099            relativeVelocity =
 6630100                fastPointVelocityB - fastPointVelocityA;
 6630101            return true;
 102        }
 103
 8104        bool firstCrossResolved = TryCross(
 8105                angularA,
 8106                leverA,
 8107                out Vector3d angularVelocityA);
 8108        bool secondCrossResolved = TryCross(
 8109                angularB,
 8110                leverB,
 8111                out Vector3d angularVelocityB);
 8112        bool pointVelocitiesResolved = firstCrossResolved
 8113            & secondCrossResolved
 8114            & Vector3d.TryAdd(
 8115                linearA,
 8116                angularVelocityA,
 8117                out Vector3d pointVelocityA)
 8118            & Vector3d.TryAdd(
 8119                linearB,
 8120                angularVelocityB,
 8121                out Vector3d pointVelocityB);
 8122        if (!pointVelocitiesResolved)
 123        {
 2124            relativeVelocity = default;
 2125            return false;
 126        }
 127
 6128        return Vector3d.TrySubtract(
 6129            pointVelocityB,
 6130            pointVelocityA,
 6131            out relativeVelocity);
 132    }
 133
 134    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 135    internal static bool TryCross(
 136        Vector3d left,
 137        Vector3d right,
 138        out Vector3d result)
 139    {
 15017140        if (HasSafeProductInputs(left, right))
 141        {
 14979142            result = Vector3d.Cross(left, right);
 14979143            return PreservesNonzeroCrossProduct(
 14979144                left,
 14979145                right,
 14979146                result);
 147        }
 148
 38149        return Vector3d.TryCross(left, right, out result)
 38150            && PreservesNonzeroCrossProduct(left, right, result);
 151    }
 152
 153    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 154    internal static bool TryDot(
 155        Vector3d left,
 156        Vector3d right,
 157        out Fixed64 result)
 158    {
 14214159        if (HasSafeProductInputs(left, right))
 160        {
 14188161            result = Vector3d.Dot(left, right);
 14188162            return PreservesNonzeroDotProduct(
 14188163                left,
 14188164                right,
 14188165                result);
 166        }
 167
 26168        return Vector3d.TryDot(left, right, out result)
 26169            && PreservesNonzeroDotProduct(left, right, result);
 170    }
 171
 172    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 173    internal static bool TryTransformDirection(
 174        Fixed3x3 matrix,
 175        Vector3d direction,
 176        out Vector3d result)
 177    {
 26178        if (HasSafeProductInputs(matrix, direction))
 179        {
 15180            result = Fixed3x3.TransformDirection(matrix, direction);
 15181            return PreservesNonzeroTransformDirection(
 15182                matrix,
 15183                direction,
 15184                result);
 185        }
 186
 11187        return Fixed3x3.TryTransformDirection(
 11188            matrix,
 11189            direction,
 11190            out result)
 11191            && PreservesNonzeroTransformDirection(matrix, direction, result);
 192    }
 193
 194    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 195    internal static bool TryLinearCombination(
 196        Vector3d first,
 197        Fixed64 firstScale,
 198        Vector3d second,
 199        Fixed64 secondScale,
 200        Vector3d third,
 201        Fixed64 thirdScale,
 202        out Vector3d result)
 203    {
 6257204        if (HasSafeProductInputs(
 6257205                first,
 6257206                firstScale,
 6257207                second,
 6257208                secondScale,
 6257209                third,
 6257210                thirdScale))
 211        {
 6249212            result = first * firstScale
 6249213                + second * secondScale
 6249214                + third * thirdScale;
 6249215            return PreservesNonzeroLinearCombination(
 6249216                first,
 6249217                firstScale,
 6249218                second,
 6249219                secondScale,
 6249220                third,
 6249221                thirdScale,
 6249222                result);
 223        }
 224
 8225        return Vector3d.TryLinearCombination(
 8226            first,
 8227            firstScale,
 8228            second,
 8229            secondScale,
 8230            third,
 8231            thirdScale,
 8232            out result)
 8233            && PreservesNonzeroLinearCombination(
 8234                first,
 8235                firstScale,
 8236                second,
 8237                secondScale,
 8238                third,
 8239                thirdScale,
 8240                result);
 241    }
 242
 243    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 244    internal static bool TryScale(
 245        Vector3d value,
 246        Fixed64 scale,
 247        out Vector3d result)
 248    {
 11832249        if (HasSafeProductInputs(value, scale))
 250        {
 11821251            result = value * scale;
 11821252            return true;
 253        }
 254
 11255        bool resolved = Fixed64.TryMultiplyDivide(
 11256                value.X,
 11257                scale,
 11258                Fixed64.One,
 11259                out Fixed64 x)
 11260            & Fixed64.TryMultiplyDivide(
 11261                value.Y,
 11262                scale,
 11263                Fixed64.One,
 11264                out Fixed64 y)
 11265            & Fixed64.TryMultiplyDivide(
 11266                value.Z,
 11267                scale,
 11268                Fixed64.One,
 11269                out Fixed64 z);
 11270        result = resolved ? new Vector3d(x, y, z) : default;
 11271        return resolved;
 272    }
 273
 274    internal static bool PreservesNonzeroCrossProduct(
 275        Vector3d left,
 276        Vector3d right,
 277        Vector3d result)
 278    {
 101318279        bool inspectX = result.X == Fixed64.Zero
 101318280            && HasNonzeroDifferenceTerm(
 101318281                left.Y,
 101318282                right.Z,
 101318283                left.Z,
 101318284                right.Y);
 101318285        bool inspectY = result.Y == Fixed64.Zero
 101318286            && HasNonzeroDifferenceTerm(
 101318287                left.Z,
 101318288                right.X,
 101318289                left.X,
 101318290                right.Z);
 101318291        bool inspectZ = result.Z == Fixed64.Zero
 101318292            && HasNonzeroDifferenceTerm(
 101318293                left.X,
 101318294                right.Y,
 101318295                left.Y,
 101318296                right.X);
 101318297        if (!(inspectX | inspectY | inspectZ))
 99440298            return true;
 299
 1878300        WideGeometry.GetDifferenceCrossProduct3D(
 1878301            left.X, Fixed64.Zero,
 1878302            left.Y, Fixed64.Zero,
 1878303            left.Z, Fixed64.Zero,
 1878304            right.X, Fixed64.Zero,
 1878305            right.Y, Fixed64.Zero,
 1878306            right.Z, Fixed64.Zero,
 1878307            out Signed192 exactX,
 1878308            out Signed192 exactY,
 1878309            out Signed192 exactZ);
 1878310        return PreservesExactValue(inspectX, exactX)
 1878311            & PreservesExactValue(inspectY, exactY)
 1878312            & PreservesExactValue(inspectZ, exactZ);
 313    }
 314
 315    internal static bool PreservesNonzeroDotProduct(
 316        Vector3d left,
 317        Vector3d right,
 318        Fixed64 result)
 319    {
 44326320        if (result != Fixed64.Zero
 44326321            || !HasNonzeroTerm(
 44326322                left.X,
 44326323                right.X,
 44326324                left.Y,
 44326325                right.Y,
 44326326                left.Z,
 44326327                right.Z))
 328        {
 44322329            return true;
 330        }
 331
 4332        return GetLinearCombinationComponent(
 4333            left.X,
 4334            right.X,
 4335            left.Y,
 4336            right.Y,
 4337            left.Z,
 4338            right.Z).IsZero;
 339    }
 340
 341    internal static bool PreservesNonzeroTransformDirection(
 342        Fixed3x3 matrix,
 343        Vector3d direction,
 344        Vector3d result)
 345    {
 53616346        bool inspectX = result.X == Fixed64.Zero
 53616347            && HasNonzeroTerm(
 53616348                direction.X,
 53616349                matrix.M11,
 53616350                direction.Y,
 53616351                matrix.M21,
 53616352                direction.Z,
 53616353                matrix.M31);
 53616354        bool inspectY = result.Y == Fixed64.Zero
 53616355            && HasNonzeroTerm(
 53616356                direction.X,
 53616357                matrix.M12,
 53616358                direction.Y,
 53616359                matrix.M22,
 53616360                direction.Z,
 53616361                matrix.M32);
 53616362        bool inspectZ = result.Z == Fixed64.Zero
 53616363            && HasNonzeroTerm(
 53616364                direction.X,
 53616365                matrix.M13,
 53616366                direction.Y,
 53616367                matrix.M23,
 53616368                direction.Z,
 53616369                matrix.M33);
 53616370        if (!(inspectX | inspectY | inspectZ))
 53615371            return true;
 372
 1373        Signed192 exactX = GetLinearCombinationComponent(
 1374            direction.X,
 1375            matrix.M11,
 1376            direction.Y,
 1377            matrix.M21,
 1378            direction.Z,
 1379            matrix.M31);
 1380        Signed192 exactY = GetLinearCombinationComponent(
 1381            direction.X,
 1382            matrix.M12,
 1383            direction.Y,
 1384            matrix.M22,
 1385            direction.Z,
 1386            matrix.M32);
 1387        Signed192 exactZ = GetLinearCombinationComponent(
 1388            direction.X,
 1389            matrix.M13,
 1390            direction.Y,
 1391            matrix.M23,
 1392            direction.Z,
 1393            matrix.M33);
 1394        return PreservesExactValue(inspectX, exactX)
 1395            & PreservesExactValue(inspectY, exactY)
 1396            & PreservesExactValue(inspectZ, exactZ);
 397    }
 398
 399    private static bool PreservesNonzeroLinearCombination(
 400        Vector3d first,
 401        Fixed64 firstScale,
 402        Vector3d second,
 403        Fixed64 secondScale,
 404        Vector3d third,
 405        Fixed64 thirdScale,
 406        Vector3d result)
 407    {
 6254408        bool inspectX = result.X == Fixed64.Zero
 6254409            && HasNonzeroTerm(
 6254410                first.X,
 6254411                firstScale,
 6254412                second.X,
 6254413                secondScale,
 6254414                third.X,
 6254415                thirdScale);
 6254416        bool inspectY = result.Y == Fixed64.Zero
 6254417            && HasNonzeroTerm(
 6254418                first.Y,
 6254419                firstScale,
 6254420                second.Y,
 6254421                secondScale,
 6254422                third.Y,
 6254423                thirdScale);
 6254424        bool inspectZ = result.Z == Fixed64.Zero
 6254425            && HasNonzeroTerm(
 6254426                first.Z,
 6254427                firstScale,
 6254428                second.Z,
 6254429                secondScale,
 6254430                third.Z,
 6254431                thirdScale);
 6254432        if (!(inspectX | inspectY | inspectZ))
 6240433            return true;
 434
 14435        Signed192 exactX = GetLinearCombinationComponent(
 14436            first.X,
 14437            firstScale,
 14438            second.X,
 14439            secondScale,
 14440            third.X,
 14441            thirdScale);
 14442        Signed192 exactY = GetLinearCombinationComponent(
 14443            first.Y,
 14444            firstScale,
 14445            second.Y,
 14446            secondScale,
 14447            third.Y,
 14448            thirdScale);
 14449        Signed192 exactZ = GetLinearCombinationComponent(
 14450            first.Z,
 14451            firstScale,
 14452            second.Z,
 14453            secondScale,
 14454            third.Z,
 14455            thirdScale);
 14456        return PreservesExactValue(inspectX, exactX)
 14457            & PreservesExactValue(inspectY, exactY)
 14458            & PreservesExactValue(inspectZ, exactZ);
 459    }
 460
 461    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 462    private static bool PreservesExactValue(
 463        bool inspect,
 464        Signed192 exact) =>
 5679465        !inspect || exact.IsZero;
 466
 467    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 468    private static bool HasSafeProductInputs(
 469        Vector3d left,
 470        Vector3d right) =>
 29231471        IsSafeMagnitude(
 29231472            GetAggregateMagnitude(left)
 29231473            | GetAggregateMagnitude(right),
 29231474            SafeProductMagnitudeShift);
 475
 476    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 477    private static bool HasSafeProductInputs(
 478        Fixed3x3 matrix,
 479        Vector3d direction) =>
 26480        IsSafeMagnitude(
 26481            GetAggregateMagnitude(matrix)
 26482            | GetAggregateMagnitude(direction),
 26483            SafeProductMagnitudeShift);
 484
 485    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 486    private static bool HasSafeProductInputs(
 487        Vector3d first,
 488        Fixed64 firstScale,
 489        Vector3d second,
 490        Fixed64 secondScale,
 491        Vector3d third,
 492        Fixed64 thirdScale) =>
 6257493        IsSafeMagnitude(
 6257494            GetAggregateMagnitude(first)
 6257495            | GetRawMagnitude(firstScale)
 6257496            | GetAggregateMagnitude(second)
 6257497            | GetRawMagnitude(secondScale)
 6257498            | GetAggregateMagnitude(third)
 6257499            | GetRawMagnitude(thirdScale),
 6257500            SafeProductMagnitudeShift);
 501
 502    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 503    private static bool HasSafeProductInputs(
 504        Vector3d value,
 505        Fixed64 scale) =>
 11832506        IsSafeMagnitude(
 11832507            GetAggregateMagnitude(value)
 11832508            | GetRawMagnitude(scale),
 11832509            SafeProductMagnitudeShift);
 510
 511    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 512    private static bool HasNonzeroTerm(
 513        Fixed64 first,
 514        Fixed64 firstScale,
 515        Fixed64 second,
 516        Fixed64 secondScale,
 517        Fixed64 third,
 518        Fixed64 thirdScale) =>
 172196519        (first != Fixed64.Zero && firstScale != Fixed64.Zero)
 172196520        || (second != Fixed64.Zero && secondScale != Fixed64.Zero)
 172196521        || (third != Fixed64.Zero && thirdScale != Fixed64.Zero);
 522
 523    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 524    private static bool HasNonzeroDifferenceTerm(
 525        Fixed64 first,
 526        Fixed64 firstScale,
 527        Fixed64 second,
 528        Fixed64 secondScale) =>
 248493529        (first != Fixed64.Zero && firstScale != Fixed64.Zero)
 248493530        || (second != Fixed64.Zero && secondScale != Fixed64.Zero);
 531
 532    private static Signed192 GetLinearCombinationComponent(
 533        Fixed64 first,
 534        Fixed64 firstScale,
 535        Fixed64 second,
 536        Fixed64 secondScale,
 537        Fixed64 third,
 538        Fixed64 thirdScale) =>
 49539        WideGeometry.GetDifferenceDotProduct3D(
 49540            first, Fixed64.Zero,
 49541            second, Fixed64.Zero,
 49542            third, Fixed64.Zero,
 49543            firstScale, Fixed64.Zero,
 49544            secondScale, Fixed64.Zero,
 49545            thirdScale, Fixed64.Zero);
 546
 547    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 548    private static bool IsSafeMagnitude(
 549        ulong aggregateMagnitude,
 550        int shift) =>
 124611551        (aggregateMagnitude >> shift) == 0UL;
 552
 553    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 554    private static ulong GetAggregateMagnitude(Vector3d value) =>
 337746555        GetRawMagnitude(value.X)
 337746556        | GetRawMagnitude(value.Y)
 337746557        | GetRawMagnitude(value.Z);
 558
 559    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 560    private static ulong GetAggregateMagnitude(Fixed3x3 matrix) =>
 58466561        GetRawMagnitude(matrix.M11)
 58466562        | GetRawMagnitude(matrix.M12)
 58466563        | GetRawMagnitude(matrix.M13)
 58466564        | GetRawMagnitude(matrix.M21)
 58466565        | GetRawMagnitude(matrix.M22)
 58466566        | GetRawMagnitude(matrix.M23)
 58466567        | GetRawMagnitude(matrix.M31)
 58466568        | GetRawMagnitude(matrix.M32)
 58466569        | GetRawMagnitude(matrix.M33);
 570
 571    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 572    private static ulong GetRawMagnitude(Fixed64 value)
 573    {
 1570035574        ulong raw = unchecked((ulong)value.m_rawValue);
 1570035575        ulong sign = unchecked((ulong)(value.m_rawValue >> 63));
 1570035576        return (raw ^ sign) - sign;
 577    }
 578}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/3D/ExactContactLever3D.cs

#LineLine coverage
 1//=======================================================================
 2// ExactContactLever3D.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;
 10
 11namespace Gravitas.CollisionHandling;
 12
 13/// <summary>
 14/// Evaluates solver products for a semantic lever that cannot be narrowed to
 15/// <see cref="Vector3d"/>.
 16/// </summary>
 17internal static class ExactContactLever3D
 18{
 19    internal static bool TryGetNormalResponse(
 20        SolidBody? bodyA,
 21        Vector3d linearVelocityA,
 22        Vector3d angularVelocityA,
 23        in ExactLever3D relativeContactPointA,
 24        SolidBody? bodyB,
 25        Vector3d linearVelocityB,
 26        Vector3d angularVelocityB,
 27        in ExactLever3D relativeContactPointB,
 28        Vector3d normal,
 29        Fixed64 restitution,
 30        Fixed64 restitutionVelocityThreshold,
 31        out ExactNormalResponse3D response)
 32    {
 5733        ExactContactResponseOperand3D first = CreateResponseOperand(
 5734            bodyA,
 5735            linearVelocityA,
 5736            angularVelocityA,
 5737            relativeContactPointA,
 5738            -normal);
 5739        ExactContactResponseOperand3D second = CreateResponseOperand(
 5740            bodyB,
 5741            linearVelocityB,
 5742            angularVelocityB,
 5743            relativeContactPointB,
 5744            normal);
 5745        return ExactContactResponseKernel.TryGetNormalResponse(
 5746            first,
 5747            second,
 5748            normal,
 5749            restitution,
 5750            restitutionVelocityThreshold,
 5751            out response);
 52    }
 53
 54    internal static bool TryGetAccumulatedNormalResponse(
 55        SolidBody? bodyA,
 56        Vector3d linearVelocityA,
 57        Vector3d angularVelocityA,
 58        in ExactLever3D relativeContactPointA,
 59        SolidBody? bodyB,
 60        Vector3d linearVelocityB,
 61        Vector3d angularVelocityB,
 62        in ExactLever3D relativeContactPointB,
 63        Vector3d normal,
 64        Fixed64 restitution,
 65        Fixed64 restitutionVelocityThreshold,
 66        Fixed64 accumulatedImpulse,
 67        Fixed64 positiveImpulseScale,
 68        Fixed64 negativeImpulseScale,
 69        out ExactNormalResponse3D response)
 70    {
 139471        ExactContactResponseOperand3D first = CreateResponseOperand(
 139472            bodyA,
 139473            linearVelocityA,
 139474            angularVelocityA,
 139475            relativeContactPointA,
 139476            -normal);
 139477        ExactContactResponseOperand3D second = CreateResponseOperand(
 139478            bodyB,
 139479            linearVelocityB,
 139480            angularVelocityB,
 139481            relativeContactPointB,
 139482            normal);
 139483        return ExactContactResponseKernel.TryGetAccumulatedNormalResponse(
 139484            first,
 139485            second,
 139486            normal,
 139487            restitution,
 139488            restitutionVelocityThreshold,
 139489            accumulatedImpulse,
 139490            positiveImpulseScale,
 139491            negativeImpulseScale,
 139492            out response);
 93    }
 94
 95    internal static ExactContactResponseOperand3D CreateResponseOperand(
 96        SolidBody? body,
 97        Vector3d linearVelocity,
 98        Vector3d angularVelocity,
 99        in ExactLever3D relativeContactPoint,
 100        Vector3d signedNormal) =>
 4646101        new(
 4646102            relativeContactPoint,
 4646103            linearVelocity,
 4646104            angularVelocity,
 4646105            body?.ProjectLinearMotion(signedNormal) ?? Vector3d.Zero,
 4646106            body?.EffectiveInverseMass ?? Fixed64.Zero,
 4646107            body?.GetConstrainedInverseInertiaTensor() ?? Fixed3x3.Zero);
 108
 109    internal static bool TryGetAngularVelocityDelta(
 110        SolidBody? body,
 111        in ExactLever3D relativeContactPoint,
 112        Vector3d impulse,
 113        out Vector3d velocityDelta)
 114    {
 99115        velocityDelta = Vector3d.Zero;
 99116        return body?.CanRotate != true
 99117            || ExactLever3D.TryGetTransformedScaledCrossProduct(
 99118                relativeContactPoint,
 99119                impulse,
 99120                body.GetConstrainedInverseInertiaTensor(),
 99121                Fixed64.One,
 99122                Fixed64.One,
 99123                Fixed64.One,
 99124                out velocityDelta);
 125    }
 126
 127    internal static bool TryGetImpulseCombinationVelocityDeltas(
 128        SolidBody? bodyA,
 129        in ExactLever3D relativeContactPointA,
 130        SolidBody? bodyB,
 131        in ExactLever3D relativeContactPointB,
 132        Vector3d firstAxis,
 133        Fixed64 firstScale,
 134        Vector3d secondAxis,
 135        Fixed64 secondScale,
 136        Vector3d thirdAxis,
 137        Fixed64 thirdScale,
 138        out Vector3d linearVelocityDeltaA,
 139        out Vector3d angularVelocityDeltaA,
 140        out Vector3d linearVelocityDeltaB,
 141        out Vector3d angularVelocityDeltaB)
 142    {
 9143        bool linearAResolved = TryGetLinearCombinationVelocityDelta(
 9144            bodyA,
 9145            -firstAxis,
 9146            firstScale,
 9147            -secondAxis,
 9148            secondScale,
 9149            -thirdAxis,
 9150            thirdScale,
 9151            out linearVelocityDeltaA);
 9152        bool angularAResolved = TryGetAngularCombinationVelocityDelta(
 9153            bodyA,
 9154            relativeContactPointA,
 9155            -firstAxis,
 9156            firstScale,
 9157            -secondAxis,
 9158            secondScale,
 9159            -thirdAxis,
 9160            thirdScale,
 9161            out angularVelocityDeltaA);
 9162        bool linearBResolved = TryGetLinearCombinationVelocityDelta(
 9163            bodyB,
 9164            firstAxis,
 9165            firstScale,
 9166            secondAxis,
 9167            secondScale,
 9168            thirdAxis,
 9169            thirdScale,
 9170            out linearVelocityDeltaB);
 9171        bool angularBResolved = TryGetAngularCombinationVelocityDelta(
 9172            bodyB,
 9173            relativeContactPointB,
 9174            firstAxis,
 9175            firstScale,
 9176            secondAxis,
 9177            secondScale,
 9178            thirdAxis,
 9179            thirdScale,
 9180            out angularVelocityDeltaB);
 9181        return linearAResolved
 9182            & angularAResolved
 9183            & linearBResolved
 9184            & angularBResolved;
 185    }
 186
 187    private static bool TryGetLinearCombinationVelocityDelta(
 188        SolidBody? body,
 189        Vector3d firstAxis,
 190        Fixed64 firstScale,
 191        Vector3d secondAxis,
 192        Fixed64 secondScale,
 193        Vector3d thirdAxis,
 194        Fixed64 thirdScale,
 195        out Vector3d velocityDelta)
 196    {
 18197        velocityDelta = Vector3d.Zero;
 18198        return body?.CanTranslate != true
 18199            || Vector3d.TryScaledLinearCombination(
 18200                body.ProjectLinearMotion(firstAxis),
 18201                firstScale,
 18202                body.ProjectLinearMotion(secondAxis),
 18203                secondScale,
 18204                body.ProjectLinearMotion(thirdAxis),
 18205                thirdScale,
 18206                body.EffectiveInverseMass,
 18207                out velocityDelta);
 208    }
 209
 210    private static bool TryGetAngularCombinationVelocityDelta(
 211        SolidBody? body,
 212        in ExactLever3D relativeContactPoint,
 213        Vector3d firstAxis,
 214        Fixed64 firstScale,
 215        Vector3d secondAxis,
 216        Fixed64 secondScale,
 217        Vector3d thirdAxis,
 218        Fixed64 thirdScale,
 219        out Vector3d velocityDelta)
 220    {
 18221        velocityDelta = Vector3d.Zero;
 18222        return body?.CanRotate != true
 18223            || ExactLever3D.TryGetTransformedWeightedCrossProduct(
 18224                    relativeContactPoint,
 18225                    firstAxis,
 18226                    firstScale,
 18227                    secondAxis,
 18228                    secondScale,
 18229                    thirdAxis,
 18230                    thirdScale,
 18231                    body.GetConstrainedInverseInertiaTensor(),
 18232                    out velocityDelta);
 233    }
 234}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/3D/ResponseBody.cs

#LineLine coverage
 1//=======================================================================
 2// ResponseBody.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;
 10
 11namespace Gravitas.CollisionHandling;
 12
 13/// <summary>
 14/// Represents a body involved in a collision response, containing necessary information for collision resolution.
 15/// </summary>
 16internal readonly struct ResponseBody
 17{
 18    private ResponseBody(SolidBody body, Fixed64 inverseMass)
 19    {
 1767820        Body = body;
 1767821        InverseMass = inverseMass;
 1767822    }
 23
 24    public SolidBody Body { get; }
 25
 26    public Fixed64 InverseMass { get; }
 27
 3364128    public bool HasSolverMobility => Body.HasSolverMobility;
 29
 1230530    public bool CanRotate => Body.CanRotate;
 31
 32    public Fixed64 GetConstrainedInverseMass(Vector3d axis) =>
 1351033        Body.GetConstrainedInverseMass(axis);
 34
 35    public static ResponseBody Create(LSCollider collider)
 36    {
 1767837        SolidBody body = collider.Body!;
 1767838        return Create(body);
 39    }
 40
 41    public static ResponseBody Create(SolidBody body)
 42    {
 1767843        Fixed64 inverseMass = body.EffectiveInverseMass;
 1767844        return new ResponseBody(body, inverseMass);
 45    }
 46}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/3D/SolverContact.cs

#LineLine coverage
 1//=======================================================================
 2// SolverContact.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.Materials;
 10
 11namespace Gravitas.CollisionHandling;
 12
 13internal readonly struct ContactLever3D
 14{
 15    private ContactLever3D(Vector3d vector, bool isExact)
 16    {
 1828617        Vector = vector;
 1828618        IsExact = isExact;
 1828619    }
 20
 21    internal bool IsExact { get; }
 22
 23    internal Vector3d Vector { get; }
 24
 25    internal static ContactLever3D Create(
 26        in ContactAnchor point,
 27        in ContactAnchor origin)
 28    {
 1828629        if (point.TryGetOffsetFrom(origin, out Vector3d vector))
 1824630            return new ContactLever3D(vector, isExact: false);
 31
 4032        return new ContactLever3D(default, isExact: true);
 33    }
 34}
 35
 36/// <summary>
 37/// Solver-ready 3D contact data, including the deterministic tangent frame and cached impulses.
 38/// </summary>
 39internal readonly struct SolverContact
 40{
 41    public SolverContact(
 42        int manifoldIndex,
 43        ulong contactId,
 44        ResponseBody bodyA,
 45        ResponseBody bodyB,
 46        ContactLever3D relativeA,
 47        ContactLever3D relativeB,
 48        Fixed64 depth,
 49        Vector3d normal,
 50        PhysicsMaterial materialA,
 51        PhysicsMaterial materialB,
 52        Fixed64 cachedNormalImpulse,
 53        Fixed64 cachedTangentImpulse,
 54        Fixed64 cachedSecondaryTangentImpulse)
 55    {
 908456        ManifoldIndex = manifoldIndex;
 908457        ContactId = contactId;
 908458        A = bodyA;
 908459        B = bodyB;
 908460        RelativeA = relativeA;
 908461        RelativeB = relativeB;
 908462        Depth = depth;
 908463        Normal = normal;
 908464        MaterialA = materialA;
 908465        MaterialB = materialB;
 908466        PhysicsMaterial.CombineFriction(materialA, materialB, out Fixed64 staticFriction, out Fixed64 dynamicFriction);
 908467        StaticFriction = staticFriction;
 908468        DynamicFriction = dynamicFriction;
 908469        Restitution = PhysicsMaterial.CombineRestitution(materialA, materialB);
 908470        Tangent = CreateTangent(normal);
 908471        SecondaryTangent = Vector3d.Cross(normal, Tangent).Normalized;
 908472        CachedNormalImpulse = cachedNormalImpulse;
 908473        CachedTangentImpulse = cachedTangentImpulse;
 908474        CachedSecondaryTangentImpulse = cachedSecondaryTangentImpulse;
 908475    }
 76
 77    public int ManifoldIndex { get; }
 78
 79    public ulong ContactId { get; }
 80
 81    public ResponseBody A { get; }
 82
 83    public ResponseBody B { get; }
 84
 85    public ContactLever3D RelativeA { get; }
 86
 87    public ContactLever3D RelativeB { get; }
 88
 89    public Fixed64 Depth { get; }
 90
 91    public Vector3d Normal { get; }
 92
 93    public PhysicsMaterial MaterialA { get; }
 94
 95    public PhysicsMaterial MaterialB { get; }
 96
 97    public Fixed64 StaticFriction { get; }
 98
 99    public Fixed64 DynamicFriction { get; }
 100
 101    public Fixed64 Restitution { get; }
 102
 103    public Vector3d Tangent { get; }
 104
 105    public Vector3d SecondaryTangent { get; }
 106
 107    public Fixed64 CachedNormalImpulse { get; }
 108
 109    public Fixed64 CachedTangentImpulse { get; }
 110
 111    public Fixed64 CachedSecondaryTangentImpulse { get; }
 112
 113    internal static Vector3d CreateTangent(Vector3d normal)
 114    {
 9117115        Vector3d absolute = Vector3d.Abs(normal);
 9117116        Vector3d reference = absolute.X <= absolute.Y && absolute.X <= absolute.Z
 9117117            ? Vector3d.Right
 9117118            : absolute.Y <= absolute.Z
 9117119                ? Vector3d.Up
 9117120                : Vector3d.Forward;
 121
 9117122        return Vector3d.Cross(reference, normal).Normalized;
 123    }
 124}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/3D/SolverContactBuffer.cs

#LineLine coverage
 1//=======================================================================
 2// SolverContactBuffer.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.CollisionHandling;
 12
 13/// <summary>
 14/// A buffer for storing contact points and their associated impulses and velocities during collision resolution.
 15/// </summary>
 16internal struct SolverContactBuffer
 17{
 18    private SolverContact _contact0;
 19    private SolverContact _contact1;
 20    private SolverContact _contact2;
 21    private SolverContact _contact3;
 22    private Fixed64 _normalImpulse0;
 23    private Fixed64 _normalImpulse1;
 24    private Fixed64 _normalImpulse2;
 25    private Fixed64 _normalImpulse3;
 26    private Fixed64 _tangentImpulse0;
 27    private Fixed64 _tangentImpulse1;
 28    private Fixed64 _tangentImpulse2;
 29    private Fixed64 _tangentImpulse3;
 30    private Fixed64 _secondaryTangentImpulse0;
 31    private Fixed64 _secondaryTangentImpulse1;
 32    private Fixed64 _secondaryTangentImpulse2;
 33    private Fixed64 _secondaryTangentImpulse3;
 34    private ContactNormalImpulseResult3D _normalResult0;
 35    private ContactNormalImpulseResult3D _normalResult1;
 36    private ContactNormalImpulseResult3D _normalResult2;
 37    private ContactNormalImpulseResult3D _normalResult3;
 38
 39    public int Count { get; private set; }
 40
 41    public void Add(SolverContact contact)
 42    {
 908443        switch (Count)
 44        {
 45            case 0:
 884446                _contact0 = contact;
 884447                break;
 48            case 1:
 8249                _contact1 = contact;
 8250                break;
 51            case 2:
 7952                _contact2 = contact;
 7953                break;
 54            default:
 7955                _contact3 = contact;
 56                break;
 57        }
 58
 908459        Count++;
 908460    }
 61
 62    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 63    public SolverContact GetContact(int index) =>
 3824764        index switch
 3824765        {
 3710766            0 => _contact0,
 39267            1 => _contact1,
 37468            2 => _contact2,
 37469            _ => _contact3
 3824770        };
 71
 72    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 73    public Fixed64 GetNormalImpulse(int index) =>
 1814774        index switch
 1814775        {
 1766776            0 => _normalImpulse0,
 16477            1 => _normalImpulse1,
 15878            2 => _normalImpulse2,
 15879            _ => _normalImpulse3
 1814780        };
 81
 82    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 83    public ContactNormalImpulseResult3D GetNormalResult(int index) =>
 907584        index switch
 907585        {
 883586            0 => _normalResult0,
 8287            1 => _normalResult1,
 7988            2 => _normalResult2,
 7989            _ => _normalResult3
 907590        };
 91
 92    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 93    public Fixed64 GetTangentImpulse(int index) =>
 907394        index switch
 907395        {
 883396            0 => _tangentImpulse0,
 8297            1 => _tangentImpulse1,
 7998            2 => _tangentImpulse2,
 7999            _ => _tangentImpulse3
 9073100        };
 101
 102    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 103    public Fixed64 GetSecondaryTangentImpulse(int index) =>
 9073104        index switch
 9073105        {
 8833106            0 => _secondaryTangentImpulse0,
 82107            1 => _secondaryTangentImpulse1,
 79108            2 => _secondaryTangentImpulse2,
 79109            _ => _secondaryTangentImpulse3
 9073110        };
 111
 112    public void SetNormalImpulse(
 113        int index,
 114        Fixed64 impulse,
 115        ContactNormalImpulseResult3D normalResult)
 116    {
 117        switch (index)
 118        {
 119            case 0:
 8835120                _normalImpulse0 = impulse;
 8835121                _normalResult0 = normalResult;
 8835122                break;
 123            case 1:
 82124                _normalImpulse1 = impulse;
 82125                _normalResult1 = normalResult;
 82126                break;
 127            case 2:
 79128                _normalImpulse2 = impulse;
 79129                _normalResult2 = normalResult;
 79130                break;
 131            default:
 79132                _normalImpulse3 = impulse;
 79133                _normalResult3 = normalResult;
 134                break;
 135        }
 79136    }
 137
 138    public void SetTangentImpulse(int index, Fixed64 impulse, Fixed64 secondaryImpulse)
 139    {
 140        switch (index)
 141        {
 142            case 0:
 8833143                _tangentImpulse0 = impulse;
 8833144                _secondaryTangentImpulse0 = secondaryImpulse;
 8833145                break;
 146            case 1:
 82147                _tangentImpulse1 = impulse;
 82148                _secondaryTangentImpulse1 = secondaryImpulse;
 82149                break;
 150            case 2:
 79151                _tangentImpulse2 = impulse;
 79152                _secondaryTangentImpulse2 = secondaryImpulse;
 79153                break;
 154            default:
 79155                _tangentImpulse3 = impulse;
 79156                _secondaryTangentImpulse3 = secondaryImpulse;
 157                break;
 158        }
 79159    }
 160}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/Exact/ExactContactResponseKernel.Coulomb.cs

#LineLine coverage
 1//=======================================================================
 2// ExactContactResponseKernel.Coulomb.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 System;
 9using System.Runtime.CompilerServices;
 10
 11using FixedMathSharp;
 12using FixedMathSharp.Geometry;
 13
 14namespace Gravitas.CollisionHandling;
 15
 16/// <content>
 17/// Owns exact line and disk Coulomb-friction policy and materialization.
 18/// </content>
 19internal static partial class ExactContactResponseKernel
 20{
 21    private const int MaxCoulombWords = MaxResponseWords * 2;
 22    private const int MaxCoulombSquareWords = MaxCoulombWords * 2;
 23    // Normal-response ratios use fewer than 46 active words. A common tangent
 24    // denominator therefore uses fewer than 92, its squared magnitude fewer
 25    // than 185 including carry, and the largest radial comparison fewer than
 26    // 307. The remaining words prevent silent product truncation.
 27    private const int MaxCoulombComparisonWords = 320;
 28
 29    [MethodImpl(MethodImplOptions.NoInlining)]
 30    internal static bool TryGetCoulombLineResponse(
 31        in ExactNormalConstraint3D normalConstraint,
 32        in ExactContactResponseOperand3D firstTangent,
 33        in ExactContactResponseOperand3D secondTangent,
 34        Vector3d tangent,
 35        Fixed64 accumulatedTangentImpulse,
 36        Fixed64 staticFriction,
 37        Fixed64 dynamicFriction,
 38        out ExactCoulombResponse3D response)
 39    {
 5940        response = default;
 5941        bool inputsValid =
 5942            staticFriction >= Fixed64.Zero
 5943            & dynamicFriction >= Fixed64.Zero
 5944            & tangent.IsNormalized();
 5945        if (!inputsValid)
 346            return false;
 47
 5648        bool constraintValid =
 5649            FixedMath.Abs(Vector3d.Dot(
 5650                normalConstraint.Normal,
 5651                tangent)) <= Fixed64.Epsilon
 5652            & HaveMatchingParticipants(
 5653                normalConstraint.First,
 5654                firstTangent)
 5655            & HaveMatchingParticipants(
 5656                normalConstraint.Second,
 5657                secondTangent);
 5658        if (!constraintValid)
 259            return false;
 60
 5461        Span<ulong> normalNumerator = stackalloc ulong[MaxResponseWords];
 5462        Span<ulong> normalDenominator = stackalloc ulong[MaxResponseWords];
 5463        Span<ulong> tangentNumerator = stackalloc ulong[MaxResponseWords];
 5464        Span<ulong> tangentDenominator = stackalloc ulong[MaxResponseWords];
 5465        if (!TryGetCompletedNormalAccumulatorRatio(
 5466                normalConstraint,
 5467                normalNumerator,
 5468                normalDenominator))
 169            return false;
 5370        GetBilateralImpulseRatio(
 5371            firstTangent,
 5372            secondTangent,
 5373            tangent,
 5374            Fixed64.Zero,
 5375            tangentNumerator,
 5376            tangentDenominator,
 5377            out int tangentSign);
 78
 5379        Span<ulong> desiredNumerator = stackalloc ulong[MaxResponseWords];
 5380        AddFixedToRatio(
 5381            tangentNumerator,
 5382            tangentDenominator,
 5383            tangentSign,
 5384            accumulatedTangentImpulse,
 5385            desiredNumerator,
 5386            out int desiredSign);
 87
 5388        Span<ulong> staticNumerator = stackalloc ulong[MaxResponseWords];
 5389        Span<ulong> staticDenominator = stackalloc ulong[MaxResponseWords];
 5390        GetFrictionLimit(
 5391            normalNumerator,
 5392            normalDenominator,
 5393            staticFriction,
 5394            staticNumerator,
 5395            staticDenominator);
 96
 5397        Span<ulong> appliedNumerator = stackalloc ulong[MaxCoulombWords];
 5398        Span<ulong> appliedDenominator = stackalloc ulong[MaxCoulombWords];
 99        int appliedSign;
 53100        Span<ulong> accumulatedNumerator = stackalloc ulong[MaxCoulombWords];
 53101        Span<ulong> accumulatedDenominator = stackalloc ulong[MaxCoulombWords];
 102        int accumulatedSign;
 53103        appliedNumerator.Clear();
 53104        appliedDenominator.Clear();
 53105        accumulatedNumerator.Clear();
 53106        accumulatedDenominator.Clear();
 53107        Span<ulong> clampedNumerator = stackalloc ulong[MaxCoulombWords];
 53108        Span<ulong> clampedDenominator = stackalloc ulong[MaxResponseWords];
 53109        if (CompareRatios(
 53110                desiredNumerator,
 53111                tangentDenominator,
 53112                staticNumerator,
 53113                staticDenominator) <= 0)
 114        {
 37115            tangentNumerator.CopyTo(appliedNumerator);
 37116            tangentDenominator.CopyTo(appliedDenominator);
 37117            appliedSign = tangentSign;
 37118            desiredNumerator.CopyTo(accumulatedNumerator);
 37119            tangentDenominator.CopyTo(accumulatedDenominator);
 37120            accumulatedSign = desiredSign;
 121        }
 122        else
 123        {
 16124            GetFrictionLimit(
 16125                normalNumerator,
 16126                normalDenominator,
 16127                dynamicFriction,
 16128                clampedNumerator,
 16129                clampedDenominator);
 16130            clampedNumerator.CopyTo(accumulatedNumerator);
 16131            clampedDenominator.CopyTo(accumulatedDenominator);
 16132            accumulatedSign = WideArithmetic.IsZeroMagnitude(
 16133                clampedNumerator)
 16134                ? 0
 16135                : desiredSign;
 16136            SubtractFixedFromRatio(
 16137                clampedNumerator,
 16138                clampedDenominator,
 16139                accumulatedSign,
 16140                accumulatedTangentImpulse,
 16141                appliedNumerator,
 16142                appliedDenominator,
 16143                out appliedSign);
 144        }
 145
 53146        bool resolved = TryGetLinearVelocityDelta(
 53147            firstTangent.LinearImpulseAxis,
 53148            firstTangent.InverseMass,
 53149            appliedNumerator,
 53150            appliedDenominator,
 53151            appliedSign,
 53152            out Vector3d firstLinear);
 53153        resolved &= TryGetAngularVelocityDelta(
 53154            firstTangent.Lever,
 53155            -tangent,
 53156            firstTangent.InverseInertia,
 53157            appliedNumerator,
 53158            appliedDenominator,
 53159            appliedSign,
 53160            out Vector3d firstAngular);
 53161        resolved &= TryGetLinearVelocityDelta(
 53162            secondTangent.LinearImpulseAxis,
 53163            secondTangent.InverseMass,
 53164            appliedNumerator,
 53165            appliedDenominator,
 53166            appliedSign,
 53167            out Vector3d secondLinear);
 53168        resolved &= TryGetAngularVelocityDelta(
 53169            secondTangent.Lever,
 53170            tangent,
 53171            secondTangent.InverseInertia,
 53172            appliedNumerator,
 53173            appliedDenominator,
 53174            appliedSign,
 53175            out Vector3d secondAngular);
 53176        if (!resolved)
 3177            return false;
 178
 50179        bool hasAccumulatedProjection = Fixed64.TryGetSignedRawRatio(
 50180            accumulatedNumerator,
 50181            accumulatedDenominator,
 50182            accumulatedSign < 0,
 50183            out Fixed64 accumulatedProjection);
 50184        response = new ExactCoulombResponse3D(
 50185            !WideArithmetic.IsZeroMagnitude(appliedNumerator),
 50186            firstLinear,
 50187            firstAngular,
 50188            secondLinear,
 50189            secondAngular,
 50190            hasAccumulatedProjection,
 50191            accumulatedProjection,
 50192            hasSecondaryAccumulatedImpulse: false,
 50193            secondaryAccumulatedImpulse: default);
 50194        return true;
 195    }
 196
 197    [MethodImpl(MethodImplOptions.NoInlining)]
 198    internal static bool TryGetCoulombDiskResponse(
 199        Vector3d normal,
 200        Fixed64 completedNormalImpulse,
 201        in ExactContactResponseOperand3D primaryFirst,
 202        in ExactContactResponseOperand3D primarySecond,
 203        Vector3d primaryTangent,
 204        Fixed64 accumulatedPrimaryTangentImpulse,
 205        in ExactContactResponseOperand3D secondaryFirst,
 206        in ExactContactResponseOperand3D secondarySecond,
 207        Vector3d secondaryTangent,
 208        Fixed64 accumulatedSecondaryTangentImpulse,
 209        Fixed64 staticFriction,
 210        Fixed64 dynamicFriction,
 211        out ExactCoulombResponse3D response)
 212    {
 386213        response = default;
 386214        if (completedNormalImpulse < Fixed64.Zero
 386215            || !AreCoulombDiskInputsValid(
 386216                normal,
 386217                primaryFirst,
 386218                primarySecond,
 386219                primaryTangent,
 386220                secondaryFirst,
 386221                secondarySecond,
 386222                secondaryTangent,
 386223                staticFriction,
 386224                dynamicFriction))
 225        {
 2226            return false;
 227        }
 228
 384229        Span<ulong> normalNumerator = stackalloc ulong[MaxResponseWords];
 384230        Span<ulong> normalDenominator = stackalloc ulong[MaxResponseWords];
 384231        SetMagnitude(completedNormalImpulse, normalNumerator);
 384232        normalDenominator.Clear();
 384233        normalDenominator[0] = 1UL;
 384234        return TryGetCoulombDiskResponseCore(
 384235            normalNumerator,
 384236            normalDenominator,
 384237            primaryFirst,
 384238            primarySecond,
 384239            primaryTangent,
 384240            accumulatedPrimaryTangentImpulse,
 384241            secondaryFirst,
 384242            secondarySecond,
 384243            secondaryTangent,
 384244            accumulatedSecondaryTangentImpulse,
 384245            staticFriction,
 384246            dynamicFriction,
 384247            Fixed64.Epsilon,
 384248            out response);
 249    }
 250
 251    [MethodImpl(MethodImplOptions.NoInlining)]
 252    internal static bool TryGetCoulombDiskResponse(
 253        in ExactNormalConstraint3D normalConstraint,
 254        in ExactContactResponseOperand3D primaryFirst,
 255        in ExactContactResponseOperand3D primarySecond,
 256        Vector3d primaryTangent,
 257        Fixed64 accumulatedPrimaryTangentImpulse,
 258        in ExactContactResponseOperand3D secondaryFirst,
 259        in ExactContactResponseOperand3D secondarySecond,
 260        Vector3d secondaryTangent,
 261        Fixed64 accumulatedSecondaryTangentImpulse,
 262        Fixed64 staticFriction,
 263        Fixed64 dynamicFriction,
 264        out ExactCoulombResponse3D response)
 265    {
 59266        response = default;
 59267        bool inputsValid = AreCoulombDiskInputsValid(
 59268                normalConstraint.Normal,
 59269                primaryFirst,
 59270                primarySecond,
 59271                primaryTangent,
 59272                secondaryFirst,
 59273                secondarySecond,
 59274                secondaryTangent,
 59275                staticFriction,
 59276                dynamicFriction)
 59277            & HaveMatchingParticipants(
 59278                normalConstraint.First,
 59279                primaryFirst)
 59280            & HaveMatchingParticipants(
 59281                normalConstraint.Second,
 59282                primarySecond)
 59283            & HaveMatchingParticipants(primaryFirst, secondaryFirst)
 59284            & HaveMatchingParticipants(primarySecond, secondarySecond);
 59285        if (!inputsValid)
 3286            return false;
 287
 56288        Span<ulong> normalNumerator = stackalloc ulong[MaxResponseWords];
 56289        Span<ulong> normalDenominator = stackalloc ulong[MaxResponseWords];
 56290        if (!TryGetCompletedNormalAccumulatorRatio(
 56291                normalConstraint,
 56292                normalNumerator,
 56293                normalDenominator))
 1294            return false;
 295
 55296        return TryGetCoulombDiskResponseCore(
 55297            normalNumerator,
 55298            normalDenominator,
 55299            primaryFirst,
 55300            primarySecond,
 55301            primaryTangent,
 55302            accumulatedPrimaryTangentImpulse,
 55303            secondaryFirst,
 55304            secondarySecond,
 55305            secondaryTangent,
 55306            accumulatedSecondaryTangentImpulse,
 55307            staticFriction,
 55308            dynamicFriction,
 55309            Fixed64.Zero,
 55310            out response);
 311    }
 312
 313    private static bool TryGetCoulombDiskResponseCore(
 314        ReadOnlySpan<ulong> normalNumerator,
 315        ReadOnlySpan<ulong> normalDenominator,
 316        in ExactContactResponseOperand3D primaryFirst,
 317        in ExactContactResponseOperand3D primarySecond,
 318        Vector3d primaryTangent,
 319        Fixed64 accumulatedPrimaryTangentImpulse,
 320        in ExactContactResponseOperand3D secondaryFirst,
 321        in ExactContactResponseOperand3D secondarySecond,
 322        Vector3d secondaryTangent,
 323        Fixed64 accumulatedSecondaryTangentImpulse,
 324        Fixed64 staticFriction,
 325        Fixed64 dynamicFriction,
 326        Fixed64 velocityDeadzone,
 327        out ExactCoulombResponse3D response)
 328    {
 439329        response = default;
 439330        Span<ulong> primaryNumerator = stackalloc ulong[MaxResponseWords];
 439331        Span<ulong> primaryDenominator = stackalloc ulong[MaxResponseWords];
 439332        Span<ulong> secondaryNumerator = stackalloc ulong[MaxResponseWords];
 439333        Span<ulong> secondaryDenominator = stackalloc ulong[MaxResponseWords];
 439334        GetBilateralImpulseRatio(
 439335            primaryFirst,
 439336            primarySecond,
 439337            primaryTangent,
 439338            velocityDeadzone,
 439339            primaryNumerator,
 439340            primaryDenominator,
 439341            out int primarySign);
 439342        GetBilateralImpulseRatio(
 439343            secondaryFirst,
 439344            secondarySecond,
 439345            secondaryTangent,
 439346            velocityDeadzone,
 439347            secondaryNumerator,
 439348            secondaryDenominator,
 439349            out int secondarySign);
 350
 439351        Span<ulong> desiredPrimaryNumerator =
 439352            stackalloc ulong[MaxResponseWords];
 439353        Span<ulong> desiredSecondaryNumerator =
 439354            stackalloc ulong[MaxResponseWords];
 439355        AddFixedToRatio(
 439356            primaryNumerator,
 439357            primaryDenominator,
 439358            primarySign,
 439359            accumulatedPrimaryTangentImpulse,
 439360            desiredPrimaryNumerator,
 439361            out int desiredPrimarySign);
 439362        AddFixedToRatio(
 439363            secondaryNumerator,
 439364            secondaryDenominator,
 439365            secondarySign,
 439366            accumulatedSecondaryTangentImpulse,
 439367            desiredSecondaryNumerator,
 439368            out int desiredSecondarySign);
 369
 439370        Span<ulong> commonDenominator = stackalloc ulong[MaxCoulombWords];
 439371        Span<ulong> primaryAtCommon = stackalloc ulong[MaxCoulombWords];
 439372        Span<ulong> secondaryAtCommon = stackalloc ulong[MaxCoulombWords];
 439373        WideArithmetic.MultiplyMagnitudes(
 439374            primaryDenominator,
 439375            secondaryDenominator,
 439376            commonDenominator);
 439377        WideArithmetic.MultiplyMagnitudes(
 439378            desiredPrimaryNumerator,
 439379            secondaryDenominator,
 439380            primaryAtCommon);
 439381        WideArithmetic.MultiplyMagnitudes(
 439382            desiredSecondaryNumerator,
 439383            primaryDenominator,
 439384            secondaryAtCommon);
 385
 439386        Span<ulong> magnitudeSquared = stackalloc ulong[MaxCoulombSquareWords];
 439387        Span<ulong> square = stackalloc ulong[MaxCoulombSquareWords];
 439388        WideArithmetic.MultiplyMagnitudes(
 439389            primaryAtCommon,
 439390            primaryAtCommon,
 439391            magnitudeSquared);
 439392        WideArithmetic.MultiplyMagnitudes(
 439393            secondaryAtCommon,
 439394            secondaryAtCommon,
 439395            square);
 439396        WideArithmetic.AddMagnitudeInto(square, magnitudeSquared);
 397
 439398        Span<ulong> staticNumerator = stackalloc ulong[MaxResponseWords];
 439399        Span<ulong> staticDenominator = stackalloc ulong[MaxResponseWords];
 439400        GetFrictionLimit(
 439401            normalNumerator,
 439402            normalDenominator,
 439403            staticFriction,
 439404            staticNumerator,
 439405            staticDenominator);
 439406        bool withinStaticLimit = IsVectorWithinLimit(
 439407            magnitudeSquared,
 439408            commonDenominator,
 439409            staticNumerator,
 439410            staticDenominator);
 411
 439412        Span<ulong> dynamicNumerator = stackalloc ulong[MaxResponseWords];
 439413        Span<ulong> dynamicDenominator = stackalloc ulong[MaxResponseWords];
 439414        if (!withinStaticLimit)
 415        {
 225416            GetFrictionLimit(
 225417                normalNumerator,
 225418                normalDenominator,
 225419                dynamicFriction,
 225420                dynamicNumerator,
 225421                dynamicDenominator);
 422        }
 423
 439424        bool useDynamicProjection =
 439425            !withinStaticLimit
 439426            && !WideArithmetic.IsZeroMagnitude(magnitudeSquared)
 439427            && !WideArithmetic.IsZeroMagnitude(dynamicNumerator);
 428        bool hasAppliedImpulse;
 429        bool resolved;
 430        Vector3d firstLinear;
 431        Vector3d firstAngular;
 432        Vector3d secondLinear;
 433        Vector3d secondAngular;
 439434        if (useDynamicProjection)
 435        {
 217436            hasAppliedImpulse =
 217437                !IsRadialProjectionEqualToFixed(
 217438                    primaryAtCommon,
 217439                    desiredPrimarySign,
 217440                    dynamicNumerator,
 217441                    dynamicDenominator,
 217442                    magnitudeSquared,
 217443                    accumulatedPrimaryTangentImpulse)
 217444                || !IsRadialProjectionEqualToFixed(
 217445                    secondaryAtCommon,
 217446                    desiredSecondarySign,
 217447                    dynamicNumerator,
 217448                    dynamicDenominator,
 217449                    magnitudeSquared,
 217450                    accumulatedSecondaryTangentImpulse);
 217451            resolved = TryGetDiskVelocityDeltas(
 217452                primaryFirst,
 217453                primarySecond,
 217454                secondaryFirst,
 217455                secondarySecond,
 217456                primaryTangent,
 217457                secondaryTangent,
 217458                primaryAtCommon,
 217459                desiredPrimarySign,
 217460                secondaryAtCommon,
 217461                desiredSecondarySign,
 217462                commonDenominator,
 217463                rational: false,
 217464                dynamicNumerator,
 217465                dynamicDenominator,
 217466                magnitudeSquared,
 217467                accumulatedPrimaryTangentImpulse,
 217468                accumulatedSecondaryTangentImpulse,
 217469                out firstLinear,
 217470                out firstAngular,
 217471                out secondLinear,
 217472                out secondAngular);
 473        }
 474        else
 475        {
 222476            Span<ulong> zero = stackalloc ulong[MaxCoulombWords];
 222477            Span<ulong> appliedPrimaryNumerator =
 222478                stackalloc ulong[MaxCoulombWords];
 222479            Span<ulong> appliedDenominator =
 222480                stackalloc ulong[MaxCoulombWords];
 222481            Span<ulong> appliedSecondaryNumerator =
 222482                stackalloc ulong[MaxCoulombWords];
 222483            zero.Clear();
 222484            SubtractFixedFromRatio(
 222485                withinStaticLimit ? primaryAtCommon : zero,
 222486                commonDenominator,
 222487                withinStaticLimit ? desiredPrimarySign : 0,
 222488                accumulatedPrimaryTangentImpulse,
 222489                appliedPrimaryNumerator,
 222490                appliedDenominator,
 222491                out int appliedPrimarySign);
 222492            SubtractFixedFromRatio(
 222493                withinStaticLimit ? secondaryAtCommon : zero,
 222494                commonDenominator,
 222495                withinStaticLimit ? desiredSecondarySign : 0,
 222496                accumulatedSecondaryTangentImpulse,
 222497                appliedSecondaryNumerator,
 222498                appliedDenominator,
 222499                out int appliedSecondarySign);
 222500            hasAppliedImpulse =
 222501                appliedPrimarySign != 0
 222502                || appliedSecondarySign != 0;
 222503            resolved = TryGetDiskVelocityDeltas(
 222504                primaryFirst,
 222505                primarySecond,
 222506                secondaryFirst,
 222507                secondarySecond,
 222508                primaryTangent,
 222509                secondaryTangent,
 222510                appliedPrimaryNumerator,
 222511                appliedPrimarySign,
 222512                appliedSecondaryNumerator,
 222513                appliedSecondarySign,
 222514                appliedDenominator,
 222515                rational: true,
 222516                dynamicNumerator,
 222517                dynamicDenominator,
 222518                magnitudeSquared,
 222519                Fixed64.Zero,
 222520                Fixed64.Zero,
 222521                out firstLinear,
 222522                out firstAngular,
 222523                out secondLinear,
 222524                out secondAngular);
 525        }
 439526        if (!resolved)
 6527            return false;
 528
 529        bool hasPrimaryProjection;
 530        bool hasSecondaryProjection;
 531        Fixed64 primaryProjection;
 532        Fixed64 secondaryProjection;
 433533        if (withinStaticLimit)
 534        {
 213535            hasPrimaryProjection = Fixed64.TryGetSignedRawRatio(
 213536                primaryAtCommon,
 213537                commonDenominator,
 213538                desiredPrimarySign < 0,
 213539                out primaryProjection);
 213540            hasSecondaryProjection = Fixed64.TryGetSignedRawRatio(
 213541                secondaryAtCommon,
 213542                commonDenominator,
 213543                desiredSecondarySign < 0,
 213544                out secondaryProjection);
 545        }
 220546        else if (useDynamicProjection)
 547        {
 214548            Span<ulong> projectedNumerator =
 214549                stackalloc ulong[MaxCoulombWords + MaxResponseWords];
 214550            WideArithmetic.MultiplyMagnitudes(
 214551                primaryAtCommon,
 214552                dynamicNumerator,
 214553                projectedNumerator);
 214554            hasPrimaryProjection = TryGetSignedRatioOverSquareRoot(
 214555                projectedNumerator,
 214556                dynamicDenominator,
 214557                magnitudeSquared,
 214558                desiredPrimarySign < 0,
 214559                out primaryProjection);
 214560            WideArithmetic.MultiplyMagnitudes(
 214561                secondaryAtCommon,
 214562                dynamicNumerator,
 214563                projectedNumerator);
 214564            hasSecondaryProjection = TryGetSignedRatioOverSquareRoot(
 214565                projectedNumerator,
 214566                dynamicDenominator,
 214567                magnitudeSquared,
 214568                desiredSecondarySign < 0,
 214569                out secondaryProjection);
 570        }
 571        else
 572        {
 6573            hasPrimaryProjection = true;
 6574            hasSecondaryProjection = true;
 6575            primaryProjection = Fixed64.Zero;
 6576            secondaryProjection = Fixed64.Zero;
 577        }
 578
 433579        response = new ExactCoulombResponse3D(
 433580            hasAppliedImpulse,
 433581            firstLinear,
 433582            firstAngular,
 433583            secondLinear,
 433584            secondAngular,
 433585            hasPrimaryProjection,
 433586            primaryProjection,
 433587            hasSecondaryProjection,
 433588            secondaryProjection);
 433589        return true;
 590    }
 591
 592    private static bool AreCoulombDiskInputsValid(
 593        Vector3d normal,
 594        in ExactContactResponseOperand3D primaryFirst,
 595        in ExactContactResponseOperand3D primarySecond,
 596        Vector3d primaryTangent,
 597        in ExactContactResponseOperand3D secondaryFirst,
 598        in ExactContactResponseOperand3D secondarySecond,
 599        Vector3d secondaryTangent,
 600        Fixed64 staticFriction,
 601        Fixed64 dynamicFriction) =>
 444602        staticFriction >= Fixed64.Zero
 444603        & dynamicFriction >= Fixed64.Zero
 444604        & primaryFirst.Lever.Denominator.Sign != 0
 444605        & primarySecond.Lever.Denominator.Sign != 0
 444606        & primaryFirst.InverseMass >= Fixed64.Zero
 444607        & primarySecond.InverseMass >= Fixed64.Zero
 444608        & normal.IsNormalized()
 444609        & primaryTangent.IsNormalized()
 444610        & secondaryTangent.IsNormalized()
 444611        & FixedMath.Abs(Vector3d.Dot(
 444612            primaryTangent,
 444613            secondaryTangent)) <= Fixed64.Epsilon
 444614        & FixedMath.Abs(Vector3d.Dot(
 444615            normal,
 444616            primaryTangent)) <= Fixed64.Epsilon
 444617        & FixedMath.Abs(Vector3d.Dot(
 444618            normal,
 444619            secondaryTangent)) <= Fixed64.Epsilon
 444620        & HaveMatchingParticipants(primaryFirst, secondaryFirst)
 444621        & HaveMatchingParticipants(primarySecond, secondarySecond);
 622
 623    private static bool TryGetCompletedNormalAccumulatorRatio(
 624        in ExactNormalConstraint3D constraint,
 625        Span<ulong> numerator,
 626        Span<ulong> denominator)
 627    {
 110628        numerator.Clear();
 110629        denominator.Clear();
 110630        ExactContactResponseOperand3D first = constraint.First;
 110631        ExactContactResponseOperand3D second = constraint.Second;
 110632        bool inputsValid =
 110633            first.Lever.Denominator.Sign != 0
 110634            & second.Lever.Denominator.Sign != 0
 110635            & constraint.Restitution >= Fixed64.Zero
 110636            & constraint.RestitutionVelocityThreshold >= Fixed64.Zero
 110637            & constraint.AccumulatedImpulse >= Fixed64.Zero
 110638            & constraint.PositiveImpulseScale >= Fixed64.Zero
 110639            & constraint.NegativeImpulseScale >= Fixed64.Zero
 110640            & first.InverseMass >= Fixed64.Zero
 110641            & second.InverseMass >= Fixed64.Zero
 110642            & constraint.Normal.IsNormalized();
 110643        if (!inputsValid)
 2644            return false;
 645
 108646        ExactLever3D.GetRelativePointVelocityRatio(
 108647            first.LinearVelocity,
 108648            first.AngularVelocity,
 108649            first.Lever,
 108650            second.LinearVelocity,
 108651            second.AngularVelocity,
 108652            second.Lever,
 108653            constraint.Normal,
 108654            out Signed832 velocityNumerator,
 108655            out Signed832 velocityDenominator);
 656
 108657        Span<ulong> effectiveNumerator = stackalloc ulong[MaxResponseWords];
 108658        Span<ulong> effectiveDenominator = stackalloc ulong[MaxResponseWords];
 108659        GetEffectiveMassRatio(
 108660            first,
 108661            second,
 108662            constraint.Normal,
 108663            effectiveNumerator,
 108664            effectiveDenominator);
 108665        int velocitySign =
 108666            velocityNumerator.Sign * velocityDenominator.Sign;
 108667        if (velocitySign == 0
 108668            || WideArithmetic.IsZeroMagnitude(effectiveNumerator))
 669        {
 12670            SetMagnitude(constraint.AccumulatedImpulse, numerator);
 12671            denominator[0] = 1UL;
 12672            return true;
 673        }
 674
 96675        bool hasVelocityProjection = Fixed64.TryGetSignedRawRatio(
 96676            velocityNumerator,
 96677            velocityDenominator,
 96678            0,
 96679            out Fixed64 velocityProjection);
 96680        bool applyRestitution = velocitySign < 0
 96681            & (!hasVelocityProjection
 96682                | velocityProjection
 96683                    < -constraint.RestitutionVelocityThreshold);
 96684        Fixed64 scale = velocitySign < 0
 96685            ? constraint.PositiveImpulseScale
 96686            : constraint.NegativeImpulseScale;
 96687        Span<ulong> impulseNumerator = stackalloc ulong[MaxResponseWords];
 96688        Span<ulong> impulseDenominator = stackalloc ulong[MaxResponseWords];
 96689        BuildImpulseRatio(
 96690            velocityNumerator,
 96691            velocityDenominator,
 96692            effectiveNumerator,
 96693            effectiveDenominator,
 96694            applyRestitution ? constraint.Restitution : Fixed64.Zero,
 96695            scale,
 96696            impulseNumerator,
 96697            impulseDenominator);
 698
 96699        Span<ulong> accumulatorMagnitude =
 96700            stackalloc ulong[MaxResponseWords];
 96701        Span<ulong> accumulatorAtImpulseDenominator =
 96702            stackalloc ulong[MaxResponseWords];
 96703        SetMagnitude(constraint.AccumulatedImpulse, accumulatorMagnitude);
 96704        WideArithmetic.MultiplyMagnitudes(
 96705            accumulatorMagnitude,
 96706            impulseDenominator,
 96707            accumulatorAtImpulseDenominator);
 96708        int impulseSign = -velocitySign;
 96709        if (impulseSign < 0
 96710            && WideArithmetic.CompareMagnitudeEqualLength(
 96711                impulseNumerator,
 96712                accumulatorAtImpulseDenominator) >= 0)
 713        {
 1714            denominator[0] = 1UL;
 1715            return true;
 716        }
 717
 95718        accumulatorAtImpulseDenominator.CopyTo(numerator);
 95719        if (impulseSign > 0)
 93720            WideArithmetic.AddMagnitudeInto(impulseNumerator, numerator);
 721        else
 2722            WideArithmetic.SubtractEqualMagnitudes(
 2723                accumulatorAtImpulseDenominator,
 2724                impulseNumerator,
 2725                numerator);
 95726        impulseDenominator.CopyTo(denominator);
 95727        return true;
 728    }
 729
 730    private static void GetBilateralImpulseRatio(
 731        in ExactContactResponseOperand3D first,
 732        in ExactContactResponseOperand3D second,
 733        Vector3d tangent,
 734        Fixed64 velocityDeadzone,
 735        Span<ulong> numerator,
 736        Span<ulong> denominator,
 737        out int sign)
 738    {
 931739        numerator.Clear();
 931740        denominator.Clear();
 931741        sign = 0;
 931742        ExactLever3D.GetRelativePointVelocityRatio(
 931743            first.LinearVelocity,
 931744            first.AngularVelocity,
 931745            first.Lever,
 931746            second.LinearVelocity,
 931747            second.AngularVelocity,
 931748            second.Lever,
 931749            tangent,
 931750            out Signed832 velocityNumerator,
 931751            out Signed832 velocityDenominator);
 752
 931753        Span<ulong> effectiveNumerator = stackalloc ulong[MaxResponseWords];
 931754        Span<ulong> effectiveDenominator = stackalloc ulong[MaxResponseWords];
 931755        GetEffectiveMassRatio(
 931756            first,
 931757            second,
 931758            tangent,
 931759            effectiveNumerator,
 931760            effectiveDenominator);
 931761        int velocitySign =
 931762            velocityNumerator.Sign * velocityDenominator.Sign;
 931763        bool suppressVelocity = velocityDeadzone != Fixed64.Zero
 931764            && Fixed64.TryGetSignedRawRatio(
 931765                velocityNumerator,
 931766                velocityDenominator,
 931767                0,
 931768                out Fixed64 velocityProjection)
 931769            && velocityProjection >= -velocityDeadzone
 931770            && velocityProjection <= velocityDeadzone;
 931771        if (velocitySign == 0
 931772            || suppressVelocity
 931773            || WideArithmetic.IsZeroMagnitude(effectiveNumerator))
 774        {
 467775            denominator[0] = 1UL;
 467776            return;
 777        }
 778
 464779        BuildImpulseRatio(
 464780            velocityNumerator,
 464781            velocityDenominator,
 464782            effectiveNumerator,
 464783            effectiveDenominator,
 464784            Fixed64.Zero,
 464785            Fixed64.One,
 464786            numerator,
 464787        denominator);
 464788        sign = -velocitySign;
 464789    }
 790
 791    private static void GetFrictionLimit(
 792        ReadOnlySpan<ulong> normalNumerator,
 793        ReadOnlySpan<ulong> normalDenominator,
 794        Fixed64 friction,
 795        Span<ulong> numerator,
 796        Span<ulong> denominator)
 797    {
 733798        Span<ulong> frictionMagnitude =
 733799            stackalloc ulong[3];
 733800        Span<ulong> fixedScale =
 733801            stackalloc ulong[1];
 733802        SetMagnitude(friction, frictionMagnitude);
 733803        fixedScale.Clear();
 733804        fixedScale[0] = (ulong)FixedMath.ONE_L;
 733805        WideArithmetic.MultiplyMagnitudes(
 733806            normalNumerator,
 733807            frictionMagnitude,
 733808            numerator);
 733809        WideArithmetic.MultiplyMagnitudes(
 733810            normalDenominator,
 733811            fixedScale,
 733812            denominator);
 733813    }
 814
 815    private static void AddFixedToRatio(
 816        ReadOnlySpan<ulong> ratioNumerator,
 817        ReadOnlySpan<ulong> ratioDenominator,
 818        int ratioSign,
 819        Fixed64 value,
 820        Span<ulong> resultNumerator,
 821        out int resultSign)
 822    {
 931823        Span<ulong> valueMagnitude = stackalloc ulong[3];
 931824        Span<ulong> valueAtDenominator =
 931825            stackalloc ulong[MaxResponseWords];
 931826        SetMagnitude(value, valueMagnitude);
 931827        WideArithmetic.MultiplyMagnitudes(
 931828            valueMagnitude,
 931829            ratioDenominator,
 931830            valueAtDenominator);
 931831        AddSignedMagnitudes(
 931832            ratioNumerator,
 931833            ratioSign,
 931834            valueAtDenominator,
 931835            value == Fixed64.Zero ? 0 : value < Fixed64.Zero ? -1 : 1,
 931836            resultNumerator,
 931837            out resultSign);
 931838    }
 839
 840    private static void SubtractFixedFromRatio(
 841        ReadOnlySpan<ulong> ratioNumerator,
 842        ReadOnlySpan<ulong> ratioDenominator,
 843        int ratioSign,
 844        Fixed64 value,
 845        Span<ulong> resultNumerator,
 846        Span<ulong> resultDenominator,
 847        out int resultSign)
 848    {
 460849        Span<ulong> valueMagnitude = stackalloc ulong[3];
 460850        Span<ulong> valueAtDenominator =
 460851            stackalloc ulong[MaxCoulombWords];
 460852        SetMagnitude(value, valueMagnitude);
 460853        WideArithmetic.MultiplyMagnitudes(
 460854            valueMagnitude,
 460855            ratioDenominator,
 460856            valueAtDenominator);
 460857        AddSignedMagnitudes(
 460858            ratioNumerator,
 460859            ratioSign,
 460860            valueAtDenominator,
 460861            value == Fixed64.Zero ? 0 : value < Fixed64.Zero ? 1 : -1,
 460862            resultNumerator,
 460863            out resultSign);
 460864        resultDenominator.Clear();
 460865        ratioDenominator.CopyTo(resultDenominator);
 460866    }
 867
 868    private static int CompareRatios(
 869        ReadOnlySpan<ulong> firstNumerator,
 870        ReadOnlySpan<ulong> firstDenominator,
 871        ReadOnlySpan<ulong> secondNumerator,
 872        ReadOnlySpan<ulong> secondDenominator)
 873    {
 53874        Span<ulong> first = stackalloc ulong[MaxCoulombWords];
 53875        Span<ulong> second = stackalloc ulong[MaxCoulombWords];
 53876        WideArithmetic.MultiplyMagnitudes(
 53877            firstNumerator,
 53878            secondDenominator,
 53879            first);
 53880        WideArithmetic.MultiplyMagnitudes(
 53881            secondNumerator,
 53882            firstDenominator,
 53883            second);
 53884        return WideArithmetic.CompareMagnitudeEqualLength(first, second);
 885    }
 886
 887    private static bool IsVectorWithinLimit(
 888        ReadOnlySpan<ulong> vectorMagnitudeSquared,
 889        ReadOnlySpan<ulong> vectorDenominator,
 890        ReadOnlySpan<ulong> limitNumerator,
 891        ReadOnlySpan<ulong> limitDenominator)
 892    {
 439893        Span<ulong> denominatorSquared =
 439894            stackalloc ulong[MaxCoulombSquareWords];
 439895        Span<ulong> limitNumeratorSquared =
 439896            stackalloc ulong[MaxCoulombSquareWords];
 439897        Span<ulong> limitDenominatorSquared =
 439898            stackalloc ulong[MaxCoulombSquareWords];
 439899        Span<ulong> left = stackalloc ulong[MaxCoulombComparisonWords];
 439900        Span<ulong> right = stackalloc ulong[MaxCoulombComparisonWords];
 439901        WideArithmetic.MultiplyMagnitudes(
 439902            vectorDenominator,
 439903            vectorDenominator,
 439904            denominatorSquared);
 439905        WideArithmetic.MultiplyMagnitudes(
 439906            limitNumerator,
 439907            limitNumerator,
 439908            limitNumeratorSquared);
 439909        WideArithmetic.MultiplyMagnitudes(
 439910            limitDenominator,
 439911            limitDenominator,
 439912            limitDenominatorSquared);
 439913        WideArithmetic.MultiplyMagnitudes(
 439914            vectorMagnitudeSquared,
 439915            limitDenominatorSquared,
 439916            left);
 439917        WideArithmetic.MultiplyMagnitudes(
 439918            limitNumeratorSquared,
 439919            denominatorSquared,
 439920            right);
 439921        return WideArithmetic.CompareMagnitudeEqualLength(left, right) <= 0;
 922    }
 923
 924    private static void AddSignedMagnitudes(
 925        ReadOnlySpan<ulong> first,
 926        int firstSign,
 927        ReadOnlySpan<ulong> second,
 928        int secondSign,
 929        Span<ulong> result,
 930        out int resultSign)
 931    {
 64205932        result.Clear();
 64205933        resultSign = 0;
 64205934        if (firstSign != 0 & !WideArithmetic.IsZeroMagnitude(first))
 50021935            WideArithmetic.AddSignedMagnitude(
 50021936                first,
 50021937                firstSign,
 50021938                result,
 50021939                ref resultSign);
 64205940        if (secondSign != 0 & !WideArithmetic.IsZeroMagnitude(second))
 54775941            WideArithmetic.AddSignedMagnitude(
 54775942                second,
 54775943                secondSign,
 54775944                result,
 54775945                ref resultSign);
 64205946    }
 947
 948    private static bool TryGetSignedRatioOverSquareRoot(
 949        ReadOnlySpan<ulong> numerator,
 950        ReadOnlySpan<ulong> denominator,
 951        ReadOnlySpan<ulong> radicand,
 952        bool negative,
 953        out Fixed64 result)
 954    {
 428955        if (WideArithmetic.IsZeroMagnitude(numerator))
 956        {
 206957            result = Fixed64.Zero;
 206958            return true;
 959        }
 960
 222961        ulong limit = negative ? 1UL << 63 : (ulong)long.MaxValue;
 222962        if (CompareRatioOverSquareRoot(
 222963                numerator,
 222964                denominator,
 222965                radicand,
 222966                limit) > 0)
 967        {
 2968            result = default;
 2969            return false;
 970        }
 971
 220972        ulong low = 0UL;
 220973        ulong high = limit;
 14083974        while (low < high)
 975        {
 13863976            ulong difference = high - low;
 13863977            ulong middle = low + (difference >> 1) + (difference & 1UL);
 13863978            if (CompareRatioOverSquareRoot(
 13863979                    numerator,
 13863980                    denominator,
 13863981                    radicand,
 13863982                    middle) >= 0)
 983            {
 3394984                low = middle;
 985            }
 986            else
 987            {
 10469988                high = middle - 1UL;
 989            }
 990        }
 991
 220992        ulong quotient = low;
 220993        Span<ulong> doubledQuotient = stackalloc ulong[2];
 220994        doubledQuotient[0] = (quotient << 1) | 1UL;
 220995        doubledQuotient[1] = quotient >> 63;
 220996        int midpointComparison = CompareRatioOverSquareRoot(
 220997            numerator,
 220998            denominator,
 220999            radicand,
 2201000            doubledQuotient,
 2201001            numeratorMultiplier: 2UL);
 2201002        bool roundUp = midpointComparison > 0
 2201003            | (midpointComparison == 0 & (quotient & 1UL) != 0UL);
 2201004        if (roundUp & quotient < limit)
 1005        {
 101006            quotient++;
 1007        }
 1008
 2201009        result = new Fixed64(
 2201010            negative ? unchecked(-(long)quotient) : (long)quotient);
 2201011        return true;
 1012    }
 1013
 1014    private static int CompareRatioOverSquareRoot(
 1015        ReadOnlySpan<ulong> numerator,
 1016        ReadOnlySpan<ulong> denominator,
 1017        ReadOnlySpan<ulong> radicand,
 1018        ulong candidate)
 1019    {
 140851020        Span<ulong> candidateMagnitude = stackalloc ulong[1];
 140851021        candidateMagnitude.Clear();
 140851022        candidateMagnitude[0] = candidate;
 140851023        return CompareRatioOverSquareRoot(
 140851024            numerator,
 140851025            denominator,
 140851026            radicand,
 140851027            candidateMagnitude,
 140851028            numeratorMultiplier: 1UL);
 1029    }
 1030
 1031    private static int CompareRatioOverSquareRoot(
 1032        ReadOnlySpan<ulong> numerator,
 1033        ReadOnlySpan<ulong> denominator,
 1034        ReadOnlySpan<ulong> radicand,
 1035        ReadOnlySpan<ulong> candidate,
 1036        ulong numeratorMultiplier)
 1037    {
 143051038        Span<ulong> multiplier = stackalloc ulong[1];
 143051039        multiplier.Clear();
 143051040        multiplier[0] = numeratorMultiplier;
 143051041        Span<ulong> scaledNumerator =
 143051042            stackalloc ulong[(MaxCoulombComparisonWords / 2) + 1];
 143051043        Span<ulong> left = stackalloc ulong[MaxCoulombComparisonWords];
 143051044        Span<ulong> denominatorTimesCandidate =
 143051045            stackalloc ulong[(MaxCoulombComparisonWords / 2) + 1];
 143051046        Span<ulong> rightBase =
 143051047            stackalloc ulong[MaxCoulombComparisonWords];
 143051048        Span<ulong> right = stackalloc ulong[MaxCoulombComparisonWords];
 143051049        WideArithmetic.MultiplyMagnitudes(
 143051050            numerator,
 143051051            multiplier,
 143051052            scaledNumerator);
 143051053        WideArithmetic.MultiplyMagnitudes(
 143051054            scaledNumerator,
 143051055            scaledNumerator,
 143051056            left);
 143051057        WideArithmetic.MultiplyMagnitudes(
 143051058            denominator,
 143051059            candidate,
 143051060            denominatorTimesCandidate);
 143051061        WideArithmetic.MultiplyMagnitudes(
 143051062            denominatorTimesCandidate,
 143051063            denominatorTimesCandidate,
 143051064            rightBase);
 143051065        WideArithmetic.MultiplyMagnitudes(rightBase, radicand, right);
 143051066        return WideArithmetic.CompareMagnitudeEqualLength(left, right);
 1067    }
 1068
 1069    private static bool HaveMatchingParticipants(
 1070        in ExactContactResponseOperand3D first,
 1071        in ExactContactResponseOperand3D second) =>
 12361072        first.Lever.XNumerator.Equals(second.Lever.XNumerator)
 12361073        & first.Lever.YNumerator.Equals(second.Lever.YNumerator)
 12361074        & first.Lever.ZNumerator.Equals(second.Lever.ZNumerator)
 12361075        & first.Lever.Denominator.Equals(second.Lever.Denominator)
 12361076        & first.InverseMass == second.InverseMass
 12361077        & first.InverseInertia == second.InverseInertia;
 1078}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/Exact/ExactContactResponseKernel.CoulombDiskCache.cs

#LineLine coverage
 1//=======================================================================
 2// ExactContactResponseKernel.CoulombDiskCache.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 System;
 9
 10using FixedMathSharp;
 11
 12namespace Gravitas.CollisionHandling;
 13
 14/// <content>
 15/// Owns exact cached-disk projection comparison and materialization.
 16/// </content>
 17internal static partial class ExactContactResponseKernel
 18{
 19    private static bool IsRadialProjectionEqualToFixed(
 20        ReadOnlySpan<ulong> directionNumerator,
 21        int directionSign,
 22        ReadOnlySpan<ulong> radialNumerator,
 23        ReadOnlySpan<ulong> radialDenominator,
 24        ReadOnlySpan<ulong> radicand,
 25        Fixed64 value)
 26    {
 41127        Span<ulong> projectedNumerator =
 41128            stackalloc ulong[MaxCoulombComparisonWords / 2];
 41129        Span<ulong> valueMagnitude = stackalloc ulong[3];
 41130        Span<ulong> fixedDenominator = stackalloc ulong[1];
 41131        WideArithmetic.MultiplyMagnitudes(
 41132            directionNumerator,
 41133            radialNumerator,
 41134            projectedNumerator);
 41135        SetMagnitude(value, valueMagnitude);
 41136        fixedDenominator.Clear();
 41137        fixedDenominator[0] = 1UL;
 41138        int valueSign =
 41139            value == Fixed64.Zero ? 0 : value < Fixed64.Zero ? -1 : 1;
 41140        return CompareSignedRadicalAndRationalSumToMagnitude(
 41141                projectedNumerator,
 41142                radialDenominator,
 41143                radicand,
 41144                directionSign,
 41145                valueMagnitude,
 41146                fixedDenominator,
 41147                -valueSign,
 41148                candidate: 0UL)
 41149            == 0;
 50    }
 51
 52    internal static bool TryGetSignedRadicalAndRationalSum(
 53        ReadOnlySpan<ulong> radicalNumerator,
 54        ReadOnlySpan<ulong> radicalDenominator,
 55        ReadOnlySpan<ulong> radicand,
 56        int radicalSign,
 57        ReadOnlySpan<ulong> rationalNumerator,
 58        ReadOnlySpan<ulong> rationalDenominator,
 59        int rationalSign,
 60        out Fixed64 result)
 61    {
 256662        int resultSign = CompareSignedRadicalAndRationalSumToMagnitude(
 256663            radicalNumerator,
 256664            radicalDenominator,
 256665            radicand,
 256666            radicalSign,
 256667            rationalNumerator,
 256668            rationalDenominator,
 256669            rationalSign,
 256670            candidate: 0UL);
 256671        if (resultSign == 0)
 72        {
 176073            result = Fixed64.Zero;
 176074            return true;
 75        }
 76
 80677        int normalizedRadicalSign =
 80678            resultSign < 0 ? -radicalSign : radicalSign;
 80679        int normalizedRationalSign =
 80680            resultSign < 0 ? -rationalSign : rationalSign;
 80681        ulong limit =
 80682            resultSign < 0 ? 1UL << 63 : (ulong)long.MaxValue;
 80683        Span<ulong> doubledLimit = stackalloc ulong[2];
 80684        doubledLimit[0] = (limit << 1) | 1UL;
 80685        doubledLimit[1] = limit >> 63;
 80686        int limitMidpointComparison =
 80687            CompareSignedRadicalAndRationalSumToMagnitude(
 80688                radicalNumerator,
 80689                radicalDenominator,
 80690                radicand,
 80691                normalizedRadicalSign,
 80692                rationalNumerator,
 80693                rationalDenominator,
 80694                normalizedRationalSign,
 80695                doubledLimit,
 80696                expressionMultiplier: 2UL);
 80697        if (limitMidpointComparison > 0
 80698            || (limitMidpointComparison == 0
 80699                && (limit & 1UL) != 0UL))
 100        {
 6101            result = default;
 6102            return false;
 103        }
 104
 800105        ulong low = 0UL;
 800106        ulong high = limit;
 51201107        while (low < high)
 108        {
 50401109            ulong difference = high - low;
 50401110            ulong middle =
 50401111                low + (difference >> 1) + (difference & 1UL);
 50401112            if (CompareSignedRadicalAndRationalSumToMagnitude(
 50401113                    radicalNumerator,
 50401114                    radicalDenominator,
 50401115                    radicand,
 50401116                    normalizedRadicalSign,
 50401117                    rationalNumerator,
 50401118                    rationalDenominator,
 50401119                    normalizedRationalSign,
 50401120                    middle) >= 0)
 121            {
 11761122                low = middle;
 123            }
 124            else
 125            {
 38640126                high = middle - 1UL;
 127            }
 128        }
 129
 800130        ulong quotient = low;
 800131        Span<ulong> doubledQuotient = stackalloc ulong[2];
 800132        doubledQuotient[0] = (quotient << 1) | 1UL;
 800133        doubledQuotient[1] = quotient >> 63;
 800134        int midpointComparison =
 800135            CompareSignedRadicalAndRationalSumToMagnitude(
 800136                radicalNumerator,
 800137                radicalDenominator,
 800138                radicand,
 800139                normalizedRadicalSign,
 800140                rationalNumerator,
 800141                rationalDenominator,
 800142                normalizedRationalSign,
 800143                doubledQuotient,
 800144                expressionMultiplier: 2UL);
 800145        bool roundUp = midpointComparison > 0
 800146            | (midpointComparison == 0 & (quotient & 1UL) != 0UL);
 800147        if (roundUp & quotient < limit)
 244148            quotient++;
 149
 800150        result = new Fixed64(
 800151            resultSign < 0
 800152                ? unchecked(-(long)quotient)
 800153                : (long)quotient);
 800154        return true;
 155    }
 156
 157    private static int CompareSignedRadicalAndRationalSumToMagnitude(
 158        ReadOnlySpan<ulong> radicalNumerator,
 159        ReadOnlySpan<ulong> radicalDenominator,
 160        ReadOnlySpan<ulong> radicand,
 161        int radicalSign,
 162        ReadOnlySpan<ulong> rationalNumerator,
 163        ReadOnlySpan<ulong> rationalDenominator,
 164        int rationalSign,
 165        ulong candidate)
 166    {
 53378167        Span<ulong> candidateMagnitude = stackalloc ulong[1];
 53378168        candidateMagnitude[0] = candidate;
 53378169        return CompareSignedRadicalAndRationalSumToMagnitude(
 53378170            radicalNumerator,
 53378171            radicalDenominator,
 53378172            radicand,
 53378173            radicalSign,
 53378174            rationalNumerator,
 53378175            rationalDenominator,
 53378176            rationalSign,
 53378177            candidateMagnitude,
 53378178            expressionMultiplier: 1UL);
 179    }
 180
 181    private static int CompareSignedRadicalAndRationalSumToMagnitude(
 182        ReadOnlySpan<ulong> radicalNumerator,
 183        ReadOnlySpan<ulong> radicalDenominator,
 184        ReadOnlySpan<ulong> radicand,
 185        int radicalSign,
 186        ReadOnlySpan<ulong> rationalNumerator,
 187        ReadOnlySpan<ulong> rationalDenominator,
 188        int rationalSign,
 189        ReadOnlySpan<ulong> candidate,
 190        ulong expressionMultiplier)
 191    {
 54984192        Span<ulong> multiplier = stackalloc ulong[1];
 54984193        Span<ulong> scaledRadicalNumerator =
 54984194            stackalloc ulong[MaxCoulombComparisonWords];
 54984195        Span<ulong> scaledRationalNumerator =
 54984196            stackalloc ulong[MaxCoulombComparisonWords];
 54984197        Span<ulong> candidateAtDenominator =
 54984198            stackalloc ulong[MaxCoulombComparisonWords];
 54984199        Span<ulong> adjustedRationalNumerator =
 54984200            stackalloc ulong[MaxCoulombComparisonWords];
 54984201        Span<ulong> rationalTerm =
 54984202            stackalloc ulong[MaxCoulombComparisonWords];
 54984203        Span<ulong> radicalCoefficient =
 54984204            stackalloc ulong[MaxCoulombComparisonWords];
 54984205        multiplier[0] = expressionMultiplier;
 54984206        WideArithmetic.MultiplyMagnitudes(
 54984207            radicalNumerator,
 54984208            multiplier,
 54984209            scaledRadicalNumerator);
 54984210        WideArithmetic.MultiplyMagnitudes(
 54984211            rationalNumerator,
 54984212            multiplier,
 54984213            scaledRationalNumerator);
 54984214        WideArithmetic.MultiplyMagnitudes(
 54984215            candidate,
 54984216            rationalDenominator,
 54984217            candidateAtDenominator);
 54984218        AddSignedMagnitudes(
 54984219            scaledRationalNumerator,
 54984220            rationalSign,
 54984221            candidateAtDenominator,
 54984222            -1,
 54984223            adjustedRationalNumerator,
 54984224            out int adjustedRationalSign);
 54984225        WideArithmetic.MultiplyMagnitudes(
 54984226            scaledRadicalNumerator,
 54984227            rationalDenominator,
 54984228            rationalTerm);
 54984229        WideArithmetic.MultiplyMagnitudes(
 54984230            adjustedRationalNumerator,
 54984231            radicalDenominator,
 54984232            radicalCoefficient);
 54984233        return WideArithmetic.GetSignedMagnitudeAndSquareRootSign(
 54984234            TrimMagnitude(rationalTerm),
 54984235            radicalSign,
 54984236            TrimMagnitude(radicalCoefficient),
 54984237            adjustedRationalSign,
 54984238            TrimMagnitude(radicand));
 239    }
 240
 241    private static void GetWeightedFixedSum(
 242        Fixed64 firstFactor,
 243        Fixed64 firstValue,
 244        Fixed64 secondFactor,
 245        Fixed64 secondValue,
 246        Span<ulong> result,
 247        out int resultSign)
 248    {
 1260249        Span<ulong> factorMagnitude = stackalloc ulong[3];
 1260250        Span<ulong> valueMagnitude = stackalloc ulong[3];
 1260251        Span<ulong> first = stackalloc ulong[3];
 1260252        Span<ulong> second = stackalloc ulong[3];
 1260253        SetMagnitude(firstFactor, factorMagnitude);
 1260254        SetMagnitude(firstValue, valueMagnitude);
 1260255        WideArithmetic.MultiplyMagnitudes(
 1260256            factorMagnitude,
 1260257            valueMagnitude,
 1260258            first);
 1260259        SetMagnitude(secondFactor, factorMagnitude);
 1260260        SetMagnitude(secondValue, valueMagnitude);
 1260261        WideArithmetic.MultiplyMagnitudes(
 1260262            factorMagnitude,
 1260263            valueMagnitude,
 1260264            second);
 1260265        AddSignedMagnitudes(
 1260266            first,
 1260267            GetProductSign(firstFactor, firstValue),
 1260268            second,
 1260269            GetProductSign(secondFactor, secondValue),
 1260270            result,
 1260271            out resultSign);
 1260272    }
 273
 274    private static void GetWeightedSignedWideSum(
 275        Signed832 firstFactor,
 276        Fixed64 firstValue,
 277        Signed832 secondFactor,
 278        Fixed64 secondValue,
 279        Span<ulong> result,
 280        out int resultSign)
 281    {
 1302282        Span<ulong> factorMagnitude = stackalloc ulong[13];
 1302283        Span<ulong> valueMagnitude = stackalloc ulong[3];
 1302284        Span<ulong> first = stackalloc ulong[15];
 1302285        Span<ulong> second = stackalloc ulong[15];
 1302286        SetMagnitude(firstFactor, factorMagnitude);
 1302287        SetMagnitude(firstValue, valueMagnitude);
 1302288        WideArithmetic.MultiplyMagnitudes(
 1302289            factorMagnitude,
 1302290            valueMagnitude,
 1302291            first);
 1302292        SetMagnitude(secondFactor, factorMagnitude);
 1302293        SetMagnitude(secondValue, valueMagnitude);
 1302294        WideArithmetic.MultiplyMagnitudes(
 1302295            factorMagnitude,
 1302296            valueMagnitude,
 1302297            second);
 1302298        AddSignedMagnitudes(
 1302299            first,
 1302300            firstFactor.Sign * Math.Sign(firstValue.m_rawValue),
 1302301            second,
 1302302            secondFactor.Sign * Math.Sign(secondValue.m_rawValue),
 1302303            result,
 1302304            out resultSign);
 1302305    }
 306
 307    private static int GetProductSign(Fixed64 first, Fixed64 second) =>
 2520308        Math.Sign(first.m_rawValue) * Math.Sign(second.m_rawValue);
 309
 310    private static ReadOnlySpan<ulong> TrimMagnitude(
 311        ReadOnlySpan<ulong> value)
 312    {
 164952313        int length = value.Length;
 40996574314        while (length > 0 && value[length - 1] == 0UL)
 40831622315            length--;
 164952316        return value.Slice(0, length);
 317    }
 318
 319    private static bool TryGetDiskVelocityDeltas(
 320        in ExactContactResponseOperand3D primaryFirst,
 321        in ExactContactResponseOperand3D primarySecond,
 322        in ExactContactResponseOperand3D secondaryFirst,
 323        in ExactContactResponseOperand3D secondarySecond,
 324        Vector3d primaryTangent,
 325        Vector3d secondaryTangent,
 326        ReadOnlySpan<ulong> primaryNumerator,
 327        int primarySign,
 328        ReadOnlySpan<ulong> secondaryNumerator,
 329        int secondarySign,
 330        ReadOnlySpan<ulong> commonDenominator,
 331        bool rational,
 332        ReadOnlySpan<ulong> radialNumerator,
 333        ReadOnlySpan<ulong> radialDenominator,
 334        ReadOnlySpan<ulong> radicand,
 335        Fixed64 accumulatedPrimaryTangentImpulse,
 336        Fixed64 accumulatedSecondaryTangentImpulse,
 337        out Vector3d firstLinear,
 338        out Vector3d firstAngular,
 339        out Vector3d secondLinear,
 340        out Vector3d secondAngular)
 341    {
 439342        bool resolved = TryGetDiskLinearVelocityDelta(
 439343            primaryFirst.LinearImpulseAxis,
 439344            secondaryFirst.LinearImpulseAxis,
 439345            primaryFirst.InverseMass,
 439346            primaryNumerator,
 439347            primarySign,
 439348            secondaryNumerator,
 439349            secondarySign,
 439350            commonDenominator,
 439351            rational,
 439352            radialNumerator,
 439353            radialDenominator,
 439354            radicand,
 439355            accumulatedPrimaryTangentImpulse,
 439356            accumulatedSecondaryTangentImpulse,
 439357            out firstLinear);
 439358        resolved &= TryGetDiskAngularVelocityDelta(
 439359            primaryFirst.Lever,
 439360            -primaryTangent,
 439361            -secondaryTangent,
 439362            primaryFirst.InverseInertia,
 439363            primaryNumerator,
 439364            primarySign,
 439365            secondaryNumerator,
 439366            secondarySign,
 439367            commonDenominator,
 439368            rational,
 439369            radialNumerator,
 439370            radialDenominator,
 439371            radicand,
 439372            accumulatedPrimaryTangentImpulse,
 439373            accumulatedSecondaryTangentImpulse,
 439374            out firstAngular);
 439375        resolved &= TryGetDiskLinearVelocityDelta(
 439376            primarySecond.LinearImpulseAxis,
 439377            secondarySecond.LinearImpulseAxis,
 439378            primarySecond.InverseMass,
 439379            primaryNumerator,
 439380            primarySign,
 439381            secondaryNumerator,
 439382            secondarySign,
 439383            commonDenominator,
 439384            rational,
 439385            radialNumerator,
 439386            radialDenominator,
 439387            radicand,
 439388            accumulatedPrimaryTangentImpulse,
 439389            accumulatedSecondaryTangentImpulse,
 439390            out secondLinear);
 439391        resolved &= TryGetDiskAngularVelocityDelta(
 439392            primarySecond.Lever,
 439393            primaryTangent,
 439394            secondaryTangent,
 439395            primarySecond.InverseInertia,
 439396            primaryNumerator,
 439397            primarySign,
 439398            secondaryNumerator,
 439399            secondarySign,
 439400            commonDenominator,
 439401            rational,
 439402            radialNumerator,
 439403            radialDenominator,
 439404            radicand,
 439405            accumulatedPrimaryTangentImpulse,
 439406            accumulatedSecondaryTangentImpulse,
 439407            out secondAngular);
 439408        return resolved;
 409    }
 410
 411    private static bool TryGetDiskLinearVelocityDelta(
 412        Vector3d primaryAxis,
 413        Vector3d secondaryAxis,
 414        Fixed64 inverseMass,
 415        ReadOnlySpan<ulong> primaryNumerator,
 416        int primarySign,
 417        ReadOnlySpan<ulong> secondaryNumerator,
 418        int secondarySign,
 419        ReadOnlySpan<ulong> commonDenominator,
 420        bool rational,
 421        ReadOnlySpan<ulong> radialNumerator,
 422        ReadOnlySpan<ulong> radialDenominator,
 423        ReadOnlySpan<ulong> radicand,
 424        Fixed64 accumulatedPrimaryTangentImpulse,
 425        Fixed64 accumulatedSecondaryTangentImpulse,
 426        out Vector3d result)
 427    {
 878428        bool xResolved = TryGetDiskLinearComponent(
 878429            primaryAxis.X,
 878430            secondaryAxis.X,
 878431            inverseMass,
 878432            primaryNumerator,
 878433            primarySign,
 878434            secondaryNumerator,
 878435            secondarySign,
 878436            commonDenominator,
 878437            rational,
 878438            radialNumerator,
 878439            radialDenominator,
 878440            radicand,
 878441            accumulatedPrimaryTangentImpulse,
 878442            accumulatedSecondaryTangentImpulse,
 878443            out Fixed64 x);
 878444        bool yResolved = TryGetDiskLinearComponent(
 878445            primaryAxis.Y,
 878446            secondaryAxis.Y,
 878447            inverseMass,
 878448            primaryNumerator,
 878449            primarySign,
 878450            secondaryNumerator,
 878451            secondarySign,
 878452            commonDenominator,
 878453            rational,
 878454            radialNumerator,
 878455            radialDenominator,
 878456            radicand,
 878457            accumulatedPrimaryTangentImpulse,
 878458            accumulatedSecondaryTangentImpulse,
 878459            out Fixed64 y);
 878460        bool zResolved = TryGetDiskLinearComponent(
 878461            primaryAxis.Z,
 878462            secondaryAxis.Z,
 878463            inverseMass,
 878464            primaryNumerator,
 878465            primarySign,
 878466            secondaryNumerator,
 878467            secondarySign,
 878468            commonDenominator,
 878469            rational,
 878470            radialNumerator,
 878471            radialDenominator,
 878472            radicand,
 878473            accumulatedPrimaryTangentImpulse,
 878474            accumulatedSecondaryTangentImpulse,
 878475            out Fixed64 z);
 878476        result = xResolved & yResolved & zResolved
 878477            ? new Vector3d(x, y, z)
 878478            : default;
 878479        return xResolved & yResolved & zResolved;
 480    }
 481
 482    private static bool TryGetDiskLinearComponent(
 483        Fixed64 primaryAxis,
 484        Fixed64 secondaryAxis,
 485        Fixed64 inverseMass,
 486        ReadOnlySpan<ulong> primaryNumerator,
 487        int primarySign,
 488        ReadOnlySpan<ulong> secondaryNumerator,
 489        int secondarySign,
 490        ReadOnlySpan<ulong> commonDenominator,
 491        bool rational,
 492        ReadOnlySpan<ulong> radialNumerator,
 493        ReadOnlySpan<ulong> radialDenominator,
 494        ReadOnlySpan<ulong> radicand,
 495        Fixed64 accumulatedPrimaryTangentImpulse,
 496        Fixed64 accumulatedSecondaryTangentImpulse,
 497        out Fixed64 result)
 498    {
 2634499        Span<ulong> combined = stackalloc ulong[MaxCoulombWords + 2];
 2634500        GetWeightedSignedSum(
 2634501            primaryAxis,
 2634502            primaryNumerator,
 2634503            primarySign,
 2634504            secondaryAxis,
 2634505            secondaryNumerator,
 2634506            secondarySign,
 2634507            combined,
 2634508            out int combinedSign);
 2634509        if (inverseMass == Fixed64.Zero)
 510        {
 93511            result = Fixed64.Zero;
 93512            return true;
 513        }
 514
 2541515        Span<ulong> inverseMassMagnitude =
 2541516            stackalloc ulong[3];
 2541517        Span<ulong> fixedScale = stackalloc ulong[1];
 2541518        Span<ulong> numerator = stackalloc ulong[MaxCoulombComparisonWords / 2];
 2541519        Span<ulong> denominator =
 2541520            stackalloc ulong[MaxCoulombComparisonWords / 2];
 2541521        SetMagnitude(inverseMass, inverseMassMagnitude);
 2541522        fixedScale.Clear();
 2541523        fixedScale[0] = (ulong)FixedMath.ONE_L;
 2541524        Multiply3(
 2541525            commonDenominator,
 2541526            fixedScale,
 2541527            fixedScale,
 2541528            denominator);
 2541529        if (rational)
 530        {
 1281531            if (combinedSign == 0)
 532            {
 877533                result = Fixed64.Zero;
 877534                return true;
 535            }
 536
 404537            WideArithmetic.MultiplyMagnitudes(
 404538                combined,
 404539                inverseMassMagnitude,
 404540                numerator);
 404541            return Fixed64.TryGetSignedRawRatio(
 404542                numerator,
 404543                denominator,
 404544                combinedSign < 0,
 404545                out result);
 546        }
 547
 1260548        Span<ulong> scaledNumerator =
 1260549            stackalloc ulong[MaxCoulombComparisonWords / 2];
 1260550        Span<ulong> radicalDenominator =
 1260551            stackalloc ulong[MaxCoulombComparisonWords / 2];
 1260552        Span<ulong> cachedCombined = stackalloc ulong[3];
 1260553        Span<ulong> cachedNumerator = stackalloc ulong[4];
 1260554        Span<ulong> cachedDenominator = stackalloc ulong[2];
 1260555        WideArithmetic.MultiplyMagnitudes(
 1260556            combined,
 1260557            inverseMassMagnitude,
 1260558            numerator);
 1260559        WideArithmetic.MultiplyMagnitudes(
 1260560            numerator,
 1260561            radialNumerator,
 1260562            scaledNumerator);
 1260563        WideArithmetic.MultiplyMagnitudes(
 1260564            radialDenominator,
 1260565            fixedScale,
 1260566            radicalDenominator);
 1260567        WideArithmetic.MultiplyMagnitudes(
 1260568            radicalDenominator,
 1260569            fixedScale,
 1260570            denominator);
 1260571        GetWeightedFixedSum(
 1260572            primaryAxis,
 1260573            accumulatedPrimaryTangentImpulse,
 1260574            secondaryAxis,
 1260575            accumulatedSecondaryTangentImpulse,
 1260576            cachedCombined,
 1260577            out int cachedSign);
 1260578        WideArithmetic.MultiplyMagnitudes(
 1260579            cachedCombined,
 1260580            inverseMassMagnitude,
 1260581            cachedNumerator);
 1260582        WideArithmetic.MultiplyMagnitudes(
 1260583            fixedScale,
 1260584            fixedScale,
 1260585            cachedDenominator);
 1260586        return TryGetSignedRadicalAndRationalSum(
 1260587            scaledNumerator,
 1260588            denominator,
 1260589            radicand,
 1260590            combinedSign,
 1260591            cachedNumerator,
 1260592            cachedDenominator,
 1260593            -cachedSign,
 1260594            out result);
 595    }
 596
 597    private static bool TryGetDiskAngularVelocityDelta(
 598        in ExactLever3D lever,
 599        Vector3d primaryAxis,
 600        Vector3d secondaryAxis,
 601        Fixed3x3 inverseInertia,
 602        ReadOnlySpan<ulong> primaryNumerator,
 603        int primarySign,
 604        ReadOnlySpan<ulong> secondaryNumerator,
 605        int secondarySign,
 606        ReadOnlySpan<ulong> commonDenominator,
 607        bool rational,
 608        ReadOnlySpan<ulong> radialNumerator,
 609        ReadOnlySpan<ulong> radialDenominator,
 610        ReadOnlySpan<ulong> radicand,
 611        Fixed64 accumulatedPrimaryTangentImpulse,
 612        Fixed64 accumulatedSecondaryTangentImpulse,
 613        out Vector3d result)
 614    {
 878615        ExactLever3D.GetTransformedCrossProduct(
 878616            lever,
 878617            primaryAxis,
 878618            inverseInertia,
 878619            out Signed832 primaryX,
 878620            out Signed832 primaryY,
 878621            out Signed832 primaryZ);
 878622        ExactLever3D.GetTransformedCrossProduct(
 878623            lever,
 878624            secondaryAxis,
 878625            inverseInertia,
 878626            out Signed832 secondaryX,
 878627            out Signed832 secondaryY,
 878628            out Signed832 secondaryZ);
 878629        Signed320 fixedScaleSquared = WideArithmetic.MultiplySigned192(
 878630            Signed192.One,
 878631            Signed192.One);
 878632        Signed704 transformedDenominator =
 878633            WideArithmetic.MultiplySigned576ToSigned704(
 878634                lever.Denominator,
 878635                fixedScaleSquared);
 636
 878637        bool xResolved = TryGetDiskAngularComponent(
 878638            primaryX,
 878639            secondaryX,
 878640            transformedDenominator,
 878641            primaryNumerator,
 878642            primarySign,
 878643            secondaryNumerator,
 878644            secondarySign,
 878645            commonDenominator,
 878646            rational,
 878647            radialNumerator,
 878648            radialDenominator,
 878649            radicand,
 878650            accumulatedPrimaryTangentImpulse,
 878651            accumulatedSecondaryTangentImpulse,
 878652            out Fixed64 x);
 878653        bool yResolved = TryGetDiskAngularComponent(
 878654            primaryY,
 878655            secondaryY,
 878656            transformedDenominator,
 878657            primaryNumerator,
 878658            primarySign,
 878659            secondaryNumerator,
 878660            secondarySign,
 878661            commonDenominator,
 878662            rational,
 878663            radialNumerator,
 878664            radialDenominator,
 878665            radicand,
 878666            accumulatedPrimaryTangentImpulse,
 878667            accumulatedSecondaryTangentImpulse,
 878668            out Fixed64 y);
 878669        bool zResolved = TryGetDiskAngularComponent(
 878670            primaryZ,
 878671            secondaryZ,
 878672            transformedDenominator,
 878673            primaryNumerator,
 878674            primarySign,
 878675            secondaryNumerator,
 878676            secondarySign,
 878677            commonDenominator,
 878678            rational,
 878679            radialNumerator,
 878680            radialDenominator,
 878681            radicand,
 878682            accumulatedPrimaryTangentImpulse,
 878683            accumulatedSecondaryTangentImpulse,
 878684            out Fixed64 z);
 878685        result = xResolved & yResolved & zResolved
 878686            ? new Vector3d(x, y, z)
 878687            : default;
 878688        return xResolved & yResolved & zResolved;
 689    }
 690
 691    private static bool TryGetDiskAngularComponent(
 692        Signed832 primary,
 693        Signed832 secondary,
 694        Signed704 transformedDenominator,
 695        ReadOnlySpan<ulong> primaryNumerator,
 696        int primarySign,
 697        ReadOnlySpan<ulong> secondaryNumerator,
 698        int secondarySign,
 699        ReadOnlySpan<ulong> commonDenominator,
 700        bool rational,
 701        ReadOnlySpan<ulong> radialNumerator,
 702        ReadOnlySpan<ulong> radialDenominator,
 703        ReadOnlySpan<ulong> radicand,
 704        Fixed64 accumulatedPrimaryTangentImpulse,
 705        Fixed64 accumulatedSecondaryTangentImpulse,
 706        out Fixed64 result)
 707    {
 2634708        Span<ulong> primaryMagnitude = stackalloc ulong[13];
 2634709        Span<ulong> secondaryMagnitude = stackalloc ulong[13];
 2634710        SetMagnitude(primary, primaryMagnitude);
 2634711        SetMagnitude(secondary, secondaryMagnitude);
 2634712        Span<ulong> primaryProduct =
 2634713            stackalloc ulong[MaxCoulombComparisonWords / 2];
 2634714        Span<ulong> secondaryProduct =
 2634715            stackalloc ulong[MaxCoulombComparisonWords / 2];
 2634716        WideArithmetic.MultiplyMagnitudes(
 2634717            primaryMagnitude,
 2634718            primaryNumerator,
 2634719            primaryProduct);
 2634720        WideArithmetic.MultiplyMagnitudes(
 2634721            secondaryMagnitude,
 2634722            secondaryNumerator,
 2634723            secondaryProduct);
 2634724        Span<ulong> combined =
 2634725            stackalloc ulong[MaxCoulombComparisonWords / 2];
 2634726        AddSignedMagnitudes(
 2634727            primaryProduct,
 2634728            primary.Sign * primarySign,
 2634729            secondaryProduct,
 2634730            secondary.Sign * secondarySign,
 2634731            combined,
 2634732            out int combinedSign);
 733
 2634734        Span<ulong> transformedDenominatorMagnitude =
 2634735            stackalloc ulong[11];
 2634736        Span<ulong> fixedScale = stackalloc ulong[1];
 2634737        Span<ulong> denominator =
 2634738            stackalloc ulong[MaxCoulombComparisonWords / 2];
 2634739        SetMagnitude(transformedDenominator, transformedDenominatorMagnitude);
 2634740        fixedScale.Clear();
 2634741        fixedScale[0] = (ulong)FixedMath.ONE_L;
 2634742        Multiply3(
 2634743            transformedDenominatorMagnitude,
 2634744            commonDenominator,
 2634745            fixedScale,
 2634746            denominator);
 2634747        if (rational)
 748        {
 1332749            if (combinedSign == 0)
 750            {
 994751                result = Fixed64.Zero;
 994752                return true;
 753            }
 754
 338755            return Fixed64.TryGetSignedRawRatio(
 338756                combined,
 338757                denominator,
 338758                combinedSign < 0,
 338759                out result);
 760        }
 761
 1302762        Span<ulong> scaledNumerator =
 1302763            stackalloc ulong[MaxCoulombComparisonWords / 2];
 1302764        Span<ulong> radicalDenominator =
 1302765            stackalloc ulong[MaxCoulombComparisonWords / 2];
 1302766        Span<ulong> cachedCombined = stackalloc ulong[15];
 1302767        Span<ulong> cachedDenominator = stackalloc ulong[12];
 1302768        WideArithmetic.MultiplyMagnitudes(
 1302769            combined,
 1302770            radialNumerator,
 1302771            scaledNumerator);
 1302772        WideArithmetic.MultiplyMagnitudes(
 1302773            transformedDenominatorMagnitude,
 1302774            radialDenominator,
 1302775            radicalDenominator);
 1302776        WideArithmetic.MultiplyMagnitudes(
 1302777            radicalDenominator,
 1302778            fixedScale,
 1302779            denominator);
 1302780        GetWeightedSignedWideSum(
 1302781            primary,
 1302782            accumulatedPrimaryTangentImpulse,
 1302783            secondary,
 1302784            accumulatedSecondaryTangentImpulse,
 1302785            cachedCombined,
 1302786            out int cachedSign);
 1302787        WideArithmetic.MultiplyMagnitudes(
 1302788            transformedDenominatorMagnitude,
 1302789            fixedScale,
 1302790            cachedDenominator);
 1302791        return TryGetSignedRadicalAndRationalSum(
 1302792            scaledNumerator,
 1302793            denominator,
 1302794            radicand,
 1302795            combinedSign,
 1302796            cachedCombined,
 1302797            cachedDenominator,
 1302798            -cachedSign,
 1302799            out result);
 800    }
 801
 802    private static void GetWeightedSignedSum(
 803        Fixed64 firstFactor,
 804        ReadOnlySpan<ulong> firstMagnitude,
 805        int firstSign,
 806        Fixed64 secondFactor,
 807        ReadOnlySpan<ulong> secondMagnitude,
 808        int secondSign,
 809        Span<ulong> result,
 810        out int resultSign)
 811    {
 2634812        Span<ulong> factorMagnitude = stackalloc ulong[3];
 2634813        Span<ulong> first = stackalloc ulong[MaxCoulombWords + 2];
 2634814        Span<ulong> second = stackalloc ulong[MaxCoulombWords + 2];
 2634815        SetMagnitude(firstFactor, factorMagnitude);
 2634816        WideArithmetic.MultiplyMagnitudes(
 2634817            factorMagnitude,
 2634818            firstMagnitude,
 2634819            first);
 2634820        SetMagnitude(secondFactor, factorMagnitude);
 2634821        WideArithmetic.MultiplyMagnitudes(
 2634822            factorMagnitude,
 2634823            secondMagnitude,
 2634824            second);
 2634825        AddSignedMagnitudes(
 2634826            first,
 2634827            firstFactor == Fixed64.Zero
 2634828                ? 0
 2634829                : firstFactor < Fixed64.Zero ? -firstSign : firstSign,
 2634830            second,
 2634831            secondFactor == Fixed64.Zero
 2634832                ? 0
 2634833                : secondFactor < Fixed64.Zero ? -secondSign : secondSign,
 2634834            result,
 2634835            out resultSign);
 2634836    }
 837
 838}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/Exact/ExactContactResponseKernel.Normal.cs

#LineLine coverage
 1//=======================================================================
 2// ExactContactResponseKernel.Normal.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 System;
 9using System.Runtime.CompilerServices;
 10
 11using FixedMathSharp;
 12using FixedMathSharp.Geometry;
 13
 14namespace Gravitas.CollisionHandling;
 15
 16/// <content>
 17/// Owns unilateral exact normal-response policy and materialization.
 18/// </content>
 19internal static partial class ExactContactResponseKernel
 20{
 21    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 22    internal static bool TryGetNormalResponse(
 23        in ExactContactResponseOperand3D first,
 24        in ExactContactResponseOperand3D second,
 25        Vector3d normal,
 26        Fixed64 restitution,
 27        Fixed64 restitutionVelocityThreshold,
 28        out ExactNormalResponse3D response) =>
 21029        TryGetNormalResponseCore(
 21030            first,
 21031            second,
 21032            normal,
 21033            restitution,
 21034            restitutionVelocityThreshold,
 21035            accumulatedImpulse: Fixed64.Zero,
 21036            positiveImpulseScale: Fixed64.One,
 21037            negativeImpulseScale: Fixed64.One,
 21038            includeAccumulator: false,
 21039            out response);
 40
 41    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 42    internal static bool TryGetAccumulatedNormalResponse(
 43        in ExactContactResponseOperand3D first,
 44        in ExactContactResponseOperand3D second,
 45        Vector3d normal,
 46        Fixed64 restitution,
 47        Fixed64 restitutionVelocityThreshold,
 48        Fixed64 accumulatedImpulse,
 49        Fixed64 positiveImpulseScale,
 50        Fixed64 negativeImpulseScale,
 51        out ExactNormalResponse3D response) =>
 147152        TryGetNormalResponseCore(
 147153            first,
 147154            second,
 147155            normal,
 147156            restitution,
 147157            restitutionVelocityThreshold,
 147158            accumulatedImpulse,
 147159            positiveImpulseScale,
 147160            negativeImpulseScale,
 147161            includeAccumulator: true,
 147162            out response);
 63
 64    [MethodImpl(MethodImplOptions.NoInlining)]
 65    private static bool TryGetNormalResponseCore(
 66        in ExactContactResponseOperand3D first,
 67        in ExactContactResponseOperand3D second,
 68        Vector3d normal,
 69        Fixed64 restitution,
 70        Fixed64 restitutionVelocityThreshold,
 71        Fixed64 accumulatedImpulse,
 72        Fixed64 positiveImpulseScale,
 73        Fixed64 negativeImpulseScale,
 74        bool includeAccumulator,
 75        out ExactNormalResponse3D response)
 76    {
 168177        response = default;
 168178        if (first.Lever.Denominator.Sign == 0
 168179            || second.Lever.Denominator.Sign == 0
 168180            || restitution < Fixed64.Zero
 168181            || restitutionVelocityThreshold < Fixed64.Zero
 168182            || accumulatedImpulse < Fixed64.Zero
 168183            || positiveImpulseScale < Fixed64.Zero
 168184            || negativeImpulseScale < Fixed64.Zero
 168185            || first.InverseMass < Fixed64.Zero
 168186            || second.InverseMass < Fixed64.Zero
 168187            || !normal.IsNormalized())
 88        {
 1389            return false;
 90        }
 91
 166892        ExactLever3D.GetRelativePointVelocityRatio(
 166893            first.LinearVelocity,
 166894            first.AngularVelocity,
 166895            first.Lever,
 166896            second.LinearVelocity,
 166897            second.AngularVelocity,
 166898            second.Lever,
 166899            normal,
 1668100            out Signed832 velocityNumerator,
 1668101            out Signed832 velocityDenominator);
 1668102        int velocitySign =
 1668103            velocityNumerator.Sign * velocityDenominator.Sign;
 1668104        bool hasNormalVelocity = Fixed64.TryGetSignedRawRatio(
 1668105            velocityNumerator,
 1668106            velocityDenominator,
 1668107            0,
 1668108            out Fixed64 normalVelocity);
 1668109        Span<ulong> effectiveNumerator =
 1668110            stackalloc ulong[MaxResponseWords];
 1668111        Span<ulong> effectiveDenominator =
 1668112            stackalloc ulong[MaxResponseWords];
 1668113        GetEffectiveMassRatio(
 1668114            first,
 1668115            second,
 1668116            normal,
 1668117            effectiveNumerator,
 1668118            effectiveDenominator);
 119
 1668120        bool denominatorTooSmall =
 1668121            WideArithmetic.IsZeroMagnitude(effectiveNumerator);
 1668122        if (denominatorTooSmall)
 123        {
 14124            if (velocitySign < 0 && !includeAccumulator)
 10125                return false;
 126
 4127            response = CreateZeroResponse(
 4128                velocitySign < 0,
 4129                hasNormalVelocity,
 4130                normalVelocity,
 4131                includeAccumulator,
 4132                accumulatedImpulse);
 4133            return true;
 134        }
 135
 1654136        if (velocitySign == 0)
 137        {
 34138            response = CreateZeroResponse(
 34139                isClosing: false,
 34140                hasNormalVelocity,
 34141                normalVelocity,
 34142                includeAccumulator,
 34143                accumulatedImpulse);
 34144            return true;
 145        }
 146
 1620147        bool applyRestitution = velocitySign < 0
 1620148            && (!hasNormalVelocity
 1620149                || normalVelocity < -restitutionVelocityThreshold);
 1620150        Fixed64 impulseScale = velocitySign < 0
 1620151            ? positiveImpulseScale
 1620152            : negativeImpulseScale;
 1620153        Span<ulong> impulseNumerator =
 1620154            stackalloc ulong[MaxResponseWords];
 1620155        Span<ulong> impulseDenominator =
 1620156            stackalloc ulong[MaxResponseWords];
 1620157        BuildImpulseRatio(
 1620158            velocityNumerator,
 1620159            velocityDenominator,
 1620160            effectiveNumerator,
 1620161            effectiveDenominator,
 1620162            applyRestitution ? restitution : Fixed64.Zero,
 1620163            impulseScale,
 1620164            impulseNumerator,
 1620165            impulseDenominator);
 1620166        int impulseSign = -velocitySign;
 167
 1620168        Span<ulong> appliedNumerator =
 1620169            stackalloc ulong[MaxResponseWords];
 1620170        Span<ulong> appliedDenominator =
 1620171            stackalloc ulong[MaxResponseWords];
 1620172        appliedNumerator.Clear();
 1620173        appliedDenominator.Clear();
 1620174        ResolveUnilateralImpulse(
 1620175            impulseNumerator,
 1620176            impulseDenominator,
 1620177            impulseSign,
 1620178            accumulatedImpulse,
 1620179            includeAccumulator,
 1620180            appliedNumerator,
 1620181            appliedDenominator,
 1620182            out int appliedSign,
 1620183            out bool useImpulseRatio,
 1620184            out bool hasAccumulatedProjection,
 1620185            out Fixed64 accumulatedProjection);
 186
 1620187        ReadOnlySpan<ulong> resolvedImpulseNumerator =
 1620188            useImpulseRatio ? impulseNumerator : appliedNumerator;
 1620189        ReadOnlySpan<ulong> resolvedImpulseDenominator =
 1620190            useImpulseRatio ? impulseDenominator : appliedDenominator;
 1620191        bool hasAppliedImpulse =
 1620192            !WideArithmetic.IsZeroMagnitude(resolvedImpulseNumerator);
 1620193        bool hasAppliedProjection = Fixed64.TryGetSignedRawRatio(
 1620194            resolvedImpulseNumerator,
 1620195            resolvedImpulseDenominator,
 1620196            appliedSign < 0,
 1620197            out Fixed64 appliedImpulse);
 1620198        bool resolved = TryGetLinearVelocityDelta(
 1620199            first.LinearImpulseAxis,
 1620200            first.InverseMass,
 1620201            resolvedImpulseNumerator,
 1620202            resolvedImpulseDenominator,
 1620203            appliedSign,
 1620204            out Vector3d firstLinear);
 1620205        resolved &= TryGetAngularVelocityDelta(
 1620206            first.Lever,
 1620207            -normal,
 1620208            first.InverseInertia,
 1620209            resolvedImpulseNumerator,
 1620210            resolvedImpulseDenominator,
 1620211            appliedSign,
 1620212            out Vector3d firstAngular);
 1620213        resolved &= TryGetLinearVelocityDelta(
 1620214            second.LinearImpulseAxis,
 1620215            second.InverseMass,
 1620216            resolvedImpulseNumerator,
 1620217            resolvedImpulseDenominator,
 1620218            appliedSign,
 1620219            out Vector3d secondLinear);
 1620220        resolved &= TryGetAngularVelocityDelta(
 1620221            second.Lever,
 1620222            normal,
 1620223            second.InverseInertia,
 1620224            resolvedImpulseNumerator,
 1620225            resolvedImpulseDenominator,
 1620226            appliedSign,
 1620227            out Vector3d secondAngular);
 1620228        if (!resolved)
 13229            return false;
 230
 1607231        response = new ExactNormalResponse3D(
 1607232            velocitySign < 0,
 1607233            hasAppliedImpulse,
 1607234            firstLinear,
 1607235            firstAngular,
 1607236            secondLinear,
 1607237            secondAngular,
 1607238            hasNormalVelocity,
 1607239            normalVelocity,
 1607240            hasAppliedProjection,
 1607241            appliedImpulse,
 1607242            includeAccumulator && hasAccumulatedProjection,
 1607243            accumulatedProjection);
 1607244        return true;
 245    }
 246
 247    private static ExactNormalResponse3D CreateZeroResponse(
 248        bool isClosing,
 249        bool hasNormalVelocity,
 250        Fixed64 normalVelocity,
 251        bool includeAccumulator,
 252        Fixed64 accumulatedImpulse) =>
 38253        new(
 38254            isClosing,
 38255            hasAppliedImpulse: false,
 38256            Vector3d.Zero,
 38257            Vector3d.Zero,
 38258            Vector3d.Zero,
 38259            Vector3d.Zero,
 38260            hasNormalVelocity,
 38261            normalVelocity,
 38262            hasAppliedImpulseProjection: true,
 38263            Fixed64.Zero,
 38264            includeAccumulator,
 38265            accumulatedImpulse);
 266
 267    private static void GetEffectiveMassRatio(
 268        in ExactContactResponseOperand3D first,
 269        in ExactContactResponseOperand3D second,
 270        Vector3d normal,
 271        Span<ulong> resultNumerator,
 272        Span<ulong> resultDenominator)
 273    {
 2707274        Signed192 normalSquared = GetRawDot(normal, normal);
 275
 2707276        Signed192 firstAllowed =
 2707277            GetRawDot(first.LinearImpulseAxis, -normal);
 2707278        Signed192 secondAllowed =
 2707279            GetRawDot(second.LinearImpulseAxis, normal);
 2707280        Signed320 linearNumerator = default;
 2707281        if (firstAllowed.Sign > 0
 2707282            && first.InverseMass > Fixed64.Zero)
 283        {
 2506284            linearNumerator = WideArithmetic.MultiplySigned192(
 2506285                firstAllowed,
 2506286                Signed192.Raw(first.InverseMass));
 287        }
 2707288        if (secondAllowed.Sign > 0
 2707289            && second.InverseMass > Fixed64.Zero)
 290        {
 2480291            linearNumerator = WideArithmetic.AddSigned320(
 2480292                linearNumerator,
 2480293                WideArithmetic.MultiplySigned192(
 2480294                    secondAllowed,
 2480295                    Signed192.Raw(second.InverseMass)));
 296        }
 297
 2707298        ExactLever3D.GetCrossProductQuadraticFormRatio(
 2707299            first.Lever,
 2707300            normal,
 2707301            first.InverseInertia,
 2707302            out Signed832 firstAngularNumerator,
 2707303            out Signed832 firstAngularDenominator);
 2707304        ExactLever3D.GetCrossProductQuadraticFormRatio(
 2707305            second.Lever,
 2707306            normal,
 2707307            second.InverseInertia,
 2707308            out Signed832 secondAngularNumerator,
 2707309            out Signed832 secondAngularDenominator);
 310
 2707311        Span<ulong> linearNumeratorMagnitude =
 2707312            stackalloc ulong[MaxResponseWords];
 2707313        Span<ulong> linearDenominatorMagnitude =
 2707314            stackalloc ulong[MaxResponseWords];
 2707315        Span<ulong> firstNumeratorMagnitude =
 2707316            stackalloc ulong[MaxResponseWords];
 2707317        Span<ulong> firstDenominatorMagnitude =
 2707318            stackalloc ulong[MaxResponseWords];
 2707319        Span<ulong> secondNumeratorMagnitude =
 2707320            stackalloc ulong[MaxResponseWords];
 2707321        Span<ulong> secondDenominatorMagnitude =
 2707322            stackalloc ulong[MaxResponseWords];
 2707323        SetMagnitude(linearNumerator, linearNumeratorMagnitude);
 2707324        SetMagnitude(normalSquared, linearDenominatorMagnitude);
 2707325        SetNonNegativeRatio(
 2707326            firstAngularNumerator,
 2707327            firstAngularDenominator,
 2707328            firstNumeratorMagnitude,
 2707329            firstDenominatorMagnitude);
 2707330        SetNonNegativeRatio(
 2707331            secondAngularNumerator,
 2707332            secondAngularDenominator,
 2707333            secondNumeratorMagnitude,
 2707334            secondDenominatorMagnitude);
 335
 2707336        Span<ulong> temporary = stackalloc ulong[MaxResponseWords];
 2707337        Multiply3(
 2707338            linearDenominatorMagnitude,
 2707339            firstDenominatorMagnitude,
 2707340            secondDenominatorMagnitude,
 2707341            resultDenominator);
 2707342        Multiply3(
 2707343            linearNumeratorMagnitude,
 2707344            firstDenominatorMagnitude,
 2707345            secondDenominatorMagnitude,
 2707346            resultNumerator);
 2707347        Multiply3(
 2707348            firstNumeratorMagnitude,
 2707349            linearDenominatorMagnitude,
 2707350            secondDenominatorMagnitude,
 2707351            temporary);
 2707352        WideArithmetic.AddMagnitudeInto(temporary, resultNumerator);
 2707353        Multiply3(
 2707354            secondNumeratorMagnitude,
 2707355            linearDenominatorMagnitude,
 2707356            firstDenominatorMagnitude,
 2707357            temporary);
 2707358        WideArithmetic.AddMagnitudeInto(temporary, resultNumerator);
 2707359    }
 360
 361    private static void BuildImpulseRatio(
 362        Signed832 velocityNumerator,
 363        Signed832 velocityDenominator,
 364        ReadOnlySpan<ulong> effectiveNumerator,
 365        ReadOnlySpan<ulong> effectiveDenominator,
 366        Fixed64 restitution,
 367        Fixed64 impulseScale,
 368        Span<ulong> numerator,
 369        Span<ulong> denominator)
 370    {
 2180371        Span<ulong> velocityNumeratorMagnitude =
 2180372            stackalloc ulong[MaxResponseWords];
 2180373        Span<ulong> velocityDenominatorMagnitude =
 2180374            stackalloc ulong[MaxResponseWords];
 2180375        Span<ulong> restitutionMagnitude =
 2180376            stackalloc ulong[MaxResponseWords];
 2180377        Span<ulong> scaleMagnitude =
 2180378            stackalloc ulong[MaxResponseWords];
 2180379        Span<ulong> fixedScale =
 2180380            stackalloc ulong[MaxResponseWords];
 2180381        SetMagnitude(velocityNumerator, velocityNumeratorMagnitude);
 2180382        SetMagnitude(velocityDenominator, velocityDenominatorMagnitude);
 2180383        SetUnsignedSum(
 2180384            (ulong)FixedMath.ONE_L,
 2180385            (ulong)restitution.m_rawValue,
 2180386            restitutionMagnitude);
 2180387        SetMagnitude(impulseScale, scaleMagnitude);
 2180388        fixedScale.Clear();
 2180389        fixedScale[0] = (ulong)FixedMath.ONE_L;
 390
 2180391        Multiply4(
 2180392            velocityNumeratorMagnitude,
 2180393            effectiveDenominator,
 2180394            restitutionMagnitude,
 2180395            scaleMagnitude,
 2180396            numerator);
 2180397        Multiply3(
 2180398            velocityDenominatorMagnitude,
 2180399            effectiveNumerator,
 2180400            fixedScale,
 2180401            denominator);
 2180402    }
 403
 404    private static void ResolveUnilateralImpulse(
 405        ReadOnlySpan<ulong> impulseNumerator,
 406        ReadOnlySpan<ulong> impulseDenominator,
 407        int impulseSign,
 408        Fixed64 accumulatedImpulse,
 409        bool includeAccumulator,
 410        Span<ulong> appliedNumerator,
 411        Span<ulong> appliedDenominator,
 412        out int appliedSign,
 413        out bool useImpulseRatio,
 414        out bool hasAccumulatedProjection,
 415        out Fixed64 accumulatedProjection)
 416    {
 1620417        appliedNumerator.Clear();
 1620418        appliedDenominator.Clear();
 1620419        useImpulseRatio = false;
 1620420        hasAccumulatedProjection = false;
 1620421        accumulatedProjection = default;
 1620422        if (!includeAccumulator)
 423        {
 189424            if (impulseSign <= 0)
 425            {
 4426                appliedDenominator[0] = 1UL;
 4427                appliedSign = 0;
 4428                return;
 429            }
 430
 185431            appliedSign = 1;
 185432            useImpulseRatio = true;
 185433            return;
 434        }
 435
 1431436        Span<ulong> accumulatorMagnitude =
 1431437            stackalloc ulong[MaxResponseWords];
 1431438        Span<ulong> accumulatedAtImpulseDenominator =
 1431439            stackalloc ulong[MaxResponseWords];
 1431440        SetMagnitude(accumulatedImpulse, accumulatorMagnitude);
 1431441        WideArithmetic.MultiplyMagnitudes(
 1431442            accumulatorMagnitude,
 1431443            impulseDenominator,
 1431444            accumulatedAtImpulseDenominator);
 1431445        if (impulseSign < 0
 1431446            && WideArithmetic.CompareMagnitudeEqualLength(
 1431447                impulseNumerator,
 1431448                accumulatedAtImpulseDenominator) >= 0)
 449        {
 359450            accumulatorMagnitude.CopyTo(appliedNumerator);
 359451            appliedDenominator[0] = 1UL;
 359452            appliedSign = WideArithmetic.IsZeroMagnitude(
 359453                accumulatorMagnitude)
 359454                ? 0
 359455                : -1;
 359456            hasAccumulatedProjection = true;
 359457            accumulatedProjection = Fixed64.Zero;
 359458            return;
 459        }
 460
 1072461        Span<ulong> completedAccumulator =
 1072462            stackalloc ulong[MaxResponseWords];
 1072463        if (impulseSign > 0)
 464        {
 876465            accumulatedAtImpulseDenominator.CopyTo(completedAccumulator);
 876466            WideArithmetic.AddMagnitudeInto(
 876467                impulseNumerator,
 876468                completedAccumulator);
 469        }
 470        else
 471        {
 196472            WideArithmetic.SubtractEqualMagnitudes(
 196473                accumulatedAtImpulseDenominator,
 196474                impulseNumerator,
 196475                completedAccumulator);
 476        }
 477
 1072478        hasAccumulatedProjection = Fixed64.TryGetSignedRawRatio(
 1072479            completedAccumulator,
 1072480            impulseDenominator,
 1072481            negative: false,
 1072482            out accumulatedProjection);
 1072483        appliedSign = impulseSign;
 1072484        useImpulseRatio = true;
 1072485    }
 486
 487    private static bool TryGetLinearVelocityDelta(
 488        Vector3d impulseAxis,
 489        Fixed64 inverseMass,
 490        ReadOnlySpan<ulong> impulseNumerator,
 491        ReadOnlySpan<ulong> impulseDenominator,
 492        int impulseSign,
 493        out Vector3d result)
 494    {
 3346495        if (inverseMass == Fixed64.Zero
 3346496            || impulseAxis == Vector3d.Zero
 3346497            || WideArithmetic.IsZeroMagnitude(impulseNumerator))
 498        {
 978499            result = Vector3d.Zero;
 978500            return true;
 501        }
 502
 2368503        bool xResolved = TryGetLinearComponent(
 2368504            impulseAxis.X,
 2368505            inverseMass,
 2368506            impulseNumerator,
 2368507            impulseDenominator,
 2368508            impulseSign,
 2368509            out Fixed64 x);
 2368510        bool yResolved = TryGetLinearComponent(
 2368511            impulseAxis.Y,
 2368512            inverseMass,
 2368513            impulseNumerator,
 2368514            impulseDenominator,
 2368515            impulseSign,
 2368516            out Fixed64 y);
 2368517        bool zResolved = TryGetLinearComponent(
 2368518            impulseAxis.Z,
 2368519            inverseMass,
 2368520            impulseNumerator,
 2368521            impulseDenominator,
 2368522            impulseSign,
 2368523            out Fixed64 z);
 2368524        result = xResolved & yResolved & zResolved
 2368525            ? new Vector3d(x, y, z)
 2368526            : default;
 2368527        return xResolved & yResolved & zResolved;
 528    }
 529
 530    private static bool TryGetLinearComponent(
 531        Fixed64 axis,
 532        Fixed64 inverseMass,
 533        ReadOnlySpan<ulong> impulseNumerator,
 534        ReadOnlySpan<ulong> impulseDenominator,
 535        int impulseSign,
 536        out Fixed64 result)
 537    {
 7104538        if (axis == Fixed64.Zero)
 539        {
 4578540            result = Fixed64.Zero;
 4578541            return true;
 542        }
 543
 2526544        Span<ulong> axisMagnitude =
 2526545            stackalloc ulong[MaxResponseWords];
 2526546        Span<ulong> inverseMassMagnitude =
 2526547            stackalloc ulong[MaxResponseWords];
 2526548        Span<ulong> fixedScale =
 2526549            stackalloc ulong[MaxResponseWords];
 2526550        Span<ulong> numerator =
 2526551            stackalloc ulong[MaxResponseWords];
 2526552        Span<ulong> denominator =
 2526553            stackalloc ulong[MaxResponseWords];
 2526554        SetMagnitude(axis, axisMagnitude);
 2526555        SetMagnitude(inverseMass, inverseMassMagnitude);
 2526556        fixedScale.Clear();
 2526557        fixedScale[0] = (ulong)FixedMath.ONE_L;
 2526558        Multiply3(
 2526559            axisMagnitude,
 2526560            inverseMassMagnitude,
 2526561            impulseNumerator,
 2526562            numerator);
 2526563        Multiply3(
 2526564            impulseDenominator,
 2526565            fixedScale,
 2526566            fixedScale,
 2526567            denominator);
 2526568        return Fixed64.TryGetSignedRawRatio(
 2526569            numerator,
 2526570            denominator,
 2526571            (axis < Fixed64.Zero) != (impulseSign < 0),
 2526572            out result);
 573    }
 574
 575    private static bool TryGetAngularVelocityDelta(
 576        in ExactLever3D lever,
 577        Vector3d impulseAxis,
 578        Fixed3x3 inverseInertia,
 579        ReadOnlySpan<ulong> impulseNumerator,
 580        ReadOnlySpan<ulong> impulseDenominator,
 581        int impulseSign,
 582        out Vector3d result)
 583    {
 3346584        if (WideArithmetic.IsZeroMagnitude(impulseNumerator))
 585        {
 738586            result = Vector3d.Zero;
 738587            return true;
 588        }
 589
 2608590        ExactLever3D.GetTransformedCrossProduct(
 2608591            lever,
 2608592            impulseAxis,
 2608593            inverseInertia,
 2608594            out Signed832 transformedX,
 2608595            out Signed832 transformedY,
 2608596            out Signed832 transformedZ);
 2608597        Signed320 fixedScaleSquared = WideArithmetic.MultiplySigned192(
 2608598            Signed192.One,
 2608599            Signed192.One);
 2608600        Signed704 transformedDenominator =
 2608601            WideArithmetic.MultiplySigned576ToSigned704(
 2608602                lever.Denominator,
 2608603                fixedScaleSquared);
 2608604        bool xResolved = TryGetAngularComponent(
 2608605            transformedX,
 2608606            transformedDenominator,
 2608607            impulseNumerator,
 2608608            impulseDenominator,
 2608609            impulseSign,
 2608610            out Fixed64 x);
 2608611        bool yResolved = TryGetAngularComponent(
 2608612            transformedY,
 2608613            transformedDenominator,
 2608614            impulseNumerator,
 2608615            impulseDenominator,
 2608616            impulseSign,
 2608617            out Fixed64 y);
 2608618        bool zResolved = TryGetAngularComponent(
 2608619            transformedZ,
 2608620            transformedDenominator,
 2608621            impulseNumerator,
 2608622            impulseDenominator,
 2608623            impulseSign,
 2608624            out Fixed64 z);
 2608625        result = xResolved & yResolved & zResolved
 2608626            ? new Vector3d(x, y, z)
 2608627            : default;
 2608628        return xResolved & yResolved & zResolved;
 629    }
 630
 631    private static bool TryGetAngularComponent(
 632        Signed832 transformed,
 633        Signed704 transformedDenominator,
 634        ReadOnlySpan<ulong> impulseNumerator,
 635        ReadOnlySpan<ulong> impulseDenominator,
 636        int impulseSign,
 637        out Fixed64 result)
 638    {
 7824639        if (transformed.Sign == 0)
 640        {
 5682641            result = Fixed64.Zero;
 5682642            return true;
 643        }
 644
 2142645        Span<ulong> transformedMagnitude =
 2142646            stackalloc ulong[MaxResponseWords];
 2142647        Span<ulong> transformedDenominatorMagnitude =
 2142648            stackalloc ulong[MaxResponseWords];
 2142649        Span<ulong> fixedScale =
 2142650            stackalloc ulong[MaxResponseWords];
 2142651        Span<ulong> numerator =
 2142652            stackalloc ulong[MaxResponseWords];
 2142653        Span<ulong> denominator =
 2142654            stackalloc ulong[MaxResponseWords];
 2142655        SetMagnitude(transformed, transformedMagnitude);
 2142656        SetMagnitude(transformedDenominator, transformedDenominatorMagnitude);
 2142657        fixedScale.Clear();
 2142658        fixedScale[0] = (ulong)FixedMath.ONE_L;
 2142659        WideArithmetic.MultiplyMagnitudes(
 2142660            transformedMagnitude,
 2142661            impulseNumerator,
 2142662            numerator);
 2142663        Multiply3(
 2142664            transformedDenominatorMagnitude,
 2142665            impulseDenominator,
 2142666            fixedScale,
 2142667            denominator);
 2142668        return Fixed64.TryGetSignedRawRatio(
 2142669            numerator,
 2142670            denominator,
 2142671            (transformed.Sign < 0) != (impulseSign < 0),
 2142672            out result);
 673    }
 674
 675    private static Signed192 GetRawDot(Vector3d left, Vector3d right) =>
 8121676        WideGeometry.GetDifferenceDotProduct3D(
 8121677            left.X,
 8121678            Fixed64.Zero,
 8121679            left.Y,
 8121680            Fixed64.Zero,
 8121681            left.Z,
 8121682            Fixed64.Zero,
 8121683            right.X,
 8121684            Fixed64.Zero,
 8121685            right.Y,
 8121686            Fixed64.Zero,
 8121687            right.Z,
 8121688            Fixed64.Zero);
 689
 690    private static void SetNonNegativeRatio(
 691        Signed832 numerator,
 692        Signed832 denominator,
 693        Span<ulong> numeratorMagnitude,
 694        Span<ulong> denominatorMagnitude)
 695    {
 5414696        numeratorMagnitude.Clear();
 5414697        denominatorMagnitude.Clear();
 5414698        if (numerator.Sign <= 0)
 699        {
 1158700            denominatorMagnitude[0] = 1UL;
 1158701            return;
 702        }
 703
 4256704        SetMagnitude(numerator, numeratorMagnitude);
 4256705        SetMagnitude(denominator, denominatorMagnitude);
 4256706    }
 707
 708    private static void SetUnsignedSum(
 709        ulong first,
 710        ulong second,
 711        Span<ulong> result)
 712    {
 2180713        result.Clear();
 2180714        result[0] = unchecked(first + second);
 2180715    }
 716
 717    private static void SetMagnitude(
 718        Fixed64 value,
 719        Span<ulong> result)
 720    {
 27143721        result.Clear();
 27143722        WideArithmetic.GetMagnitude(
 27143723            Signed192.Raw(value),
 27143724            out result[2],
 27143725            out result[1],
 27143726            out result[0]);
 27143727    }
 728
 729    private static void SetMagnitude(
 730        Signed192 value,
 731        Span<ulong> result)
 732    {
 2707733        result.Clear();
 2707734        WideArithmetic.GetMagnitude(
 2707735            value,
 2707736            out result[2],
 2707737            out result[1],
 2707738            out result[0]);
 2707739    }
 740
 741    private static void SetMagnitude(
 742        Signed320 value,
 743        Span<ulong> result)
 744    {
 2707745        result.Clear();
 2707746        WideArithmetic.GetMagnitude(
 2707747            value,
 2707748            out result[4],
 2707749            out result[3],
 2707750            out result[2],
 2707751            out result[1],
 2707752            out result[0]);
 2707753    }
 754
 755    private static void SetMagnitude(
 756        Signed704 value,
 757        Span<ulong> result)
 758    {
 4776759        result.Clear();
 4776760        WideArithmetic.GetMagnitude(value, result);
 4776761    }
 762
 763    private static void SetMagnitude(
 764        Signed832 value,
 765        Span<ulong> result)
 766    {
 22886767        result.Clear();
 22886768        WideArithmetic.GetMagnitude(value, result);
 22886769    }
 770
 771    private static void Multiply3(
 772        ReadOnlySpan<ulong> first,
 773        ReadOnlySpan<ulong> second,
 774        ReadOnlySpan<ulong> third,
 775        Span<ulong> result)
 776    {
 25377777        Span<ulong> temporary =
 25377778            stackalloc ulong[result.Length];
 25377779        WideArithmetic.MultiplyMagnitudes(first, second, temporary);
 25377780        WideArithmetic.MultiplyMagnitudes(temporary, third, result);
 25377781    }
 782
 783    private static void Multiply4(
 784        ReadOnlySpan<ulong> first,
 785        ReadOnlySpan<ulong> second,
 786        ReadOnlySpan<ulong> third,
 787        ReadOnlySpan<ulong> fourth,
 788        Span<ulong> result)
 789    {
 2180790        Span<ulong> firstProduct =
 2180791            stackalloc ulong[MaxResponseWords];
 2180792        Span<ulong> secondProduct =
 2180793            stackalloc ulong[MaxResponseWords];
 2180794        WideArithmetic.MultiplyMagnitudes(first, second, firstProduct);
 2180795        WideArithmetic.MultiplyMagnitudes(third, fourth, secondProduct);
 2180796        WideArithmetic.MultiplyMagnitudes(
 2180797            firstProduct,
 2180798            secondProduct,
 2180799            result);
 2180800    }
 801
 802}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/Exact/ExactContactResponseOperand3D.cs

#LineLine coverage
 1//=======================================================================
 2// ExactContactResponseOperand3D.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;
 9
 10namespace Gravitas.CollisionHandling;
 11
 12/// <summary>
 13/// Describes one participant in an exact rigid-body contact response.
 14/// </summary>
 15internal readonly struct ExactContactResponseOperand3D
 16{
 17    internal ExactContactResponseOperand3D(
 18        in ExactLever3D lever,
 19        Vector3d linearVelocity,
 20        Vector3d angularVelocity,
 21        Vector3d linearImpulseAxis,
 22        Fixed64 inverseMass,
 23        Fixed3x3 inverseInertia)
 24    {
 525825        Lever = lever;
 525826        LinearVelocity = linearVelocity;
 525827        AngularVelocity = angularVelocity;
 525828        LinearImpulseAxis = linearImpulseAxis;
 525829        InverseMass = inverseMass;
 525830        InverseInertia = inverseInertia;
 525831    }
 32
 33    internal ExactLever3D Lever { get; }
 34
 35    internal Vector3d LinearVelocity { get; }
 36
 37    internal Vector3d AngularVelocity { get; }
 38
 39    internal Vector3d LinearImpulseAxis { get; }
 40
 41    internal Fixed64 InverseMass { get; }
 42
 43    internal Fixed3x3 InverseInertia { get; }
 44}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/Exact/ExactCoulombResponse3D.cs

#LineLine coverage
 1//=======================================================================
 2// ExactCoulombResponse3D.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;
 9
 10namespace Gravitas.CollisionHandling;
 11
 12/// <summary>
 13/// Contains atomically materialized velocity deltas from an exact Coulomb
 14/// response.
 15/// </summary>
 16internal readonly struct ExactCoulombResponse3D
 17{
 18    private readonly Fixed64 _primaryAccumulatedImpulse;
 19    private readonly Fixed64 _secondaryAccumulatedImpulse;
 20    private readonly byte _projectionFlags;
 21
 22    internal ExactCoulombResponse3D(
 23        bool hasAppliedImpulse,
 24        Vector3d firstLinearVelocityDelta,
 25        Vector3d firstAngularVelocityDelta,
 26        Vector3d secondLinearVelocityDelta,
 27        Vector3d secondAngularVelocityDelta,
 28        bool hasPrimaryAccumulatedImpulse,
 29        Fixed64 primaryAccumulatedImpulse,
 30        bool hasSecondaryAccumulatedImpulse,
 31        Fixed64 secondaryAccumulatedImpulse)
 32    {
 48333        HasAppliedImpulse = hasAppliedImpulse;
 48334        FirstLinearVelocityDelta = firstLinearVelocityDelta;
 48335        FirstAngularVelocityDelta = firstAngularVelocityDelta;
 48336        SecondLinearVelocityDelta = secondLinearVelocityDelta;
 48337        SecondAngularVelocityDelta = secondAngularVelocityDelta;
 48338        _primaryAccumulatedImpulse = primaryAccumulatedImpulse;
 48339        _secondaryAccumulatedImpulse = secondaryAccumulatedImpulse;
 48340        _projectionFlags = (byte)(
 48341            (hasPrimaryAccumulatedImpulse ? 1 : 0)
 48342            | (hasSecondaryAccumulatedImpulse ? 2 : 0));
 48343    }
 44
 45    internal bool HasAppliedImpulse { get; }
 46
 47    internal Vector3d FirstLinearVelocityDelta { get; }
 48
 49    internal Vector3d FirstAngularVelocityDelta { get; }
 50
 51    internal Vector3d SecondLinearVelocityDelta { get; }
 52
 53    internal Vector3d SecondAngularVelocityDelta { get; }
 54
 55    internal bool TryGetPrimaryAccumulatedImpulse(out Fixed64 value)
 56    {
 42957        value = _primaryAccumulatedImpulse;
 42958        return (_projectionFlags & 1) != 0;
 59    }
 60
 61    internal bool TryGetSecondaryAccumulatedImpulse(out Fixed64 value)
 62    {
 39463        value = _secondaryAccumulatedImpulse;
 39464        return (_projectionFlags & 2) != 0;
 65    }
 66}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/Exact/ExactLever3D.Arithmetic.cs

#LineLine coverage
 1//=======================================================================
 2// ExactLever3D.Arithmetic.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;
 10
 11namespace Gravitas.CollisionHandling;
 12
 13/// <content>
 14/// Contains exact point-velocity, quadratic-form, and transformed-cross
 15/// arithmetic for rigid-body contact levers.
 16/// </content>
 17internal readonly partial struct ExactLever3D
 18{
 19    internal static void GetRelativePointVelocityRatio(
 20        Vector3d firstLinearVelocity,
 21        Vector3d firstAngularVelocity,
 22        in ExactLever3D firstLever,
 23        Vector3d secondLinearVelocity,
 24        Vector3d secondAngularVelocity,
 25        in ExactLever3D secondLever,
 26        Vector3d projectionAxis,
 27        out Signed832 numerator,
 28        out Signed832 denominator)
 29    {
 30        // Normalized anchors bound each lever denominator below the Signed320 width.
 270731        Signed320 firstDenominator =
 270732            Signed320.NarrowValue(firstLever.Denominator);
 270733        Signed320 secondDenominator =
 270734            Signed320.NarrowValue(secondLever.Denominator);
 270735        Signed704 firstAngular =
 270736            GetCrossProductProjectionNumerator(
 270737                firstLever,
 270738                projectionAxis,
 270739                firstAngularVelocity);
 270740        Signed704 secondAngular =
 270741            GetCrossProductProjectionNumerator(
 270742                secondLever,
 270743                projectionAxis,
 270744                secondAngularVelocity);
 270745        Signed192 linear = WideGeometry.GetDifferenceDotProduct3D(
 270746            secondLinearVelocity.X,
 270747            firstLinearVelocity.X,
 270748            secondLinearVelocity.Y,
 270749            firstLinearVelocity.Y,
 270750            secondLinearVelocity.Z,
 270751            firstLinearVelocity.Z,
 270752            projectionAxis.X,
 270753            Fixed64.Zero,
 270754            projectionAxis.Y,
 270755            Fixed64.Zero,
 270756            projectionAxis.Z,
 270757            Fixed64.Zero);
 270758        Signed576 commonLeverDenominator =
 270759            WideArithmetic.MultiplySigned320(
 270760                firstDenominator,
 270761                secondDenominator);
 270762        Signed576 linearAtCommonDenominator =
 270763            WideArithmetic.MultiplySigned576(
 270764                commonLeverDenominator,
 270765                linear,
 270766                Signed192.One);
 270767        numerator = WideArithmetic.AddSigned832(
 270768            Signed832.ExtendValue(linearAtCommonDenominator),
 270769            WideArithmetic.MultiplySigned704ToSigned832(
 270770                secondAngular,
 270771                firstDenominator));
 270772        numerator = WideArithmetic.SubtractSigned832(
 270773            numerator,
 270774            WideArithmetic.MultiplySigned704ToSigned832(
 270775                firstAngular,
 270776                secondDenominator));
 270777        Signed576 narrowDenominator = WideArithmetic.MultiplySigned576(
 270778            commonLeverDenominator,
 270779            Signed192.One,
 270780            Signed192.One);
 270781        denominator = Signed832.ExtendValue(narrowDenominator);
 270782    }
 83
 84    internal static void GetCrossProductQuadraticFormRatio(
 85        in ExactLever3D lever,
 86        Vector3d crossVector,
 87        Fixed3x3 transform,
 88        out Signed832 numerator,
 89        out Signed832 denominator)
 90    {
 541491        GetCrossProductNumerators(
 541492            lever.XNumerator,
 541493            lever.YNumerator,
 541494            lever.ZNumerator,
 541495            crossVector,
 541496            out Signed704 crossX,
 541497            out Signed704 crossY,
 541498            out Signed704 crossZ);
 99
 5414100        numerator = GetQuadraticTerm(
 5414101            crossX, crossX, transform.M11);
 5414102        numerator = WideArithmetic.AddSigned832(
 5414103            numerator,
 5414104            GetQuadraticTerm(crossX, crossY, transform.M21));
 5414105        numerator = WideArithmetic.AddSigned832(
 5414106            numerator,
 5414107            GetQuadraticTerm(crossX, crossZ, transform.M31));
 5414108        numerator = WideArithmetic.AddSigned832(
 5414109            numerator,
 5414110            GetQuadraticTerm(crossY, crossX, transform.M12));
 5414111        numerator = WideArithmetic.AddSigned832(
 5414112            numerator,
 5414113            GetQuadraticTerm(crossY, crossY, transform.M22));
 5414114        numerator = WideArithmetic.AddSigned832(
 5414115            numerator,
 5414116            GetQuadraticTerm(crossY, crossZ, transform.M32));
 5414117        numerator = WideArithmetic.AddSigned832(
 5414118            numerator,
 5414119            GetQuadraticTerm(crossZ, crossX, transform.M13));
 5414120        numerator = WideArithmetic.AddSigned832(
 5414121            numerator,
 5414122            GetQuadraticTerm(crossZ, crossY, transform.M23));
 5414123        numerator = WideArithmetic.AddSigned832(
 5414124            numerator,
 5414125            GetQuadraticTerm(crossZ, crossZ, transform.M33));
 126
 5414127        denominator = WideArithmetic.MultiplySigned576ToSigned832(
 5414128            lever.Denominator,
 5414129            lever.Denominator);
 130        // A raw quadratic result carries four Q32.32 denominator factors.
 5414131        denominator = WideArithmetic.MultiplySigned832(
 5414132            denominator,
 5414133            Signed192.One);
 5414134        denominator = WideArithmetic.MultiplySigned832(
 5414135            denominator,
 5414136            Signed192.One);
 5414137        denominator = WideArithmetic.MultiplySigned832(
 5414138            denominator,
 5414139            Signed192.One);
 5414140        denominator = WideArithmetic.MultiplySigned832(
 5414141            denominator,
 5414142            Signed192.One);
 5414143    }
 144
 145    internal static bool TryGetTransformedScaledCrossProduct(
 146        in ExactLever3D lever,
 147        Vector3d crossVector,
 148        Fixed3x3 transform,
 149        Fixed64 firstMultiplier,
 150        Fixed64 secondMultiplier,
 151        Fixed64 divisor,
 152        out Vector3d result)
 153    {
 95154        GetTransformedCrossProduct(
 95155            lever,
 95156            crossVector,
 95157            transform,
 95158            out Signed832 transformedX,
 95159            out Signed832 transformedY,
 95160            out Signed832 transformedZ);
 95161        Signed832 denominator = GetTransformedCrossProductDenominator(
 95162            lever,
 95163            Signed192.Raw(divisor));
 95164        return TryGetScaledVector(
 95165            transformedX,
 95166            transformedY,
 95167            transformedZ,
 95168            firstMultiplier,
 95169            secondMultiplier,
 95170            denominator,
 95171            out result);
 172    }
 173
 174    internal static bool TryGetTransformedWeightedCrossProduct(
 175        in ExactLever3D lever,
 176        Vector3d first,
 177        Fixed64 firstScale,
 178        Vector3d second,
 179        Fixed64 secondScale,
 180        Vector3d third,
 181        Fixed64 thirdScale,
 182        Fixed3x3 transform,
 183        out Vector3d result)
 184    {
 7185        GetTransformedCrossProduct(
 7186            lever,
 7187            first,
 7188            transform,
 7189            out Signed832 firstX,
 7190            out Signed832 firstY,
 7191            out Signed832 firstZ);
 7192        GetTransformedCrossProduct(
 7193            lever,
 7194            second,
 7195            transform,
 7196            out Signed832 secondX,
 7197            out Signed832 secondY,
 7198            out Signed832 secondZ);
 7199        GetTransformedCrossProduct(
 7200            lever,
 7201            third,
 7202            transform,
 7203            out Signed832 thirdX,
 7204            out Signed832 thirdY,
 7205            out Signed832 thirdZ);
 7206        Signed832 denominator = GetTransformedCrossProductDenominator(
 7207            lever,
 7208            Signed192.One);
 7209        bool representable = TryGetWeightedComponent(
 7210                firstX,
 7211                firstScale,
 7212                secondX,
 7213                secondScale,
 7214                thirdX,
 7215                thirdScale,
 7216                denominator,
 7217                out Fixed64 resultX)
 7218            & TryGetWeightedComponent(
 7219                firstY,
 7220                firstScale,
 7221                secondY,
 7222                secondScale,
 7223                thirdY,
 7224                thirdScale,
 7225                denominator,
 7226                out Fixed64 resultY)
 7227            & TryGetWeightedComponent(
 7228                firstZ,
 7229                firstScale,
 7230                secondZ,
 7231                secondScale,
 7232                thirdZ,
 7233                thirdScale,
 7234                denominator,
 7235                out Fixed64 resultZ);
 7236        result = representable
 7237            ? new Vector3d(resultX, resultY, resultZ)
 7238            : default;
 7239        return representable;
 240    }
 241
 242    internal static void GetTransformedCrossProduct(
 243        in ExactLever3D lever,
 244        Vector3d crossVector,
 245        Fixed3x3 transform,
 246        out Signed832 x,
 247        out Signed832 y,
 248        out Signed832 z)
 249    {
 4480250        GetCrossProductNumerators(
 4480251            lever.XNumerator,
 4480252            lever.YNumerator,
 4480253            lever.ZNumerator,
 4480254            crossVector,
 4480255            out Signed704 crossX,
 4480256            out Signed704 crossY,
 4480257            out Signed704 crossZ);
 4480258        x = TransformCrossProduct(
 4480259            crossX,
 4480260            crossY,
 4480261            crossZ,
 4480262            transform.M11,
 4480263            transform.M21,
 4480264            transform.M31);
 4480265        y = TransformCrossProduct(
 4480266            crossX,
 4480267            crossY,
 4480268            crossZ,
 4480269            transform.M12,
 4480270            transform.M22,
 4480271            transform.M32);
 4480272        z = TransformCrossProduct(
 4480273            crossX,
 4480274            crossY,
 4480275            crossZ,
 4480276            transform.M13,
 4480277            transform.M23,
 4480278            transform.M33);
 4480279    }
 280
 281    private static bool TryGetScaledVector(
 282        Signed832 transformedX,
 283        Signed832 transformedY,
 284        Signed832 transformedZ,
 285        Fixed64 firstMultiplier,
 286        Fixed64 secondMultiplier,
 287        Signed832 denominator,
 288        out Vector3d result)
 289    {
 95290        bool representable = TryGetScaledComponent(
 95291                transformedX,
 95292                firstMultiplier,
 95293                secondMultiplier,
 95294                denominator,
 95295                out Fixed64 resultX)
 95296            & TryGetScaledComponent(
 95297                transformedY,
 95298                firstMultiplier,
 95299                secondMultiplier,
 95300                denominator,
 95301                out Fixed64 resultY)
 95302            & TryGetScaledComponent(
 95303                transformedZ,
 95304                firstMultiplier,
 95305                secondMultiplier,
 95306                denominator,
 95307                out Fixed64 resultZ);
 95308        result = representable
 95309            ? new Vector3d(resultX, resultY, resultZ)
 95310            : default;
 95311        return representable;
 312    }
 313
 314    private static void GetRawCrossProduct(
 315        Vector3d first,
 316        Vector3d second,
 317        out Signed320 x,
 318        out Signed320 y,
 319        out Signed320 z)
 320    {
 5414321        x = WideArithmetic.SubtractSigned320(
 5414322            WideArithmetic.MultiplySigned192(
 5414323                Signed192.Raw(first.Y),
 5414324                Signed192.Raw(second.Z)),
 5414325            WideArithmetic.MultiplySigned192(
 5414326                Signed192.Raw(first.Z),
 5414327                Signed192.Raw(second.Y)));
 5414328        y = WideArithmetic.SubtractSigned320(
 5414329            WideArithmetic.MultiplySigned192(
 5414330                Signed192.Raw(first.Z),
 5414331                Signed192.Raw(second.X)),
 5414332            WideArithmetic.MultiplySigned192(
 5414333                Signed192.Raw(first.X),
 5414334                Signed192.Raw(second.Z)));
 5414335        z = WideArithmetic.SubtractSigned320(
 5414336            WideArithmetic.MultiplySigned192(
 5414337                Signed192.Raw(first.X),
 5414338                Signed192.Raw(second.Y)),
 5414339            WideArithmetic.MultiplySigned192(
 5414340                Signed192.Raw(first.Y),
 5414341                Signed192.Raw(second.X)));
 5414342    }
 343
 344    private static Signed704 GetCrossProductProjectionNumerator(
 345        in ExactLever3D lever,
 346        Vector3d crossVector,
 347        Vector3d projectionVector)
 348    {
 5414349        GetRawCrossProduct(
 5414350            crossVector,
 5414351            projectionVector,
 5414352            out Signed320 coefficientX,
 5414353            out Signed320 coefficientY,
 5414354            out Signed320 coefficientZ);
 5414355        return WideArithmetic.AddSigned704(
 5414356            WideArithmetic.AddSigned704(
 5414357                WideArithmetic.MultiplySigned576ToSigned704(
 5414358                    lever.XNumerator,
 5414359                    coefficientX),
 5414360                WideArithmetic.MultiplySigned576ToSigned704(
 5414361                    lever.YNumerator,
 5414362                    coefficientY)),
 5414363            WideArithmetic.MultiplySigned576ToSigned704(
 5414364                lever.ZNumerator,
 5414365                coefficientZ));
 366    }
 367
 368    private static void GetCrossProductNumerators(
 369        Signed576 x,
 370        Signed576 y,
 371        Signed576 z,
 372        Vector3d vector,
 373        out Signed704 crossX,
 374        out Signed704 crossY,
 375        out Signed704 crossZ)
 376    {
 9894377        Signed320 vectorX =
 9894378            Signed320.ExtendValue(Signed192.Raw(vector.X));
 9894379        Signed320 vectorY =
 9894380            Signed320.ExtendValue(Signed192.Raw(vector.Y));
 9894381        Signed320 vectorZ =
 9894382            Signed320.ExtendValue(Signed192.Raw(vector.Z));
 9894383        crossX = WideArithmetic.SubtractSigned704(
 9894384            WideArithmetic.MultiplySigned576ToSigned704(y, vectorZ),
 9894385            WideArithmetic.MultiplySigned576ToSigned704(z, vectorY));
 9894386        crossY = WideArithmetic.SubtractSigned704(
 9894387            WideArithmetic.MultiplySigned576ToSigned704(z, vectorX),
 9894388            WideArithmetic.MultiplySigned576ToSigned704(x, vectorZ));
 9894389        crossZ = WideArithmetic.SubtractSigned704(
 9894390            WideArithmetic.MultiplySigned576ToSigned704(x, vectorY),
 9894391            WideArithmetic.MultiplySigned576ToSigned704(y, vectorX));
 9894392    }
 393
 394    private static Signed832 TransformCrossProduct(
 395        Signed704 x,
 396        Signed704 y,
 397        Signed704 z,
 398        Fixed64 coefficientX,
 399        Fixed64 coefficientY,
 400        Fixed64 coefficientZ) =>
 13440401        WideArithmetic.AddSigned832(
 13440402            WideArithmetic.AddSigned832(
 13440403                WideArithmetic.MultiplySigned704ToSigned832(
 13440404                    x,
 13440405                    Signed192.Raw(coefficientX)),
 13440406                WideArithmetic.MultiplySigned704ToSigned832(
 13440407                    y,
 13440408                    Signed192.Raw(coefficientY))),
 13440409            WideArithmetic.MultiplySigned704ToSigned832(
 13440410                z,
 13440411                Signed192.Raw(coefficientZ)));
 412
 413    private static Signed832 GetTransformedCrossProductDenominator(
 414        in ExactLever3D lever,
 415        Signed192 divisor)
 416    {
 102417        Signed320 fixedScaleSquared = WideArithmetic.MultiplySigned192(
 102418            Signed192.One,
 102419            Signed192.One);
 102420        Signed704 transformedDenominator =
 102421            WideArithmetic.MultiplySigned576ToSigned704(
 102422                lever.Denominator,
 102423                fixedScaleSquared);
 102424        Signed320 scaleAndDivisor = WideArithmetic.MultiplySigned192(
 102425            Signed192.One,
 102426            divisor);
 102427        return WideArithmetic.MultiplySigned704ToSigned832(
 102428            transformedDenominator,
 102429            scaleAndDivisor);
 430    }
 431
 432    private static bool TryGetScaledComponent(
 433        Signed832 value,
 434        Fixed64 firstMultiplier,
 435        Fixed64 secondMultiplier,
 436        Signed832 denominator,
 437        out Fixed64 result) =>
 285438        Fixed64.TryGetSignedRawRatio(
 285439            WideArithmetic.MultiplySigned832(
 285440                WideArithmetic.MultiplySigned832(
 285441                    value,
 285442                    Signed192.Raw(firstMultiplier)),
 285443                Signed192.Raw(secondMultiplier)),
 285444            denominator,
 285445            0,
 285446            out result);
 447
 448    private static bool TryGetWeightedComponent(
 449        Signed832 first,
 450        Fixed64 firstScale,
 451        Signed832 second,
 452        Fixed64 secondScale,
 453        Signed832 third,
 454        Fixed64 thirdScale,
 455        Signed832 denominator,
 456        out Fixed64 result)
 457    {
 21458        Signed832 numerator = WideArithmetic.AddSigned832(
 21459            WideArithmetic.AddSigned832(
 21460                WideArithmetic.MultiplySigned832(
 21461                    first,
 21462                    Signed192.Raw(firstScale)),
 21463                WideArithmetic.MultiplySigned832(
 21464                    second,
 21465                    Signed192.Raw(secondScale))),
 21466            WideArithmetic.MultiplySigned832(
 21467                third,
 21468                Signed192.Raw(thirdScale)));
 21469        return Fixed64.TryGetSignedRawRatio(
 21470            WideArithmetic.MultiplySigned832(
 21471                numerator,
 21472                Signed192.One),
 21473            denominator,
 21474            0,
 21475            out result);
 476    }
 477
 478    private static Signed832 GetQuadraticTerm(
 479        Signed704 left,
 480        Signed704 right,
 481        Fixed64 coefficient)
 482    {
 48726483        if (left.IsZero | right.IsZero | coefficient == Fixed64.Zero)
 43453484            return default;
 485
 5273486        return WideArithmetic.MultiplySigned704ToSigned832(
 5273487            left,
 5273488            right,
 5273489            Signed192.Raw(coefficient));
 490    }
 491}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/Exact/ExactLever3D.cs

#LineLine coverage
 1//=======================================================================
 2// ExactLever3D.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;
 10
 11namespace Gravitas.CollisionHandling;
 12
 13/// <summary>
 14/// Represents a rigid-body contact lever without narrowing its point-anchor
 15/// displacement to the Q32.32 scalar domain.
 16/// </summary>
 17internal readonly partial struct ExactLever3D
 18{
 19    private ExactLever3D(
 20        Signed576 xNumerator,
 21        Signed576 yNumerator,
 22        Signed576 zNumerator,
 23        Signed576 denominator)
 24    {
 429625        XNumerator = xNumerator;
 429626        YNumerator = yNumerator;
 429627        ZNumerator = zNumerator;
 429628        Denominator = denominator;
 429629    }
 30
 31    internal Signed576 XNumerator { get; }
 32
 33    internal Signed576 YNumerator { get; }
 34
 35    internal Signed576 ZNumerator { get; }
 36
 37    internal Signed576 Denominator { get; }
 38
 39    internal static ExactLever3D Create(
 40        in FixedPointAnchor point,
 41        in FixedPointAnchor center)
 42    {
 410443        WidePointAnchor3d.GetExactRelativeOffsetRatio(
 410444            point.Origin,
 410445            point.Rotation,
 410446            point.LocalPoint,
 410447            point.LocalDisplacement,
 410448            point.LocalTranslation,
 410449            point.ExactLocalTerm,
 410450            center.Origin,
 410451            center.Rotation,
 410452            center.LocalPoint,
 410453            center.LocalDisplacement,
 410454            center.LocalTranslation,
 410455            center.ExactLocalTerm,
 410456            out Signed576 x,
 410457            out Signed576 y,
 410458            out Signed576 z,
 410459            out Signed576 denominator);
 410460        return new ExactLever3D(x, y, z, denominator);
 61    }
 62
 63    internal static ExactLever3D CreateXZ(
 64        in FixedPointAnchor2d point,
 65        in FixedPointAnchor2d center)
 66    {
 19267        WidePointAnchor2d.GetExactRelativeOffsetRatio(
 19268            point.Origin,
 19269            point.LocalPoint,
 19270            point.LocalDisplacement,
 19271            point.ExactLocalTerm,
 19272            point.Rotation,
 19273            center.Origin,
 19274            center.LocalPoint,
 19275            center.LocalDisplacement,
 19276            center.ExactLocalTerm,
 19277            center.Rotation,
 19278            out Signed320 x,
 19279            out Signed320 z,
 19280            out Signed320 denominator);
 19281        return new ExactLever3D(
 19282            Signed576.ExtendValue(x),
 19283            default,
 19284            Signed576.ExtendValue(z),
 19285            Signed576.ExtendValue(denominator));
 86    }
 87}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/Exact/ExactNormalConstraint3D.cs

#LineLine coverage
 1//=======================================================================
 2// ExactNormalConstraint3D.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;
 9
 10namespace Gravitas.CollisionHandling;
 11
 12/// <summary>
 13/// Describes the normal constraint that supplies a Coulomb response.
 14/// </summary>
 15internal readonly struct ExactNormalConstraint3D
 16{
 17    internal ExactNormalConstraint3D(
 18        in ExactContactResponseOperand3D first,
 19        in ExactContactResponseOperand3D second,
 20        Vector3d normal,
 21        Fixed64 restitution,
 22        Fixed64 restitutionVelocityThreshold,
 23        Fixed64 accumulatedImpulse,
 24        Fixed64 positiveImpulseScale,
 25        Fixed64 negativeImpulseScale)
 26    {
 8727        First = first;
 8728        Second = second;
 8729        Normal = normal;
 8730        Restitution = restitution;
 8731        RestitutionVelocityThreshold = restitutionVelocityThreshold;
 8732        AccumulatedImpulse = accumulatedImpulse;
 8733        PositiveImpulseScale = positiveImpulseScale;
 8734        NegativeImpulseScale = negativeImpulseScale;
 8735    }
 36
 37    internal ExactContactResponseOperand3D First { get; }
 38
 39    internal ExactContactResponseOperand3D Second { get; }
 40
 41    internal Vector3d Normal { get; }
 42
 43    internal Fixed64 Restitution { get; }
 44
 45    internal Fixed64 RestitutionVelocityThreshold { get; }
 46
 47    internal Fixed64 AccumulatedImpulse { get; }
 48
 49    internal Fixed64 PositiveImpulseScale { get; }
 50
 51    internal Fixed64 NegativeImpulseScale { get; }
 52}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/Exact/ExactNormalResponse3D.cs

#LineLine coverage
 1//=======================================================================
 2// ExactNormalResponse3D.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;
 9
 10namespace Gravitas.CollisionHandling;
 11
 12/// <summary>
 13/// Contains atomically materialized velocity deltas from an exact normal
 14/// response.
 15/// </summary>
 16internal readonly struct ExactNormalResponse3D
 17{
 18    private readonly Fixed64 _normalVelocity;
 19    private readonly Fixed64 _appliedImpulse;
 20    private readonly Fixed64 _accumulatedImpulse;
 21    private readonly byte _projectionFlags;
 22
 23    internal ExactNormalResponse3D(
 24        bool isClosing,
 25        bool hasAppliedImpulse,
 26        Vector3d firstLinearVelocityDelta,
 27        Vector3d firstAngularVelocityDelta,
 28        Vector3d secondLinearVelocityDelta,
 29        Vector3d secondAngularVelocityDelta,
 30        bool hasNormalVelocity,
 31        Fixed64 normalVelocity,
 32        bool hasAppliedImpulseProjection,
 33        Fixed64 appliedImpulse,
 34        bool hasAccumulatedImpulse,
 35        Fixed64 accumulatedImpulse)
 36    {
 164537        IsClosing = isClosing;
 164538        HasAppliedImpulse = hasAppliedImpulse;
 164539        FirstLinearVelocityDelta = firstLinearVelocityDelta;
 164540        FirstAngularVelocityDelta = firstAngularVelocityDelta;
 164541        SecondLinearVelocityDelta = secondLinearVelocityDelta;
 164542        SecondAngularVelocityDelta = secondAngularVelocityDelta;
 164543        _normalVelocity = normalVelocity;
 164544        _appliedImpulse = appliedImpulse;
 164545        _accumulatedImpulse = accumulatedImpulse;
 164546        _projectionFlags = (byte)(
 164547            (hasNormalVelocity ? 1 : 0)
 164548            | (hasAppliedImpulseProjection ? 2 : 0)
 164549            | (hasAccumulatedImpulse ? 4 : 0));
 164550    }
 51
 52    internal bool IsClosing { get; }
 53
 54    internal bool HasAppliedImpulse { get; }
 55
 56    internal Vector3d FirstLinearVelocityDelta { get; }
 57
 58    internal Vector3d FirstAngularVelocityDelta { get; }
 59
 60    internal Vector3d SecondLinearVelocityDelta { get; }
 61
 62    internal Vector3d SecondAngularVelocityDelta { get; }
 63
 64    internal bool TryGetNormalVelocity(out Fixed64 value)
 65    {
 161966        value = _normalVelocity;
 161967        return (_projectionFlags & 1) != 0;
 68    }
 69
 70    internal bool TryGetAppliedImpulse(out Fixed64 value)
 71    {
 145972        value = _appliedImpulse;
 145973        return (_projectionFlags & 2) != 0;
 74    }
 75
 76    internal bool TryGetAccumulatedImpulse(out Fixed64 value)
 77    {
 145878        value = _accumulatedImpulse;
 145879        return (_projectionFlags & 4) != 0;
 80    }
 81}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/Mixed/CollisionResponseMixed.cs

#LineLine coverage
 1//=======================================================================
 2// CollisionResponseMixed.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.Materials;
 12using System.Runtime.CompilerServices;
 13
 14namespace Gravitas.CollisionHandling;
 15
 16/// <summary>
 17/// Solves deterministic mixed 3D/2D contact response with the 2D body constrained to its X/Z plane.
 18/// </summary>
 19public static class CollisionResponseMixed
 20{
 21    /// <summary>Penetration tolerated before positional correction is applied.</summary>
 122    public static readonly Fixed64 PenetrationSlop = (Fixed64)0.01f;
 23
 24    /// <summary>Fraction of excess penetration corrected by the mixed solver.</summary>
 125    public static readonly Fixed64 PenetrationCorrectionPercent = (Fixed64)0.8f;
 26
 27    internal static bool Resolve(CollisionPairMixed pair, MixedContact contact) =>
 13528        Resolve(pair, contact, iteration: 0, iterationLimit: 1, applyPositionCorrection: true);
 29
 30    internal static bool Resolve(
 31        CollisionPairMixed pair,
 32        MixedContact contact,
 33        int iteration,
 34        int iterationLimit,
 35        bool applyPositionCorrection)
 36    {
 30937        if (!contact.HasContact || pair.Collider3D.IsTrigger || pair.Collider2D.IsTrigger)
 738            return false;
 39
 30240        SolidBody? body3D = pair.Collider3D.Body;
 30241        SolidBody2D? body2D = pair.Collider2D.Body;
 30242        Vector3d normal = ResolveNormal(pair, contact);
 30243        bool hasPlanarResponseCoupling = normal.ToVector2d() != Vector2d.Zero;
 44
 30245        Fixed64 correctionInverseMass = GetConstrainedInverseMass(body3D, normal)
 30246            + GetConstrainedPlanarInverseMass(body2D, normal);
 47
 30248        Vector2d relative2D = default;
 30249        bool resolved3D = body3D != null
 30250            ? body3D.TryGetOffsetFromCenterOfMass(contact.Anchor3D, out Vector3d relative3D)
 30251            : contact.Anchor3D.TryGetOffsetFrom(pair.Collider3D.Center, out relative3D);
 30252        bool resolved2D = body2D == null
 30253            || contact.TryGetPlanarOffset2DFrom(
 30254                body2D.Position,
 30255                body2D.Rotation,
 30256                body2D.LocalCenterOfMassOffset,
 30257                out relative2D);
 30258        if (body2D == null)
 2959            relative2D = Vector2d.Zero;
 30260        Vector3d normalLinearVelocity3D =
 30261            body3D == null
 30262                ? Vector3d.Zero
 30263                : ResolveLinearVelocity(body3D);
 30264        Vector3d normalAngularVelocity3D =
 30265            body3D == null
 30266                ? Vector3d.Zero
 30267                : ResolveAngularVelocity(body3D);
 30268        Vector2d normalLinearVelocity2D =
 30269            body2D == null
 30270                ? Vector2d.Zero
 30271                : ResolveLinearVelocity(body2D);
 30272        Fixed64 normalAngularVelocity2D =
 30273            body2D == null
 30274                ? Fixed64.Zero
 30275                : ResolveAngularVelocity(body2D);
 76
 30277        bool normalResolved = TryGetNormalImpulse(
 30278            pair,
 30279            contact,
 30280            body3D,
 30281            body2D,
 30282            normal,
 30283            relative3D,
 30284            relative2D,
 30285            resolved3D,
 30286            resolved2D,
 30287            normalLinearVelocity3D,
 30288            normalAngularVelocity3D,
 30289            normalLinearVelocity2D,
 30290            normalAngularVelocity2D,
 30291            out ContactNormalImpulseResultMixed normalResult);
 30292        if (!normalResolved
 30293            || !CanApplyNormalVelocityDeltas(body3D, body2D, normalResult))
 94        {
 395            GravitasLogger.Channel.Write(
 396                DiagnosticLevel.Error,
 397                "Mixed contact response is outside the representable velocity domain.",
 398                nameof(CollisionResponseMixed));
 399            return false;
 100        }
 101
 299102        if (applyPositionCorrection && correctionInverseMass > Fixed64.Zero)
 151103            ApplyPositionCorrection(body3D, body2D, normal, contact.Depth, correctionInverseMass);
 104
 299105        bool appliedImpulse = HasVelocityDelta(normalResult);
 299106        if (appliedImpulse)
 107        {
 113108            if (normalResult.HasRepresentableAppliedImpulse
 113109                && normalResult.HasRepresentableNormalVelocity)
 110            {
 109111                pair.Context.Diagnostics.EmitMixedResponseImpulse(
 109112                    pair,
 109113                    contact,
 109114                    normal * normalResult.AppliedImpulseScalar,
 109115                    normalResult.NormalVelocity,
 109116                    iteration,
 109117                    iterationLimit);
 118            }
 113119            ApplyNormalVelocityDeltas(body3D, body2D, normalResult);
 120        }
 299121        PhysicsMaterial material3D = contact.HasMaterialOverride
 299122            ? contact.Material3D
 299123            : pair.Collider3D.Material;
 299124        PhysicsMaterial material2D = contact.HasMaterialOverride
 299125            ? contact.Material2D
 299126            : pair.Collider2D.Material;
 299127        bool frictionResolved = TryApplyFrictionImpulse(
 299128            pair,
 299129            contact,
 299130            body3D,
 299131            body2D,
 299132            normal,
 299133            relative3D,
 299134            relative2D,
 299135            material3D,
 299136            material2D,
 299137            normalResult,
 299138            normalLinearVelocity3D,
 299139            normalAngularVelocity3D,
 299140            normalLinearVelocity2D,
 299141            normalAngularVelocity2D,
 299142            pair.Context.Settings.RestitutionVelocityThreshold,
 299143            hasPlanarResponseCoupling,
 299144            resolved3D,
 299145            resolved2D,
 299146            out bool frictionApplied);
 299147        if (!frictionResolved)
 148        {
 3149            GravitasLogger.Channel.Write(
 3150                DiagnosticLevel.Error,
 3151                "Mixed contact friction is outside the representable velocity domain.",
 3152                nameof(CollisionResponseMixed));
 153        }
 154
 299155        return appliedImpulse | frictionApplied;
 156    }
 157
 158    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 159    internal static bool HasPlanarResponseCoupling(CollisionPairMixed pair, MixedContact contact) =>
 144160        ResolveNormal(pair, contact).ToVector2d() != Vector2d.Zero;
 161
 162    private static void ApplyPositionCorrection(
 163        SolidBody? body3D,
 164        SolidBody2D? body2D,
 165        Vector3d normal,
 166        Fixed64 depth,
 167        Fixed64 effectiveInverseMass)
 168    {
 151169        Fixed64 correctionDepth = depth - PenetrationSlop;
 151170        if (correctionDepth <= Fixed64.Zero)
 23171            return;
 172
 128173        Fixed64 correctionScalar = correctionDepth * PenetrationCorrectionPercent / effectiveInverseMass;
 128174        Fixed64 constrainedInverseMass3D = GetConstrainedInverseMass(body3D, normal);
 128175        if (constrainedInverseMass3D > Fixed64.Zero)
 123176            body3D!.ApplyCollisionPositionCorrection(-normal * (correctionScalar * constrainedInverseMass3D));
 177
 128178        Fixed64 constrainedInverseMass2D = GetConstrainedPlanarInverseMass(body2D, normal);
 128179        if (constrainedInverseMass2D <= Fixed64.Zero)
 82180            return;
 181
 46182        Vector2d planarNormal = normal.ToVector2d();
 46183        body2D!.ApplyCollisionPositionCorrection(planarNormal * (correctionScalar * constrainedInverseMass2D));
 46184    }
 185
 186    private static bool TryGetNormalImpulse(
 187        CollisionPairMixed pair,
 188        MixedContact contact,
 189        SolidBody? body3D,
 190        SolidBody2D? body2D,
 191        Vector3d normal,
 192        Vector3d relative3D,
 193        Vector2d relative2D,
 194        bool hasCompact3D,
 195        bool hasCompact2D,
 196        Vector3d linearVelocity3D,
 197        Vector3d angularVelocity3D,
 198        Vector2d linearVelocity2D,
 199        Fixed64 angularVelocity2D,
 200        out ContactNormalImpulseResultMixed result)
 201    {
 302202        PhysicsMaterial material3D = contact.HasMaterialOverride
 302203            ? contact.Material3D
 302204            : pair.Collider3D.Material;
 302205        PhysicsMaterial material2D = contact.HasMaterialOverride
 302206            ? contact.Material2D
 302207            : pair.Collider2D.Material;
 302208        Fixed64 restitution =
 302209            PhysicsMaterial.CombineRestitution(material3D, material2D);
 302210        Fixed64 restitutionVelocityThreshold =
 302211            pair.Context.Settings.RestitutionVelocityThreshold;
 302212        if (hasCompact3D
 302213            && hasCompact2D
 302214            && ContactNormalImpulseMixed.CanUseCompactResponse(
 302215                body3D,
 302216                linearVelocity3D,
 302217                angularVelocity3D,
 302218                relative3D,
 302219                body2D,
 302220                linearVelocity2D,
 302221                angularVelocity2D,
 302222                relative2D,
 302223                normal)
 302224            && ContactNormalImpulseMixed.TryCalculateAccumulatedDelta(
 302225                body3D,
 302226                linearVelocity3D,
 302227                angularVelocity3D,
 302228                relative3D,
 302229                body2D,
 302230                linearVelocity2D,
 302231                angularVelocity2D,
 302232                relative2D,
 302233                normal,
 302234                restitution,
 302235                restitutionVelocityThreshold,
 302236                Fixed64.Zero,
 302237                Fixed64.One,
 302238                Fixed64.One,
 302239                out result))
 240        {
 273241            return true;
 242        }
 243
 29244        GetExactLevers(
 29245            pair,
 29246            contact,
 29247            body3D,
 29248            body2D,
 29249            out ExactLever3D exact3D,
 29250            out ExactLever3D exact2D);
 29251        return ContactNormalImpulseMixed.TryCalculateAccumulatedDeltaExact(
 29252            body3D,
 29253            linearVelocity3D,
 29254            angularVelocity3D,
 29255            exact3D,
 29256            body2D,
 29257            linearVelocity2D,
 29258            angularVelocity2D,
 29259            exact2D,
 29260            normal,
 29261            restitution,
 29262            restitutionVelocityThreshold,
 29263            Fixed64.Zero,
 29264            Fixed64.One,
 29265            Fixed64.One,
 29266            out result);
 267    }
 268
 269    private static bool CanApplyNormalVelocityDeltas(
 270        SolidBody? body3D,
 271        SolidBody2D? body2D,
 272        ContactNormalImpulseResultMixed result) =>
 300273        CanApplyVelocityDeltas(
 300274            body3D,
 300275            body2D,
 300276            result.LinearVelocityDelta3D,
 300277            result.AngularVelocityDelta3D,
 300278            result.LinearVelocityDelta2D,
 300279            result.AngularVelocityDelta2D);
 280
 281    private static bool HasVelocityDelta(
 282        ContactNormalImpulseResultMixed result) =>
 299283        result.LinearVelocityDelta3D != Vector3d.Zero
 299284        || result.AngularVelocityDelta3D != Vector3d.Zero
 299285        || result.LinearVelocityDelta2D != Vector2d.Zero
 299286        || result.AngularVelocityDelta2D != Fixed64.Zero;
 287
 288    private static void ApplyNormalVelocityDeltas(
 289        SolidBody? body3D,
 290        SolidBody2D? body2D,
 291        ContactNormalImpulseResultMixed result) =>
 113292        ApplyVelocityDeltas(
 113293            body3D,
 113294            body2D,
 113295            result.LinearVelocityDelta3D,
 113296            result.AngularVelocityDelta3D,
 113297            result.LinearVelocityDelta2D,
 113298            result.AngularVelocityDelta2D);
 299
 300    private static bool CanApplyVelocityDeltas(
 301        SolidBody? body3D,
 302        SolidBody2D? body2D,
 303        Vector3d linear3D,
 304        Vector3d angular3D,
 305        Vector2d linear2D,
 306        Fixed64 angular2D)
 307    {
 343308        bool response3DFits = body3D?.CanApplyCollisionVelocityDeltas(
 343309                linear3D,
 343310                angular3D)
 343311            ?? true;
 343312        bool response2DFits = body2D?.CanApplyCollisionVelocityDeltas(
 343313                linear2D,
 343314                angular2D)
 343315            ?? true;
 343316        return response3DFits & response2DFits;
 317    }
 318
 319    private static void ApplyVelocityDeltas(
 320        SolidBody? body3D,
 321        SolidBody2D? body2D,
 322        Vector3d linear3D,
 323        Vector3d angular3D,
 324        Vector2d linear2D,
 325        Fixed64 angular2D)
 326    {
 154327        if (body3D != null)
 328        {
 150329            body3D.ApplyCollisionLinearVelocityDelta(linear3D);
 150330            body3D.ApplyCollisionAngularVelocityDelta(angular3D);
 331        }
 332
 154333        if (body2D != null)
 334        {
 146335            body2D.ApplyCollisionLinearVelocityDelta(linear2D);
 146336            body2D.ApplyCollisionAngularVelocityDelta(angular2D);
 337        }
 154338    }
 339
 340    private static bool TryApplyFrictionImpulse(
 341        CollisionPairMixed pair,
 342        MixedContact contact,
 343        SolidBody? body3D,
 344        SolidBody2D? body2D,
 345        Vector3d normal,
 346        Vector3d relative3D,
 347        Vector2d relative2D,
 348        PhysicsMaterial material3D,
 349        PhysicsMaterial material2D,
 350        ContactNormalImpulseResultMixed normalResult,
 351        Vector3d normalLinearVelocity3D,
 352        Vector3d normalAngularVelocity3D,
 353        Vector2d normalLinearVelocity2D,
 354        Fixed64 normalAngularVelocity2D,
 355        Fixed64 restitutionVelocityThreshold,
 356        bool applyTo2D,
 357        bool hasCompact3D,
 358        bool hasCompact2D,
 359        out bool applied)
 360    {
 299361        applied = false;
 299362        Fixed64 normalImpulse = normalResult.HasRepresentableAppliedImpulse
 299363            ? FixedMath.Max(
 299364                Fixed64.Zero,
 299365                normalResult.AppliedImpulseScalar)
 299366            : Fixed64.Zero;
 299367        if (normalResult.HasRepresentableAppliedImpulse
 299368            && normalImpulse <= Fixed64.Zero)
 187369            return true;
 370
 112371        PhysicsMaterial.CombineFriction(material3D, material2D, out Fixed64 staticFriction, out Fixed64 dynamicFriction)
 112372        Fixed64 restitution =
 112373            PhysicsMaterial.CombineRestitution(material3D, material2D);
 112374        if (staticFriction <= Fixed64.Zero && dynamicFriction <= Fixed64.Zero)
 8375            return true;
 376
 104377        if (normalResult.HasRepresentableAppliedImpulse
 104378            && hasCompact3D
 104379            && hasCompact2D
 104380            && TryGetCompactFrictionImpulse(
 104381                body3D,
 104382                body2D,
 104383                normal,
 104384                relative3D,
 104385                relative2D,
 104386                normalImpulse,
 104387                staticFriction,
 104388                dynamicFriction,
 104389                applyTo2D,
 104390                out Vector3d tangent,
 104391                out Fixed64 impulseScalar)
 104392            && (impulseScalar == Fixed64.Zero
 104393                || TryApplyImpulse(
 104394                    body3D,
 104395                    body2D,
 104396                    tangent,
 104397                    relative3D,
 104398                    relative2D,
 104399                    impulseScalar,
 104400                    applyTo2D)))
 401        {
 76402            applied = impulseScalar != Fixed64.Zero;
 76403            return true;
 404        }
 405
 28406        return TryApplyFrictionImpulseExact(
 28407            pair,
 28408            contact,
 28409            body3D,
 28410            body2D,
 28411            normal,
 28412            normalLinearVelocity3D,
 28413            normalAngularVelocity3D,
 28414            normalLinearVelocity2D,
 28415            normalAngularVelocity2D,
 28416            restitutionVelocityThreshold,
 28417            restitution,
 28418            staticFriction,
 28419            dynamicFriction,
 28420            applyTo2D,
 28421            out applied);
 422    }
 423
 424    private static bool TryGetCompactFrictionImpulse(
 425        SolidBody? body3D,
 426        SolidBody2D? body2D,
 427        Vector3d normal,
 428        Vector3d relative3D,
 429        Vector2d relative2D,
 430        Fixed64 normalImpulse,
 431        Fixed64 staticFriction,
 432        Fixed64 dynamicFriction,
 433        bool applyTo2D,
 434        out Vector3d tangent,
 435        out Fixed64 impulseScalar)
 436    {
 86437        tangent = default;
 86438        impulseScalar = Fixed64.Zero;
 86439        Vector3d linearVelocity3D = body3D == null
 86440            ? Vector3d.Zero
 86441            : ResolveLinearVelocity(body3D);
 86442        Vector3d angularVelocity3D = body3D == null
 86443            ? Vector3d.Zero
 86444            : ResolveAngularVelocity(body3D);
 86445        Vector3d linearVelocity2D = body2D == null
 86446            ? Vector3d.Zero
 86447            : ExactContactLever2D.ToSpatial(
 86448                ResolveLinearVelocity(body2D));
 86449        Vector3d angularVelocity2D = body2D == null
 86450            ? Vector3d.Zero
 86451            : new Vector3d(
 86452                Fixed64.Zero,
 86453                -ResolveAngularVelocity(body2D),
 86454                Fixed64.Zero);
 86455        if (!ContactResponseArithmetic3D.TryGetRelativePointVelocity(
 86456                linearVelocity3D,
 86457                angularVelocity3D,
 86458                relative3D,
 86459                linearVelocity2D,
 86460                angularVelocity2D,
 86461                ExactContactLever2D.ToSpatial(relative2D),
 86462                normal,
 86463                out Vector3d relativeVelocity)
 86464            || !TryGetCompactTangent(
 86465                relativeVelocity,
 86466                normal,
 86467                out tangent,
 86468                out bool hasTangentialMotion))
 469        {
 2470            return false;
 471        }
 84472        if (!hasTangentialMotion)
 57473            return true;
 474
 27475        bool denominatorResolved =
 27476            TryGetCompactConstrainedInverseMass(
 27477                body3D,
 27478                tangent,
 27479                out Fixed64 linear3D);
 27480        denominatorResolved &=
 27481            ContactNormalImpulse3D.TryComputeAngularDenominator(
 27482                body3D,
 27483                relative3D,
 27484                tangent,
 27485                out Fixed64 angular3D);
 27486        Fixed64 linear2D = Fixed64.Zero;
 27487        Fixed64 angular2D = Fixed64.Zero;
 27488        if (applyTo2D)
 489        {
 21490            denominatorResolved &=
 21491                TryGetCompactConstrainedPlanarInverseMass(
 21492                    body2D,
 21493                    tangent,
 21494                    out linear2D);
 21495            denominatorResolved &=
 21496                ContactNormalImpulse2D.TryComputeAngularDenominator(
 21497                    body2D,
 21498                    relative2D,
 21499                    tangent.ToVector2d(),
 21500                    out angular2D);
 501        }
 502
 27503        var denominatorTerms = new ContactEffectiveMassTerms3D(
 27504            linear3D,
 27505            linear2D,
 27506            angular3D,
 27507            angular2D);
 27508        if (!denominatorResolved
 27509            || !denominatorTerms.TryGetValue(out Fixed64 denominator))
 510        {
 3511            return false;
 512        }
 24513        if (denominator <= Fixed64.Zero)
 1514            return true;
 515
 23516        if (!ContactResponseArithmetic3D.TryDot(
 23517                relativeVelocity,
 23518                tangent,
 23519                out Fixed64 tangentVelocity)
 23520            || !Fixed64.TryMultiplyDivide(
 23521                -tangentVelocity,
 23522                Fixed64.One,
 23523                denominator,
 23524                out impulseScalar)
 23525            || !Fixed64.TryMultiplyDivide(
 23526                normalImpulse,
 23527                staticFriction,
 23528                Fixed64.One,
 23529                out Fixed64 staticLimit)
 23530            || !Fixed64.TryMultiplyDivide(
 23531                normalImpulse,
 23532                dynamicFriction,
 23533                Fixed64.One,
 23534                out Fixed64 dynamicLimit))
 535        {
 3536            return false;
 537        }
 538
 20539        if (impulseScalar < -staticLimit
 20540            || impulseScalar > staticLimit)
 541        {
 10542            impulseScalar = FixedMath.Clamp(
 10543                impulseScalar,
 10544                -dynamicLimit,
 10545                dynamicLimit);
 546        }
 20547        return true;
 548    }
 549
 550    [MethodImpl(MethodImplOptions.NoInlining)]
 551    private static bool TryApplyFrictionImpulseExact(
 552        CollisionPairMixed pair,
 553        MixedContact contact,
 554        SolidBody? body3D,
 555        SolidBody2D? body2D,
 556        Vector3d normal,
 557        Vector3d normalLinearVelocity3D,
 558        Vector3d normalAngularVelocity3D,
 559        Vector2d normalLinearVelocity2D,
 560        Fixed64 normalAngularVelocity2D,
 561        Fixed64 restitutionVelocityThreshold,
 562        Fixed64 restitution,
 563        Fixed64 staticFriction,
 564        Fixed64 dynamicFriction,
 565        bool applyTo2D,
 566        out bool applied)
 567    {
 28568        applied = false;
 28569        GetExactLevers(
 28570            pair,
 28571            contact,
 28572            body3D,
 28573            body2D,
 28574            out ExactLever3D exact3D,
 28575            out ExactLever3D exact2D);
 28576        Vector3d tangent = SolverContact.CreateTangent(normal);
 28577        Vector3d secondaryTangent =
 28578            Vector3d.Cross(normal, tangent).Normalized;
 28579        SolidBody2D? responseBody2D =
 28580            applyTo2D ? body2D : null;
 28581        Vector2d responseLinearVelocity2D =
 28582            responseBody2D == null
 28583                ? Vector2d.Zero
 28584                : normalLinearVelocity2D;
 28585        Fixed64 responseAngularVelocity2D =
 28586            responseBody2D == null
 28587                ? Fixed64.Zero
 28588                : normalAngularVelocity2D;
 28589        var normalConstraint = new ExactNormalConstraint3D(
 28590            ExactContactLever3D.CreateResponseOperand(
 28591                body3D,
 28592                normalLinearVelocity3D,
 28593                normalAngularVelocity3D,
 28594                exact3D,
 28595                -normal),
 28596            ExactContactLever2D.CreateResponseOperand(
 28597                responseBody2D,
 28598                responseLinearVelocity2D,
 28599                responseAngularVelocity2D,
 28600                exact2D,
 28601                normal),
 28602            normal,
 28603            restitution,
 28604            restitutionVelocityThreshold,
 28605            Fixed64.Zero,
 28606            Fixed64.One,
 28607            Fixed64.One);
 28608        Vector3d linearVelocity3D =
 28609            body3D == null
 28610                ? Vector3d.Zero
 28611                : ResolveLinearVelocity(body3D);
 28612        Vector3d angularVelocity3D =
 28613            body3D == null
 28614                ? Vector3d.Zero
 28615                : ResolveAngularVelocity(body3D);
 28616        Vector2d linearVelocity2D =
 28617            responseBody2D == null
 28618                ? Vector2d.Zero
 28619                : ResolveLinearVelocity(responseBody2D);
 28620        Fixed64 angularVelocity2D =
 28621            responseBody2D == null
 28622                ? Fixed64.Zero
 28623                : ResolveAngularVelocity(responseBody2D);
 28624        if (!ExactContactResponseKernel.TryGetCoulombDiskResponse(
 28625                normalConstraint,
 28626                ExactContactLever3D.CreateResponseOperand(
 28627                    body3D,
 28628                    linearVelocity3D,
 28629                    angularVelocity3D,
 28630                    exact3D,
 28631                    -tangent),
 28632                ExactContactLever2D.CreateResponseOperand(
 28633                    responseBody2D,
 28634                    linearVelocity2D,
 28635                    angularVelocity2D,
 28636                    exact2D,
 28637                    tangent),
 28638                tangent,
 28639                Fixed64.Zero,
 28640                ExactContactLever3D.CreateResponseOperand(
 28641                    body3D,
 28642                    linearVelocity3D,
 28643                    angularVelocity3D,
 28644                    exact3D,
 28645                    -secondaryTangent),
 28646                ExactContactLever2D.CreateResponseOperand(
 28647                    responseBody2D,
 28648                    linearVelocity2D,
 28649                    angularVelocity2D,
 28650                    exact2D,
 28651                    secondaryTangent),
 28652                secondaryTangent,
 28653                Fixed64.Zero,
 28654                staticFriction,
 28655                dynamicFriction,
 28656                out ExactCoulombResponse3D response))
 657        {
 2658            return false;
 659        }
 660
 26661        Vector3d linear3D =
 26662            response.FirstLinearVelocityDelta;
 26663        Vector3d angular3D =
 26664            response.FirstAngularVelocityDelta;
 26665        Vector2d linear2D = ExactContactLever2D.ToPlanar(
 26666            response.SecondLinearVelocityDelta);
 26667        Fixed64 angular2D = ExactContactLever2D.ToPlanarAngular(
 26668            response.SecondAngularVelocityDelta);
 26669        if (!CanApplyVelocityDeltas(
 26670                body3D,
 26671                body2D,
 26672                linear3D,
 26673                angular3D,
 26674                linear2D,
 26675                angular2D))
 676        {
 1677            return false;
 678        }
 679
 25680        if (!response.HasAppliedImpulse)
 1681            return true;
 682
 24683        ApplyVelocityDeltas(
 24684            body3D,
 24685            body2D,
 24686            linear3D,
 24687            angular3D,
 24688            linear2D,
 24689            angular2D);
 24690        applied = true;
 24691        return true;
 692    }
 693
 694    private static bool TryApplyImpulse(
 695        SolidBody? body3D,
 696        SolidBody2D? body2D,
 697        Vector3d axis,
 698        Vector3d relative3D,
 699        Vector2d relative2D,
 700        Fixed64 impulseScalar,
 701        bool applyTo2D)
 702    {
 19703        Vector3d linear3D = Vector3d.Zero;
 19704        Vector3d angular3D = Vector3d.Zero;
 19705        Vector2d linear2D = Vector2d.Zero;
 19706        Fixed64 angular2D = Fixed64.Zero;
 19707        Vector3d impulse3D = -axis * impulseScalar;
 19708        bool impulse3DResolved =
 19709            (impulse3D.X != Fixed64.Zero
 19710                || axis.X == Fixed64.Zero)
 19711            && (impulse3D.Y != Fixed64.Zero
 19712                || axis.Y == Fixed64.Zero)
 19713            && (impulse3D.Z != Fixed64.Zero
 19714                || axis.Z == Fixed64.Zero);
 19715        bool linear3DResolved = impulse3DResolved
 19716            && ContactNormalImpulse3D.TryComputeLinearVelocityDelta(
 19717                body3D,
 19718                impulse3D,
 19719                out linear3D);
 19720        bool angular3DResolved = impulse3DResolved
 19721            && ContactNormalImpulse3D.TryComputeAngularVelocityDelta(
 19722                body3D,
 19723                relative3D,
 19724                impulse3D,
 19725                out angular3D);
 19726        Vector2d planarAxis = axis.ToVector2d();
 19727        bool linear2DResolved = !applyTo2D
 19728            || ContactNormalImpulse2D.TryComputeLinearVelocityDelta(
 19729                body2D,
 19730                planarAxis,
 19731                impulseScalar,
 19732                out linear2D);
 19733        bool angular2DResolved = !applyTo2D
 19734            || ContactNormalImpulse2D.TryComputeAngularVelocityDelta(
 19735                body2D,
 19736                relative2D,
 19737                planarAxis,
 19738                impulseScalar,
 19739                out angular2D);
 19740        if (!(linear3DResolved
 19741            & angular3DResolved
 19742            & linear2DResolved
 19743            & angular2DResolved)
 19744            || !CanApplyVelocityDeltas(
 19745                body3D,
 19746                body2D,
 19747                linear3D,
 19748                angular3D,
 19749                linear2D,
 19750                angular2D))
 751        {
 2752            return false;
 753        }
 754
 17755        ApplyVelocityDeltas(
 17756            body3D,
 17757            body2D,
 17758            linear3D,
 17759            angular3D,
 17760            linear2D,
 17761            angular2D);
 17762        return true;
 763    }
 764
 765    private static bool TryGetCompactTangent(
 766        Vector3d relativeVelocity,
 767        Vector3d normal,
 768        out Vector3d tangent,
 769        out bool hasTangentialMotion)
 770    {
 85771        tangent = default;
 85772        hasTangentialMotion = false;
 85773        if (!ContactResponseArithmetic3D.TryDot(
 85774                relativeVelocity,
 85775                normal,
 85776                out Fixed64 normalVelocity)
 85777            || !ContactResponseArithmetic3D.TryLinearCombination(
 85778                relativeVelocity,
 85779                Fixed64.One,
 85780                normal,
 85781                -normalVelocity,
 85782                Vector3d.Zero,
 85783                Fixed64.Zero,
 85784                out Vector3d tangentVelocity))
 785        {
 1786            return false;
 787        }
 84788        if (tangentVelocity == Vector3d.Zero)
 56789            return true;
 790
 28791        Fixed64 magnitudeSquared =
 28792            tangentVelocity.MagnitudeSquared;
 28793        if (magnitudeSquared <= Fixed64.Epsilon)
 1794            return true;
 795
 27796        tangent = tangentVelocity.Normalized;
 27797        hasTangentialMotion = tangent.IsNormalized();
 27798        return hasTangentialMotion;
 799    }
 800
 801    private static bool TryGetCompactConstrainedInverseMass(
 802        SolidBody? body,
 803        Vector3d axis,
 804        out Fixed64 inverseMass)
 805    {
 27806        if (body == null)
 807        {
 1808            inverseMass = Fixed64.Zero;
 1809            return true;
 810        }
 811
 26812        inverseMass = body.GetConstrainedInverseMass(axis);
 26813        return inverseMass != Fixed64.Zero
 26814            || body.EffectiveInverseMass == Fixed64.Zero
 26815            || body.ProjectLinearMotion(axis) == Vector3d.Zero;
 816    }
 817
 818    private static bool TryGetCompactConstrainedPlanarInverseMass(
 819        SolidBody2D? body,
 820        Vector3d axis,
 821        out Fixed64 inverseMass)
 822    {
 21823        inverseMass = Fixed64.Zero;
 21824        if (body == null)
 1825            return true;
 826
 20827        Vector2d planarAxis = axis.ToVector2d();
 20828        if (!ContactNormalImpulse2D.TryGetConstrainedInverseMass(
 20829                body,
 20830                planarAxis,
 20831                out Fixed64 constrained))
 1832            return false;
 19833        if (constrained == Fixed64.Zero)
 9834            return true;
 835
 10836        Fixed64 magnitudeSquared =
 10837            planarAxis.MagnitudeSquared;
 10838        if (!Fixed64.TryMultiplyDivide(
 10839                constrained,
 10840                magnitudeSquared,
 10841                Fixed64.One,
 10842                out inverseMass))
 843        {
 1844            inverseMass = default;
 1845            return false;
 846        }
 847
 9848        return inverseMass != Fixed64.Zero;
 849    }
 850
 851    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 852    private static Vector3d ResolveLinearVelocity(SolidBody body) =>
 406853        body.ProjectLinearMotion(
 406854            body.IsKinematic
 406855                ? body.SampleContinuousCollisionLinearVelocity(Fixed64.One)
 406856                : body.LinearVelocity);
 857
 858    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 859    private static Vector2d ResolveLinearVelocity(SolidBody2D body) =>
 380860        body.ProjectLinearMotion(
 380861            body.IsKinematic
 380862                ? body.SampleContinuousCollisionLinearVelocity(Fixed64.One)
 380863                : body.LinearVelocity);
 864
 865    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 866    private static Vector3d ResolveAngularVelocity(SolidBody body) =>
 406867        body.ProjectAngularMotion(
 406868            body.IsKinematic
 406869                ? body.SampleContinuousCollisionAngularVelocity(Fixed64.One)
 406870                : body.AngularVelocity);
 871
 872    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 873    private static Fixed64 ResolveAngularVelocity(SolidBody2D body) =>
 380874        body.IsKinematic
 380875            ? body.SampleContinuousCollisionAngularVelocity(Fixed64.One)
 380876            : body.AngularVelocity;
 877
 878    private static Vector3d ResolveNormal(CollisionPairMixed pair, MixedContact contact)
 879    {
 446880        Vector3d fallback = ResolveFallbackDirection(pair, contact);
 446881        if (contact.Normal3DTo2D.MagnitudeSquared > Fixed64.Epsilon)
 882        {
 440883            Vector3d normal = contact.Normal3DTo2D.Normalized;
 440884            return fallback.MagnitudeSquared > Fixed64.Epsilon && Vector3d.Dot(normal, fallback) < Fixed64.Zero
 440885                ? -normal
 440886                : normal;
 887        }
 888
 6889        return fallback == Vector3d.Zero ? Vector3d.Up : fallback;
 890    }
 891
 892    private static Vector3d ResolveFallbackDirection(CollisionPairMixed pair, MixedContact contact)
 893    {
 446894        LSCollider2D collider2D = pair.Collider2D;
 446895        Vector3d embeddedCenter = new(
 446896            collider2D.Center.X,
 446897            collider2D.MixedSlabCenterY,
 446898            collider2D.Center.Y);
 446899        Vector3d centerDirection = embeddedCenter - pair.Collider3D.Center;
 446900        if (centerDirection.MagnitudeSquared > Fixed64.Epsilon)
 313901            return centerDirection.Normalized;
 902
 133903        if (contact.TryGetPoint2D(out Vector3d point2D)
 133904            && contact.TryGetPoint3D(out Vector3d point3D))
 905        {
 112906            Vector3d pointDirection = point2D - point3D;
 112907            if (pointDirection.MagnitudeSquared > Fixed64.Epsilon)
 3908                return pointDirection.Normalized;
 909        }
 910
 130911        return Vector3d.Zero;
 912    }
 913
 914    private static void GetExactLevers(
 915        CollisionPairMixed pair,
 916        MixedContact contact,
 917        SolidBody? body3D,
 918        SolidBody2D? body2D,
 919        out ExactLever3D exact3D,
 920        out ExactLever3D exact2D)
 921    {
 57922        ContactAnchor center3D = body3D?.GetCenterOfMassAnchor()
 57923            ?? ContactAnchor.FromWorldPoint(pair.Collider3D.Center);
 57924        exact3D = contact.Anchor3D.GetLeverFrom(center3D);
 57925        exact2D = body2D == null
 57926            ? contact.GetPlanarXZLeverFrom(
 57927                pair.Collider2D.Center,
 57928                Fixed64.Zero,
 57929                Vector2d.Zero)
 57930            : contact.GetPlanarXZLeverFrom(
 57931                body2D.Position,
 57932                body2D.Rotation,
 57933                body2D.LocalCenterOfMassOffset);
 57934    }
 935
 936    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 937    private static Fixed64 GetConstrainedInverseMass(SolidBody? body, Vector3d axis) =>
 430938        body?.GetConstrainedInverseMass(axis) ?? Fixed64.Zero;
 939
 940    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 941    private static Fixed64 GetConstrainedPlanarInverseMass(SolidBody2D? body, Vector3d axis)
 942    {
 430943        if (body == null)
 41944            return Fixed64.Zero;
 945
 389946        Vector2d planarAxis = axis.ToVector2d();
 389947        if (planarAxis == Vector2d.Zero)
 157948            return Fixed64.Zero;
 949
 232950        return body.GetConstrainedInverseMass(planarAxis) * planarAxis.MagnitudeSquared;
 951    }
 952
 953}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Response/Mixed/ContactNormalImpulseMixed.cs

#LineLine coverage
 1//=======================================================================
 2// ContactNormalImpulseMixed.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 System.Runtime.CompilerServices;
 11
 12namespace Gravitas.CollisionHandling;
 13
 14/// <summary>
 15/// Side-effect-free mixed 3D/2D contact-normal impulse result.
 16/// </summary>
 17internal readonly struct ContactNormalImpulseResultMixed
 18{
 19    public ContactNormalImpulseResultMixed(
 20        Fixed64 normalVelocity,
 21        Fixed64 impulseScalar,
 22        Vector3d linearVelocityDelta3D,
 23        Vector3d angularVelocityDelta3D,
 24        Vector2d linearVelocityDelta2D,
 25        Fixed64 angularVelocityDelta2D)
 27526        : this(
 27527            normalVelocity,
 27528            impulseScalar,
 27529            impulseScalar,
 27530            linearVelocityDelta3D,
 27531            angularVelocityDelta3D,
 27532            linearVelocityDelta2D,
 27533            angularVelocityDelta2D)
 34    {
 27535    }
 36
 37    public ContactNormalImpulseResultMixed(
 38        Fixed64 normalVelocity,
 39        Fixed64 impulseScalar,
 40        Fixed64 appliedImpulseScalar,
 41        Vector3d linearVelocityDelta3D,
 42        Vector3d angularVelocityDelta3D,
 43        Vector2d linearVelocityDelta2D,
 44        Fixed64 angularVelocityDelta2D,
 45        bool hasRepresentableNormalVelocity = true,
 46        bool hasRepresentableAppliedImpulse = true)
 47    {
 30448        NormalVelocity = normalVelocity;
 30449        ImpulseScalar = impulseScalar;
 30450        AppliedImpulseScalar = appliedImpulseScalar;
 30451        LinearVelocityDelta3D = linearVelocityDelta3D;
 30452        AngularVelocityDelta3D = angularVelocityDelta3D;
 30453        LinearVelocityDelta2D = linearVelocityDelta2D;
 30454        AngularVelocityDelta2D = angularVelocityDelta2D;
 30455        HasRepresentableNormalVelocity = hasRepresentableNormalVelocity;
 30456        HasRepresentableAppliedImpulse = hasRepresentableAppliedImpulse;
 30457    }
 58
 59    public Fixed64 NormalVelocity { get; }
 60
 61    public Fixed64 ImpulseScalar { get; }
 62
 63    public Fixed64 AppliedImpulseScalar { get; }
 64
 65    public bool HasRepresentableNormalVelocity { get; }
 66
 67    public bool HasRepresentableAppliedImpulse { get; }
 68
 69    public Vector3d LinearVelocityDelta3D { get; }
 70
 71    public Vector3d AngularVelocityDelta3D { get; }
 72
 73    public Vector2d LinearVelocityDelta2D { get; }
 74
 75    public Fixed64 AngularVelocityDelta2D { get; }
 76}
 77
 78/// <summary>
 79/// Side-effect-free mixed velocity deltas for a normal response whose impulse
 80/// scalar does not need to be representable.
 81/// </summary>
 82internal readonly struct ContactNormalVelocityDeltaResultMixed
 83{
 84    public ContactNormalVelocityDeltaResultMixed(
 85        Fixed64 normalVelocity,
 86        Vector3d linearVelocityDelta3D,
 87        Vector3d angularVelocityDelta3D,
 88        Vector2d linearVelocityDelta2D,
 89        Fixed64 angularVelocityDelta2D)
 3190        : this(
 3191            normalVelocity,
 3192            linearVelocityDelta3D,
 3193            angularVelocityDelta3D,
 3194            linearVelocityDelta2D,
 3195            angularVelocityDelta2D,
 3196            normalVelocity < Fixed64.Zero,
 3197            hasRepresentableNormalVelocity: true)
 98    {
 3199    }
 100
 101    public ContactNormalVelocityDeltaResultMixed(
 102        Fixed64 normalVelocity,
 103        Vector3d linearVelocityDelta3D,
 104        Vector3d angularVelocityDelta3D,
 105        Vector2d linearVelocityDelta2D,
 106        Fixed64 angularVelocityDelta2D,
 107        bool isClosing,
 108        bool hasRepresentableNormalVelocity)
 109    {
 121110        NormalVelocity = normalVelocity;
 121111        LinearVelocityDelta3D = linearVelocityDelta3D;
 121112        AngularVelocityDelta3D = angularVelocityDelta3D;
 121113        LinearVelocityDelta2D = linearVelocityDelta2D;
 121114        AngularVelocityDelta2D = angularVelocityDelta2D;
 121115        IsClosing = isClosing;
 121116        HasRepresentableNormalVelocity = hasRepresentableNormalVelocity;
 121117    }
 118
 119    public Fixed64 NormalVelocity { get; }
 120
 121    public bool IsClosing { get; }
 122
 123    public bool HasRepresentableNormalVelocity { get; }
 124
 125    public Vector3d LinearVelocityDelta3D { get; }
 126
 127    public Vector3d AngularVelocityDelta3D { get; }
 128
 129    public Vector2d LinearVelocityDelta2D { get; }
 130
 131    public Fixed64 AngularVelocityDelta2D { get; }
 132}
 133
 134/// <summary>
 135/// Calculates allocation-free mixed 3D/2D contact-point normal response
 136/// without mutating either participant.
 137/// </summary>
 138internal static class ContactNormalImpulseMixed
 139{
 140    internal static bool CanUseCompactResponse(
 141        SolidBody? body3D,
 142        Vector3d linearVelocity3D,
 143        Vector3d angularVelocity3D,
 144        Vector3d relativeContactPoint3D,
 145        SolidBody2D? body2D,
 146        Vector2d linearVelocity2D,
 147        Fixed64 angularVelocity2D,
 148        Vector2d relativeContactPoint2D,
 149        Vector3d axis)
 150    {
 384151        Vector3d planarLever =
 384152            ExactContactLever2D.ToSpatial(relativeContactPoint2D);
 384153        return ContactResponseArithmetic3D.CanUseFastPointVelocity(
 384154                linearVelocity3D,
 384155                angularVelocity3D,
 384156                relativeContactPoint3D,
 384157                ExactContactLever2D.ToSpatial(linearVelocity2D),
 384158                new Vector3d(
 384159                    Fixed64.Zero,
 384160                    -angularVelocity2D,
 384161                    Fixed64.Zero),
 384162                planarLever,
 384163                axis)
 384164            && ContactResponseArithmetic3D.CanUseFastAngularResponse(
 384165                relativeContactPoint3D,
 384166                axis,
 384167                body3D?.GetConstrainedInverseInertiaTensor()
 384168                    ?? Fixed3x3.Zero)
 384169            && ContactResponseArithmetic3D.CanUseFastAngularResponse(
 384170                planarLever,
 384171                axis,
 384172                ExactContactLever2D.CreateInverseInertia(body2D));
 173    }
 174
 175    internal static bool TryCalculateVelocityDeltas(
 176        SolidBody? body3D,
 177        Vector3d linearVelocity3D,
 178        Vector3d angularVelocity3D,
 179        Vector3d relativeContactPoint3D,
 180        SolidBody2D? body2D,
 181        Vector2d linearVelocity2D,
 182        Fixed64 angularVelocity2D,
 183        Vector2d relativeContactPoint2D,
 184        Vector3d normal,
 185        Fixed64 restitution,
 186        Fixed64 restitutionVelocityThreshold,
 187        out ContactNormalVelocityDeltaResultMixed result)
 188    {
 37189        result = default;
 37190        if (!TryComputeNormalVelocity(
 37191                linearVelocity3D,
 37192                angularVelocity3D,
 37193                relativeContactPoint3D,
 37194                linearVelocity2D,
 37195                angularVelocity2D,
 37196                relativeContactPoint2D,
 37197                normal,
 37198                out Fixed64 normalVelocity))
 199        {
 1200            return false;
 201        }
 36202        if (normalVelocity >= Fixed64.Zero)
 203        {
 1204            result = ZeroVelocityDelta(normalVelocity);
 1205            return true;
 206        }
 207
 35208        if (!TryComputeDenominator(
 35209                body3D,
 35210                relativeContactPoint3D,
 35211                body2D,
 35212                relativeContactPoint2D,
 35213                normal,
 35214                out Fixed64 denominator))
 215        {
 1216            return false;
 217        }
 34218        if (denominator <= Fixed64.Zero)
 3219            return false;
 220
 31221        Fixed64 appliedRestitution = normalVelocity < -restitutionVelocityThreshold
 31222            ? restitution
 31223            : Fixed64.Zero;
 31224        Fixed64 responseFactor = -(Fixed64.One + appliedRestitution);
 31225        Vector2d planarNormal = normal.ToVector2d();
 31226        bool linear3DResolved = ContinuousCollisionImpulsePolicy.TryResolveVelocityDelta(
 31227                body3D?.ProjectLinearMotion(-normal) ?? Vector3d.Zero,
 31228                normalVelocity,
 31229                responseFactor,
 31230                body3D?.EffectiveInverseMass ?? Fixed64.Zero,
 31231                denominator,
 31232                out Vector3d linearVelocityDelta3D);
 31233        bool angular3DResolved = TryResolveAngularVelocityDelta3D(
 31234                body3D,
 31235                relativeContactPoint3D,
 31236                -normal,
 31237                normalVelocity,
 31238                responseFactor,
 31239                denominator,
 31240                out Vector3d angularVelocityDelta3D);
 31241        bool linear2DResolved = ContinuousCollisionImpulsePolicy.TryResolveVelocityDelta(
 31242                body2D?.ProjectLinearMotion(planarNormal) ?? Vector2d.Zero,
 31243                normalVelocity,
 31244                responseFactor,
 31245                body2D?.EffectiveInverseMass ?? Fixed64.Zero,
 31246                denominator,
 31247                out Vector2d linearVelocityDelta2D);
 31248        bool angular2DResolved = TryResolveAngularVelocityDelta2D(
 31249                body2D,
 31250                relativeContactPoint2D,
 31251                planarNormal,
 31252                normalVelocity,
 31253                responseFactor,
 31254                denominator,
 31255                out Fixed64 angularVelocityDelta2D);
 31256        if (!(linear3DResolved
 31257                & angular3DResolved
 31258                & linear2DResolved
 31259                & angular2DResolved))
 260        {
 1261            return false;
 262        }
 263
 30264        result = new ContactNormalVelocityDeltaResultMixed(
 30265            normalVelocity,
 30266            linearVelocityDelta3D,
 30267            angularVelocityDelta3D,
 30268            linearVelocityDelta2D,
 30269            angularVelocityDelta2D);
 30270        return true;
 271    }
 272
 273    internal static bool TryCalculateAccumulatedDelta(
 274        SolidBody? body3D,
 275        Vector3d linearVelocity3D,
 276        Vector3d angularVelocity3D,
 277        Vector3d relativeContactPoint3D,
 278        SolidBody2D? body2D,
 279        Vector2d linearVelocity2D,
 280        Fixed64 angularVelocity2D,
 281        Vector2d relativeContactPoint2D,
 282        Vector3d normal,
 283        Fixed64 restitution,
 284        Fixed64 restitutionVelocityThreshold,
 285        Fixed64 accumulatedImpulse,
 286        Fixed64 positiveImpulseScale,
 287        Fixed64 negativeImpulseScale,
 288        out ContactNormalImpulseResultMixed result)
 289    {
 285290        result = default;
 285291        bool inputsValid =
 285292            accumulatedImpulse >= Fixed64.Zero
 285293            & positiveImpulseScale >= Fixed64.Zero
 285294            & negativeImpulseScale >= Fixed64.Zero;
 285295        if (!inputsValid)
 3296            return false;
 282297        if (!TryComputeNormalVelocity(
 282298                linearVelocity3D,
 282299                angularVelocity3D,
 282300                relativeContactPoint3D,
 282301                linearVelocity2D,
 282302                angularVelocity2D,
 282303                relativeContactPoint2D,
 282304                normal,
 282305                out Fixed64 normalVelocity)
 282306            || !TryComputeDenominator(
 282307                body3D,
 282308                relativeContactPoint3D,
 282309                body2D,
 282310                relativeContactPoint2D,
 282311                normal,
 282312                out Fixed64 denominator))
 313        {
 3314            return false;
 315        }
 279316        if (denominator <= Fixed64.Zero)
 317        {
 3318            result = ZeroImpulse(normalVelocity);
 3319            return true;
 320        }
 321
 276322        Fixed64 appliedRestitution = normalVelocity < -restitutionVelocityThreshold
 276323            ? restitution
 276324            : Fixed64.Zero;
 276325        Fixed64 responseFactor = -(Fixed64.One + appliedRestitution);
 276326        Fixed64 impulseScale = normalVelocity < Fixed64.Zero
 276327            ? positiveImpulseScale
 276328            : negativeImpulseScale;
 329        Fixed64 impulseScalar;
 276330        if (!Fixed64.TryMultiplyDivide(
 276331                normalVelocity,
 276332                responseFactor,
 276333                impulseScale,
 276334                denominator,
 276335                out Fixed64 scaledImpulse))
 336        {
 4337            if (normalVelocity < Fixed64.Zero)
 2338                return false;
 2339            impulseScalar = -accumulatedImpulse;
 340        }
 272341        else if (!Fixed64.TryAdd(
 272342                    accumulatedImpulse,
 272343                    scaledImpulse,
 272344                    out Fixed64 accumulated)
 272345                || !Fixed64.TrySubtract(
 272346                    FixedMath.Max(Fixed64.Zero, accumulated),
 272347                    accumulatedImpulse,
 272348                    out impulseScalar))
 349        {
 1350            return false;
 351        }
 273352        if (impulseScalar == Fixed64.Zero)
 353        {
 183354            result = ZeroImpulse(normalVelocity);
 183355            return true;
 356        }
 357
 90358        Vector2d planarNormal = normal.ToVector2d();
 90359        bool impulse3DResolved = ContactResponseArithmetic3D.TryScale(
 90360            -normal,
 90361            impulseScalar,
 90362            out Vector3d impulse3D);
 90363        bool linear3DResolved =
 90364            ContactNormalImpulse3D.TryComputeLinearVelocityDelta(
 90365                body3D,
 90366                impulse3D,
 90367                out Vector3d linearVelocityDelta3D);
 90368        bool angular3DResolved =
 90369            ContactNormalImpulse3D.TryComputeAngularVelocityDelta(
 90370                body3D,
 90371                relativeContactPoint3D,
 90372                impulse3D,
 90373                out Vector3d angularVelocityDelta3D);
 90374        bool linear2DResolved =
 90375            ContactNormalImpulse2D.TryComputeLinearVelocityDelta(
 90376                body2D,
 90377                planarNormal,
 90378                impulseScalar,
 90379                out Vector2d linearVelocityDelta2D);
 90380        bool angular2DResolved =
 90381            ContactNormalImpulse2D.TryComputeAngularVelocityDelta(
 90382                body2D,
 90383                relativeContactPoint2D,
 90384                planarNormal,
 90385                impulseScalar,
 90386                out Fixed64 angularVelocityDelta2D);
 90387        if (!(impulse3DResolved
 90388            & linear3DResolved
 90389            & angular3DResolved
 90390            & linear2DResolved
 90391            & angular2DResolved))
 392        {
 1393            return false;
 394        }
 395
 89396        result = new ContactNormalImpulseResultMixed(
 89397            normalVelocity,
 89398            impulseScalar,
 89399            linearVelocityDelta3D,
 89400            angularVelocityDelta3D,
 89401            linearVelocityDelta2D,
 89402            angularVelocityDelta2D);
 89403        return true;
 404    }
 405
 406    internal static bool TryCalculateVelocityDeltasExact(
 407        SolidBody? body3D,
 408        Vector3d linearVelocity3D,
 409        Vector3d angularVelocity3D,
 410        in ExactLever3D relativeContactPoint3D,
 411        SolidBody2D? body2D,
 412        Vector2d linearVelocity2D,
 413        Fixed64 angularVelocity2D,
 414        in ExactLever3D relativeContactPoint2D,
 415        Vector3d normal,
 416        Fixed64 restitution,
 417        Fixed64 restitutionVelocityThreshold,
 418        out ContactNormalVelocityDeltaResultMixed result)
 419    {
 92420        result = default;
 92421        ExactContactResponseOperand3D first =
 92422            ExactContactLever3D.CreateResponseOperand(
 92423                body3D,
 92424                linearVelocity3D,
 92425                angularVelocity3D,
 92426                relativeContactPoint3D,
 92427                -normal);
 92428        ExactContactResponseOperand3D second =
 92429            ExactContactLever2D.CreateResponseOperand(
 92430                body2D,
 92431                linearVelocity2D,
 92432                angularVelocity2D,
 92433                relativeContactPoint2D,
 92434                normal);
 92435        if (!ExactContactResponseKernel.TryGetNormalResponse(
 92436                first,
 92437                second,
 92438                normal,
 92439                restitution,
 92440                restitutionVelocityThreshold,
 92441                out ExactNormalResponse3D response))
 442        {
 2443            return false;
 444        }
 445
 90446        bool hasNormalVelocity =
 90447            response.TryGetNormalVelocity(out Fixed64 normalVelocity);
 90448        result = new ContactNormalVelocityDeltaResultMixed(
 90449            normalVelocity,
 90450            response.FirstLinearVelocityDelta,
 90451            response.FirstAngularVelocityDelta,
 90452            ExactContactLever2D.ToPlanar(response.SecondLinearVelocityDelta),
 90453            ExactContactLever2D.ToPlanarAngular(response.SecondAngularVelocityDelta),
 90454            response.IsClosing,
 90455            hasNormalVelocity);
 90456        return true;
 457    }
 458
 459    internal static bool TryCalculateAccumulatedDeltaExact(
 460        SolidBody? body3D,
 461        Vector3d linearVelocity3D,
 462        Vector3d angularVelocity3D,
 463        in ExactLever3D relativeContactPoint3D,
 464        SolidBody2D? body2D,
 465        Vector2d linearVelocity2D,
 466        Fixed64 angularVelocity2D,
 467        in ExactLever3D relativeContactPoint2D,
 468        Vector3d normal,
 469        Fixed64 restitution,
 470        Fixed64 restitutionVelocityThreshold,
 471        Fixed64 accumulatedImpulse,
 472        Fixed64 positiveImpulseScale,
 473        Fixed64 negativeImpulseScale,
 474        out ContactNormalImpulseResultMixed result)
 475    {
 32476        result = default;
 32477        ExactContactResponseOperand3D first =
 32478            ExactContactLever3D.CreateResponseOperand(
 32479                body3D,
 32480                linearVelocity3D,
 32481                angularVelocity3D,
 32482                relativeContactPoint3D,
 32483                -normal);
 32484        ExactContactResponseOperand3D second =
 32485            ExactContactLever2D.CreateResponseOperand(
 32486                body2D,
 32487                linearVelocity2D,
 32488                angularVelocity2D,
 32489                relativeContactPoint2D,
 32490                normal);
 32491        if (!ExactContactResponseKernel.TryGetAccumulatedNormalResponse(
 32492                first,
 32493                second,
 32494                normal,
 32495                restitution,
 32496                restitutionVelocityThreshold,
 32497                accumulatedImpulse,
 32498                positiveImpulseScale,
 32499                negativeImpulseScale,
 32500                out ExactNormalResponse3D response))
 501        {
 3502            return false;
 503        }
 504
 29505        bool hasNormalVelocity =
 29506            response.TryGetNormalVelocity(out Fixed64 normalVelocity);
 29507        bool hasAppliedImpulse =
 29508            response.TryGetAppliedImpulse(out Fixed64 appliedImpulse);
 29509        Fixed64 impulseScalar = response.TryGetAccumulatedImpulse(
 29510                out Fixed64 newAccumulatedImpulse)
 29511            ? newAccumulatedImpulse - accumulatedImpulse
 29512            : -accumulatedImpulse;
 29513        result = new ContactNormalImpulseResultMixed(
 29514            normalVelocity,
 29515            impulseScalar,
 29516            appliedImpulse,
 29517            response.FirstLinearVelocityDelta,
 29518            response.FirstAngularVelocityDelta,
 29519            ExactContactLever2D.ToPlanar(response.SecondLinearVelocityDelta),
 29520            ExactContactLever2D.ToPlanarAngular(response.SecondAngularVelocityDelta),
 29521            hasNormalVelocity,
 29522            hasAppliedImpulse);
 29523        return true;
 524    }
 525
 526    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 527    private static bool TryComputeNormalVelocity(
 528        Vector3d linearVelocity3D,
 529        Vector3d angularVelocity3D,
 530        Vector3d relativeContactPoint3D,
 531        Vector2d linearVelocity2D,
 532        Fixed64 angularVelocity2D,
 533        Vector2d relativeContactPoint2D,
 534        Vector3d normal,
 535        out Fixed64 normalVelocity) =>
 319536        ContactNormalImpulse3D.TryComputeNormalVelocity(
 319537            linearVelocity3D,
 319538            angularVelocity3D,
 319539            relativeContactPoint3D,
 319540            ExactContactLever2D.ToSpatial(linearVelocity2D),
 319541            new Vector3d(Fixed64.Zero, -angularVelocity2D, Fixed64.Zero),
 319542            ExactContactLever2D.ToSpatial(relativeContactPoint2D),
 319543            normal,
 319544            out normalVelocity);
 545
 546    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 547    private static bool TryComputeDenominator(
 548        SolidBody? body3D,
 549        Vector3d relativeContactPoint3D,
 550        SolidBody2D? body2D,
 551        Vector2d relativeContactPoint2D,
 552        Vector3d normal,
 553        out Fixed64 denominator)
 554    {
 316555        Vector2d planarNormal = normal.ToVector2d();
 316556        bool angular3DResolved =
 316557            ContactNormalImpulse3D.TryComputeAngularDenominator(
 316558                body3D,
 316559                relativeContactPoint3D,
 316560                normal,
 316561                out Fixed64 angular3D);
 316562        bool angular2DResolved =
 316563            ContactNormalImpulse2D.TryComputeAngularDenominator(
 316564                body2D,
 316565                relativeContactPoint2D,
 316566                planarNormal,
 316567                out Fixed64 angular2D);
 316568        bool sumResolved = Fixed64.TryAdd(
 316569                GetConstrainedInverseMass3D(body3D, normal),
 316570                GetConstrainedInverseMass2D(body2D, normal),
 316571                out Fixed64 linear)
 316572            & Fixed64.TryAdd(linear, angular3D, out Fixed64 first)
 316573            & Fixed64.TryAdd(first, angular2D, out denominator);
 316574        if (!(angular3DResolved & angular2DResolved & sumResolved))
 575        {
 3576            denominator = default;
 3577            return false;
 578        }
 579
 313580        return true;
 581    }
 582
 583    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 584    private static Fixed64 GetConstrainedInverseMass3D(SolidBody? body, Vector3d axis) =>
 316585        body?.GetConstrainedInverseMass(axis) ?? Fixed64.Zero;
 586
 587    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 588    private static Fixed64 GetConstrainedInverseMass2D(SolidBody2D? body, Vector3d axis)
 589    {
 316590        if (body == null)
 41591            return Fixed64.Zero;
 592
 275593        Vector2d planarAxis = axis.ToVector2d();
 275594        return planarAxis == Vector2d.Zero
 275595            ? Fixed64.Zero
 275596            : body.GetConstrainedInverseMass(planarAxis) * planarAxis.MagnitudeSquared;
 597    }
 598
 599    private static bool TryResolveAngularVelocityDelta3D(
 600        SolidBody? body,
 601        Vector3d relativeContactPoint,
 602        Vector3d signedNormal,
 603        Fixed64 normalVelocity,
 604        Fixed64 responseFactor,
 605        Fixed64 denominator,
 606        out Vector3d velocityDelta)
 607    {
 31608        velocityDelta = Vector3d.Zero;
 31609        if (body?.CanRotate != true)
 8610            return true;
 611
 23612        Vector3d response = body.ApplyConstrainedInverseInertia(
 23613            Vector3d.Cross(relativeContactPoint, signedNormal));
 23614        bool xResolved = Fixed64.TryMultiplyDivide(
 23615            response.X,
 23616            normalVelocity,
 23617            responseFactor,
 23618            denominator,
 23619            out Fixed64 x);
 23620        bool yResolved = Fixed64.TryMultiplyDivide(
 23621            response.Y,
 23622            normalVelocity,
 23623            responseFactor,
 23624            denominator,
 23625            out Fixed64 y);
 23626        bool zResolved = Fixed64.TryMultiplyDivide(
 23627            response.Z,
 23628            normalVelocity,
 23629            responseFactor,
 23630            denominator,
 23631            out Fixed64 z);
 23632        velocityDelta = new Vector3d(x, y, z);
 23633        return xResolved & yResolved & zResolved;
 634    }
 635
 636    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 637    private static bool TryResolveAngularVelocityDelta2D(
 638        SolidBody2D? body,
 639        Vector2d relativeContactPoint,
 640        Vector2d signedNormal,
 641        Fixed64 normalVelocity,
 642        Fixed64 responseFactor,
 643        Fixed64 denominator,
 644        out Fixed64 velocityDelta)
 645    {
 31646        velocityDelta = Fixed64.Zero;
 31647        if (body?.CanRotate != true)
 9648            return true;
 649
 22650        Fixed64 torqueScale = Vector2d.CrossProduct(relativeContactPoint, signedNormal);
 22651        if (torqueScale == Fixed64.Zero)
 14652            return true;
 653
 8654        bool angularScaleResolved = Fixed64.TryMultiplyDivide(
 8655            normalVelocity,
 8656            responseFactor,
 8657            body.EffectiveInverseMomentOfInertia,
 8658            denominator,
 8659            out Fixed64 angularScale);
 8660        bool velocityDeltaResolved = Fixed64.TryMultiplyDivide(
 8661            torqueScale,
 8662            angularScale,
 8663            Fixed64.One,
 8664            out velocityDelta);
 8665        return angularScaleResolved
 8666            & (angularScale != Fixed64.Zero | torqueScale.Abs() <= Fixed64.One)
 8667            & velocityDeltaResolved;
 668    }
 669
 670    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 671    private static ContactNormalImpulseResultMixed ZeroImpulse(Fixed64 normalVelocity) =>
 186672        new(
 186673            normalVelocity,
 186674            Fixed64.Zero,
 186675            Vector3d.Zero,
 186676            Vector3d.Zero,
 186677            Vector2d.Zero,
 186678            Fixed64.Zero);
 679
 680    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 681    private static ContactNormalVelocityDeltaResultMixed ZeroVelocityDelta(Fixed64 normalVelocity) =>
 1682        new(
 1683            normalVelocity,
 1684            Vector3d.Zero,
 1685            Vector3d.Zero,
 1686            Vector2d.Zero,
 1687            Fixed64.Zero);
 688}

Methods/Properties

AllowsPhysicalPair(Gravitas.Colliders.LSCollider,Gravitas.Colliders.LSCollider)
AllowsPhysicalPair(Gravitas.Colliders.LSCollider2D,Gravitas.Colliders.LSCollider2D)
AllowsPhysicalPair(Gravitas.Colliders.LSCollider,Gravitas.Colliders.LSCollider2D)
.ctor(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,System.Boolean)
.ctor(Gravitas.CollisionHandling.ContactAnchor2D,Gravitas.CollisionHandling.ContactAnchor2D,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,System.Boolean)
get_PointA()
get_PointB()
GetRequiredWorldPoint(Gravitas.CollisionHandling.ContactAnchor2D,System.String)
.ctor(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d)
.ctor(FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d)
.ctor(FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d)
.ctor(FixedMathSharp.Geometry.FixedPointAnchor2d)
get_Origin()
get_LocalPoint()
get_LocalDisplacement()
CompareLocalFeature(Gravitas.CollisionHandling.ContactAnchor2D&)
GetLocalFeatureHash64()
get_Rotation()
get_Offset()
FromWorldPoint(FixedMathSharp.Vector2d)
TryGetOffset(FixedMathSharp.Vector2d&)
TryGetWorldPoint(FixedMathSharp.Vector2d&)
TryGetOffsetFrom(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d&)
TryGetOffsetFrom(Gravitas.CollisionHandling.ContactAnchor2D,FixedMathSharp.Vector2d&)
GetXZLeverFrom(Gravitas.CollisionHandling.ContactAnchor2D&)
TryRebase(FixedMathSharp.Vector2d,Gravitas.CollisionHandling.ContactAnchor2D&)
TryRebase(FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ContactAnchor2D&)
.ctor()
get_Count()
get_HasContact()
get_LastUpdatedFrame()
get_PrimaryContact()
get_Item(System.Int32)
BeginUpdate(System.Int32)
Reset()
SetContact(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d)
SetContact(Gravitas.CollisionHandling.ContactAnchor2D,Gravitas.CollisionHandling.ContactAnchor2D,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,System.Boolean)
AddContact(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d)
AddContact(Gravitas.CollisionHandling.ContactAnchor2D,Gravitas.CollisionHandling.ContactAnchor2D,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,System.Boolean)
AddContact(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,Gravitas.Materials.PhysicsMaterial,Gravitas.Materials.PhysicsMaterial,System.Boolean)
AddContact(Gravitas.CollisionHandling.ContactAnchor2D,Gravitas.CollisionHandling.ContactAnchor2D,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,Gravitas.Materials.PhysicsMaterial,Gravitas.Materials.PhysicsMaterial,System.Boolean,System.Int32,System.Int32)
AddContactCore(Gravitas.CollisionHandling.ContactAnchor2D,Gravitas.CollisionHandling.ContactAnchor2D,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,System.Boolean,Gravitas.Materials.PhysicsMaterial,Gravitas.Materials.PhysicsMaterial,System.Boolean,System.Int32,System.Int32)
GetEnumerator()
System.Collections.Generic.IEnumerable<Gravitas.CollisionHandling.ManifoldContact2D>.GetEnumerator()
System.Collections.IEnumerable.GetEnumerator()
FindShallowestReplacementIndex(Gravitas.CollisionHandling.ManifoldContact2D)
IsDeeper(Gravitas.CollisionHandling.ManifoldContact2D,Gravitas.CollisionHandling.ManifoldContact2D)
HasEqualDepth(Gravitas.CollisionHandling.ManifoldContact2D,Gravitas.CollisionHandling.ManifoldContact2D)
SortContactsById()
GetContactUnchecked(System.Int32)
SetContactUnchecked(System.Int32,Gravitas.CollisionHandling.ManifoldContact2D)
CreateContactId(Gravitas.CollisionHandling.ContactAnchor2D,System.Int32,Gravitas.CollisionHandling.ContactAnchor2D,System.Int32)
CompareLocalFeature(System.Int32,Gravitas.CollisionHandling.ContactAnchor2D,System.Int32,Gravitas.CollisionHandling.ContactAnchor2D)
Mix(System.UInt64&,System.Int64)
.ctor(Gravitas.CollisionHandling.ContactManifold2D)
get_Current()
System.Collections.IEnumerator.get_Current()
MoveNext()
Reset()
Dispose()
Clear()
Set(System.UInt64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
TryGet(System.UInt64,Gravitas.CollisionHandling.ContactWarmStartImpulse&)
Remove(System.UInt64)
GetContactIdForReplayHash(System.Int32)
GetImpulseForReplayHash(System.Int32)
GetContactId(System.Int32)
GetImpulseUnchecked(System.Int32)
SetContactIdUnchecked(System.Int32,System.UInt64)
SetImpulseUnchecked(System.Int32,Gravitas.CollisionHandling.ContactWarmStartImpulse)
.ctor(System.UInt64,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,System.Boolean,Gravitas.Materials.PhysicsMaterial,Gravitas.Materials.PhysicsMaterial,System.Boolean)
.ctor(System.UInt64,Gravitas.CollisionHandling.ContactAnchor2D,Gravitas.CollisionHandling.ContactAnchor2D,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,System.Boolean,Gravitas.Materials.PhysicsMaterial,Gravitas.Materials.PhysicsMaterial,System.Boolean)
.ctor(System.UInt64,Gravitas.CollisionHandling.ContactAnchor2D,Gravitas.CollisionHandling.ContactAnchor2D,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,System.Boolean,Gravitas.Materials.PhysicsMaterial,Gravitas.Materials.PhysicsMaterial,System.Boolean,System.Int32,System.Int32)
get_PointA()
get_PointB()
TryGetPointA(FixedMathSharp.Vector2d&)
TryGetPointB(FixedMathSharp.Vector2d&)
GetRequiredWorldPoint(Gravitas.CollisionHandling.ContactAnchor2D,System.String)
.ctor(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
.ctor(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d)
.ctor(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
.ctor(FixedMathSharp.Geometry.FixedPointAnchor)
get_Origin()
get_Rotation()
get_IsValid()
get_LocalPoint()
get_LocalDisplacement()
CompareLocalFeature(Gravitas.CollisionHandling.ContactAnchor&)
GetLocalFeatureHash64()
get_Offset()
FromWorldPoint(FixedMathSharp.Vector3d)
TryGetWorldPoint(FixedMathSharp.Vector3d&)
TryGetOffset(FixedMathSharp.Vector3d&)
TryGetOffsetFrom(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&)
TryGetOffsetFrom(Gravitas.CollisionHandling.ContactAnchor&,FixedMathSharp.Vector3d&)
GetLeverFrom(Gravitas.CollisionHandling.ContactAnchor&)
TryRebase(FixedMathSharp.Vector3d,Gravitas.CollisionHandling.ContactAnchor&)
TryRebase(FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,Gravitas.CollisionHandling.ContactAnchor&)
.ctor()
get_Count()
get_HasContact()
get_LastUpdatedFrame()
get_PrimaryContact()
get_Item(System.Int32)
BeginUpdate(System.Int32)
Reset()
SetContact(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d)
SetContact(Gravitas.CollisionHandling.ContactAnchor,Gravitas.CollisionHandling.ContactAnchor,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,System.Boolean)
AddContact(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d)
AddContact(Gravitas.CollisionHandling.ContactAnchor,Gravitas.CollisionHandling.ContactAnchor,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,System.Boolean)
AddContact(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,Gravitas.Materials.PhysicsMaterial,Gravitas.Materials.PhysicsMaterial,System.Boolean)
AddContact(Gravitas.CollisionHandling.ContactAnchor,Gravitas.CollisionHandling.ContactAnchor,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,Gravitas.Materials.PhysicsMaterial,Gravitas.Materials.PhysicsMaterial,System.Boolean,System.Int32,System.Int32)
AddContactCore(Gravitas.CollisionHandling.ContactAnchor,Gravitas.CollisionHandling.ContactAnchor,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,System.Boolean,Gravitas.Materials.PhysicsMaterial,Gravitas.Materials.PhysicsMaterial,System.Boolean,System.Int32,System.Int32)
GetEnumerator()
System.Collections.Generic.IEnumerable<Gravitas.CollisionHandling.ManifoldContact>.GetEnumerator()
System.Collections.IEnumerable.GetEnumerator()
FindShallowestReplacementIndex(Gravitas.CollisionHandling.ManifoldContact)
IsDeeper(Gravitas.CollisionHandling.ManifoldContact,Gravitas.CollisionHandling.ManifoldContact)
HasEqualDepth(Gravitas.CollisionHandling.ManifoldContact,Gravitas.CollisionHandling.ManifoldContact)
SortContactsById()
GetContactUnchecked(System.Int32)
SetContactUnchecked(System.Int32,Gravitas.CollisionHandling.ManifoldContact)
CreateContactId(Gravitas.CollisionHandling.ContactAnchor,System.Int32,Gravitas.CollisionHandling.ContactAnchor,System.Int32)
CompareLocalFeature(System.Int32,Gravitas.CollisionHandling.ContactAnchor,System.Int32,Gravitas.CollisionHandling.ContactAnchor)
Mix(System.UInt64&,System.Int64)
MixLocalFeature(System.UInt64&,Gravitas.CollisionHandling.ContactAnchor)
.ctor(Gravitas.CollisionHandling.ContactManifold)
get_Current()
System.Collections.IEnumerator.get_Current()
MoveNext()
Reset()
Dispose()
Clear()
Set(System.UInt64,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
TryGet(System.UInt64,Gravitas.CollisionHandling.ContactWarmStartImpulse&)
Remove(System.UInt64)
GetContactIdForReplayHash(System.Int32)
GetImpulseForReplayHash(System.Int32)
GetContactId(System.Int32)
GetImpulseUnchecked(System.Int32)
SetContactIdUnchecked(System.Int32,System.UInt64)
SetImpulseUnchecked(System.Int32,Gravitas.CollisionHandling.ContactWarmStartImpulse)
.ctor(System.UInt64,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,System.Boolean,Gravitas.Materials.PhysicsMaterial,Gravitas.Materials.PhysicsMaterial,System.Boolean)
.ctor(System.UInt64,Gravitas.CollisionHandling.ContactAnchor,Gravitas.CollisionHandling.ContactAnchor,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,System.Boolean,Gravitas.Materials.PhysicsMaterial,Gravitas.Materials.PhysicsMaterial,System.Boolean)
.ctor(System.UInt64,Gravitas.CollisionHandling.ContactAnchor,Gravitas.CollisionHandling.ContactAnchor,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,System.Boolean,Gravitas.Materials.PhysicsMaterial,Gravitas.Materials.PhysicsMaterial,System.Boolean,System.Int32,System.Int32)
get_PointA()
get_PointB()
TryGetPointA(FixedMathSharp.Vector3d&)
TryGetPointB(FixedMathSharp.Vector3d&)
GetRequiredWorldPoint(Gravitas.CollisionHandling.ContactAnchor,System.String)
ShouldReplaceWithDeeper(Gravitas.CollisionHandling.Contact2D,System.Boolean,Gravitas.CollisionHandling.Contact2D)
ShouldReplaceWithShallower(Gravitas.CollisionHandling.MixedContact,System.Boolean,Gravitas.CollisionHandling.MixedContact)
.ctor(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,System.Boolean)
.ctor(Gravitas.CollisionHandling.ContactAnchor,Gravitas.CollisionHandling.ContactAnchor,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,System.Boolean)
.ctor(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,Gravitas.Materials.PhysicsMaterial,Gravitas.Materials.PhysicsMaterial,System.Boolean)
.ctor(Gravitas.CollisionHandling.ContactAnchor,Gravitas.CollisionHandling.ContactAnchor,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,Gravitas.Materials.PhysicsMaterial,Gravitas.Materials.PhysicsMaterial,System.Boolean)
.ctor(Gravitas.CollisionHandling.ContactAnchor,Gravitas.CollisionHandling.ContactAnchor,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,System.Boolean,System.Boolean,Gravitas.Materials.PhysicsMaterial,Gravitas.Materials.PhysicsMaterial)
get_Point3D()
get_Point2D()
TryGetPoint3D(FixedMathSharp.Vector3d&)
TryGetPoint2D(FixedMathSharp.Vector3d&)
TryGetPlanarOffset2DFrom(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d&)
TryGetPlanarOffset2DFrom(FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d&)
GetPlanarXZLeverFrom(FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d)
WithMaterialOverride(Gravitas.Materials.PhysicsMaterial,Gravitas.Materials.PhysicsMaterial)
WithFallbackMaterials(Gravitas.Materials.PhysicsMaterial,Gravitas.Materials.PhysicsMaterial)
CreatePlanarReference(FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d)
GetRequiredWorldPoint(Gravitas.CollisionHandling.ContactAnchor,System.String)
.ctor(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
.ctor(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
Is2DHitFirst(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
ShouldReplaceHit(Gravitas.Queries.Physics3DHit,FixedMathSharp.Fixed64,System.Boolean,System.Boolean,Gravitas.Queries.Physics3DHit,FixedMathSharp.Fixed64)
ShouldReplaceHit(Gravitas.Queries.Physics2DHit,FixedMathSharp.Fixed64,System.Boolean,System.Boolean,Gravitas.Queries.Physics2DHit,FixedMathSharp.Fixed64)
ShouldReplaceMixedHit(Gravitas.Queries.PhysicsMixedHit,FixedMathSharp.Fixed64,System.Boolean,System.Boolean,Gravitas.Queries.PhysicsMixedHit,FixedMathSharp.Fixed64)
IsIgnoredTarget(Gravitas.Colliders.LSCollider,Gravitas.Colliders.LSCollider)
IsIgnoredTarget(Gravitas.Colliders.LSCollider2D,Gravitas.Colliders.LSCollider2D)
TryResolveSweptSphereContact(Gravitas.Colliders.LSCollider,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.CollisionHandling.ContactAnchor&,FixedMathSharp.Vector3d&)
ResolveLegacyNormal(Gravitas.Colliders.LSCollider,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
TryResolveVelocityDelta(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d&)
TryResolveVelocityDelta(FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d&)
TryResolveVelocityDelta(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d&)
TryResolveVelocityDelta(FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d&)
TryResolveSourceNormal(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&)
TryResolveSourceNormal(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d&)
TryResolveImpactNormal(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&)
TryResolveImpactNormal(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d&)
.cctor()
.ctor(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Int32)
TryResolveRotationalSearchLimit(Gravitas.CollisionHandling.ContinuousCollisionMath/RotationalInterval,System.Int32,System.Boolean,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&,FixedMathSharp.Fixed64&,System.Boolean&)
ShouldContinueRotationalArbiter(FixedMathSharp.Vector2d,FixedMathSharp.Fixed64)
TryResolveRotationalIntervalMotionBound(FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
TryResolveRotationalIntervalMotionBound(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
AreBoundsSeparatedByMoreThan(FixedMathSharp.Geometry.FixedBoundArea,FixedMathSharp.Geometry.FixedBoundArea,FixedMathSharp.Fixed64)
AreBoundsSeparatedByMoreThan(FixedMathSharp.Geometry.FixedBoundBox,FixedMathSharp.Geometry.FixedBoundBox,FixedMathSharp.Fixed64)
TrySubtractClosestFeatureUncertainty(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
IsAxisGapGreaterThan(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
IsWithinProxyRadius(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
IsWithinProxyRadius(FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
ResolveContactPointOnTarget(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64)
ShouldReplaceContinuousCollisionHit(FixedMathSharp.Fixed64,System.Int32,System.Boolean,FixedMathSharp.Fixed64,System.Int32)
ClipTranslationalTrajectoryInterval(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&,FixedMathSharp.Fixed64&,FixedMathSharp.Fixed64&,FixedMathSharp.Fixed64&)
IsSupersededTranslationalBoundaryHit(System.Boolean,FixedMathSharp.Fixed64,System.Int32,System.Int32,FixedMathSharp.Fixed64)
TryNormalizeTranslationalClosingSpeed(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
TryGetRelativeSphereOverlapDistanceInterval(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&,FixedMathSharp.Fixed64&,FixedMathSharp.Vector3d&,FixedMathSharp.Fixed64&,FixedMathSharp.Vector3d&,FixedMathSharp.Fixed64&)
TryGetRelativeCircleOverlapDistanceInterval(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&,FixedMathSharp.Fixed64&,FixedMathSharp.Vector2d&,FixedMathSharp.Fixed64&,FixedMathSharp.Vector2d&,FixedMathSharp.Fixed64&)
HasUsableCombinedRadius(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
GetSweepEnd(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
GetSweepEnd(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d)
ResolveNormal(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
ResolveNormal(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d)
.ctor(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
get_AngularDistance()
SamplePosition(FixedMathSharp.Fixed64)
SampleRotation(FixedMathSharp.Fixed64)
TryResolveMotionBound(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
ResolveLocalFraction(FixedMathSharp.Fixed64)
.ctor(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ContinuousCollisionRotationPath3D)
SamplePosition(FixedMathSharp.Fixed64)
SampleRotation(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
TryResolveMotionBound(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
ResolveLocalFraction(FixedMathSharp.Fixed64)
ValidateEndpoint(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64&)
ValidateEndpoint(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64&)
ValidateEndpoint(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64&)
ValidateEndpoint(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64&)
ValidateRelativeDisplacement(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64&)
ValidateRelativeDisplacement(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64&)
AllowsIndexed3DTarget(System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean)
AllowsIndexed2DTarget(System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean)
AllowsMixedIndexedTarget(System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean)
AllowsStaticOrKinematic3DTarget(System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean)
AllowsStaticOrKinematic2DTarget(System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean)
AllowsMixedStaticOrKinematicTarget(System.Boolean,System.Boolean,System.Boolean,System.Boolean,System.Boolean)
.ctor(System.Int32,System.Boolean)
get_Count()
Clear()
Add(System.Int32,SwiftCollections.Query.FixedBoundVolume)
AddOrUpdate(System.Int32,SwiftCollections.Query.FixedBoundVolume)
Remove(System.Int32)
Sort()
Query(SwiftCollections.Query.FixedBoundVolume,SwiftCollections.SwiftList`1<System.Int32>)
CreateSweptSphereBounds(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64)
CreateSweptBounds(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
CreateBoundsBetween(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
IncludeExtent(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
RemoveExtent(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
RebuildExtents()
FindFirstCandidateIndex(FixedMathSharp.Fixed64)
IsOrderedAt(System.Int32)
HeapSort()
SiftDown(System.Int32,System.Int32)
Swap(System.Int32,System.Int32)
Compare(Gravitas.CollisionHandling.DynamicCcdCandidateIndex/Entry,Gravitas.CollisionHandling.DynamicCcdCandidateIndex/Entry)
.ctor(System.Int32,SwiftCollections.Query.FixedBoundVolume)
Intersects(SwiftCollections.Query.FixedBoundVolume)
.ctor(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
.ctor(System.Int32,System.Boolean)
get_Count()
Clear()
Add(System.Int32,Gravitas.CollisionHandling.DynamicCcdPlanarBounds)
AddOrUpdate(System.Int32,Gravitas.CollisionHandling.DynamicCcdPlanarBounds)
Remove(System.Int32)
Sort()
Query(Gravitas.CollisionHandling.DynamicCcdPlanarBounds,SwiftCollections.SwiftList`1<System.Int32>)
CreateSweptCircleBounds(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64)
CreateBoundsBetween(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64)
IncludeExtent(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
RemoveExtent(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
RebuildExtents()
FindFirstCandidateIndex(FixedMathSharp.Fixed64)
IsOrderedAt(System.Int32)
HeapSort()
SiftDown(System.Int32,System.Int32)
Swap(System.Int32,System.Int32)
Compare(Gravitas.CollisionHandling.DynamicCcdCandidateIndex2D/Entry,Gravitas.CollisionHandling.DynamicCcdCandidateIndex2D/Entry)
.ctor(System.Int32,Gravitas.CollisionHandling.DynamicCcdPlanarBounds)
Intersects(Gravitas.CollisionHandling.DynamicCcdPlanarBounds)
IsEquivalent(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
TryCollide(Gravitas.Colliders.LSCollider2D,Gravitas.Colliders.LSCollider2D,Gravitas.CollisionHandling.Contact2D&)
TryCollide(Gravitas.CollisionPair2D,Gravitas.CollisionHandling.ContactManifold2D,System.Int32)
TryCollide(Gravitas.CollisionHandling.CollisionWorkItem2D,Gravitas.CollisionHandling.ContactManifold2D,System.Int32)
TryCollide(Gravitas.CollisionHandling.CollisionWorkItem2D,Gravitas.CollisionHandling.Contact2D&)
TryCollide(Gravitas.CollisionHandling.CollisionWorkItem2D,Gravitas.CollisionHandling.ContactManifold2D)
TryCircleCircle(Gravitas.Colliders.LSCircleCollider2D,Gravitas.Colliders.LSCircleCollider2D,Gravitas.CollisionHandling.Contact2D&)
TryCircleCircle(Gravitas.Colliders.LSCircleCollider2D,Gravitas.Colliders.LSCircleCollider2D,Gravitas.CollisionHandling.ContactManifold2D)
TryCircleConvex(Gravitas.Colliders.LSCircleCollider2D,Gravitas.Colliders.LSCollider2D,Gravitas.CollisionHandling.Contact2D&)
TryCircleConvex(Gravitas.Colliders.LSCircleCollider2D,Gravitas.Colliders.LSCollider2D,Gravitas.CollisionHandling.ContactManifold2D)
TryCircleConvexReversed(Gravitas.Colliders.LSCircleCollider2D,Gravitas.Colliders.LSCollider2D,Gravitas.CollisionHandling.ContactManifold2D)
TryCapsuleCircle(Gravitas.Colliders.LSCapsuleCollider2D,Gravitas.Colliders.LSCircleCollider2D,Gravitas.CollisionHandling.Contact2D&)
TryCapsuleCircle(Gravitas.Colliders.LSCapsuleCollider2D,Gravitas.Colliders.LSCircleCollider2D,Gravitas.CollisionHandling.ContactManifold2D)
TryCapsuleCircleReversed(Gravitas.Colliders.LSCapsuleCollider2D,Gravitas.Colliders.LSCircleCollider2D,Gravitas.CollisionHandling.ContactManifold2D)
TryCapsuleCapsule(Gravitas.Colliders.LSCapsuleCollider2D,Gravitas.Colliders.LSCapsuleCollider2D,Gravitas.CollisionHandling.Contact2D&)
TryCapsuleCapsule(Gravitas.Colliders.LSCapsuleCollider2D,Gravitas.Colliders.LSCapsuleCollider2D,Gravitas.CollisionHandling.ContactManifold2D)
TryCapsuleConvex(Gravitas.Colliders.LSCapsuleCollider2D,Gravitas.Colliders.LSCollider2D,Gravitas.CollisionHandling.Contact2D&)
TryGetCapsuleConvexContacts(Gravitas.Colliders.LSCapsuleCollider2D,Gravitas.Colliders.LSCollider2D,System.Span`1<FixedMathSharp.Geometry.FixedPointAnchor2d>,System.Span`1<FixedMathSharp.Geometry.FixedPointAnchor2d>,System.Int32&,FixedMathSharp.Vector2d&,FixedMathSharp.Fixed64&,System.Boolean&)
GetConvexVertexOffsets(Gravitas.Colliders.LSCollider2D,System.Span`1<FixedMathSharp.Vector2d>)
TryCapsuleConvex(Gravitas.Colliders.LSCapsuleCollider2D,Gravitas.Colliders.LSCollider2D,Gravitas.CollisionHandling.ContactManifold2D)
TryCapsuleConvexReversed(Gravitas.Colliders.LSCapsuleCollider2D,Gravitas.Colliders.LSCollider2D,Gravitas.CollisionHandling.ContactManifold2D)
TryConvexConvex(Gravitas.Colliders.LSCollider2D,Gravitas.Colliders.LSCollider2D,Gravitas.CollisionHandling.Contact2D&)
TryConvexConvex(Gravitas.Colliders.LSCollider2D,Gravitas.Colliders.LSCollider2D,Gravitas.CollisionHandling.ContactManifold2D)
TryCompound(Gravitas.Colliders.LSCollider2D,Gravitas.Colliders.LSCollider2D,Gravitas.CollisionHandling.Contact2D&)
TryCompoundCompound(Gravitas.Colliders.LSCompoundCollider2D,Gravitas.Colliders.LSCompoundCollider2D,Gravitas.CollisionHandling.Contact2D&)
TryCompoundOther(Gravitas.Colliders.LSCompoundCollider2D,Gravitas.Colliders.LSCollider2D,System.Boolean,Gravitas.CollisionHandling.Contact2D&)
TryCompound(Gravitas.Colliders.LSCollider2D,Gravitas.Colliders.LSCollider2D,Gravitas.CollisionHandling.ContactManifold2D)
TryCompoundCompound(Gravitas.Colliders.LSCompoundCollider2D,Gravitas.Colliders.LSCompoundCollider2D,Gravitas.CollisionHandling.ContactManifold2D)
TryCompoundOther(Gravitas.Colliders.LSCompoundCollider2D,Gravitas.Colliders.LSCollider2D,System.Boolean,Gravitas.CollisionHandling.ContactManifold2D)
AddCompoundPartContacts(Gravitas.CollisionHandling.ContactManifold2D,Gravitas.CollisionHandling.ContactManifold2D,System.Int32,System.Int32)
AddContact(Gravitas.CollisionHandling.ContactManifold2D,Gravitas.CollisionHandling.Contact2D,Gravitas.Colliders.LSCollider2D,Gravitas.Colliders.LSCollider2D)
BoundsOverlap(Gravitas.Colliders.LSCollider2D,Gravitas.Colliders.LSCollider2D)
OrientCoincidentCapsuleNormal(FixedMathSharp.Vector2d)
.ctor(Gravitas.Colliders.LSCollider2D,Gravitas.Colliders.LSCollider2D,Gravitas.Colliders.CollisionType2D)
Create(Gravitas.CollisionPair2D)
.ctor(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,System.Boolean)
DoCapsuleSphereCheck(Gravitas.CollisionHandling.CollisionWorkItem)
DoCapsulesCheck(Gravitas.CollisionHandling.CollisionWorkItem)
ResolveCenteredCapsuleFallback(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
DoCompoundCheck(Gravitas.CollisionHandling.CollisionWorkItem)
DoCompoundOtherCheck(Gravitas.CollisionHandling.CollisionWorkItem,Gravitas.Colliders.LSCompoundCollider,Gravitas.Colliders.LSCollider)
DoCompoundCompoundCheck(Gravitas.CollisionHandling.CollisionWorkItem,Gravitas.Colliders.LSCompoundCollider,Gravitas.Colliders.LSCompoundCollider)
TryBuildCompoundPartContact(Gravitas.CollisionHandling.CollisionWorkItem,Gravitas.Colliders.LSCollider,System.Int32,Gravitas.Colliders.LSCollider,System.Int32)
AddCompoundPartContactsInOwnerOrder(Gravitas.CollisionHandling.CollisionWorkItem,Gravitas.CollisionHandling.CollisionWorkItem,System.Int32,System.Int32)
OrderPartPairForDetection(Gravitas.Colliders.LSCollider,System.Int32,Gravitas.Colliders.LSCollider,System.Int32,Gravitas.Colliders.LSCollider&,System.Int32&,Gravitas.Colliders.LSCollider&,System.Int32&)
BoundsOverlapInclusive(Gravitas.Colliders.LSCollider,Gravitas.Colliders.LSCollider)
DoConeSphereCheck(Gravitas.CollisionHandling.CollisionWorkItem)
DoConeConvexCheck(Gravitas.CollisionHandling.CollisionWorkItem)
DoMeshConeCheck(Gravitas.CollisionHandling.CollisionWorkItem)
TryFindMeshConeTriangleContact(Gravitas.Colliders.LSMeshCollider,Gravitas.Colliders.LSConeCollider,SwiftCollections.SwiftList`1<System.Int32>,Gravitas.CollisionHandling.ContactAnchor&,Gravitas.CollisionHandling.ContactAnchor&,FixedMathSharp.Vector3d&,FixedMathSharp.Fixed64&,System.Boolean&)
GetConeConvexColliders(Gravitas.CollisionHandling.CollisionWorkItem,Gravitas.Colliders.LSConeCollider&,Gravitas.Colliders.LSCollider&)
OrientNormal(FixedMathSharp.Geometry.FixedPointAnchor&,FixedMathSharp.Geometry.FixedPointAnchor&,FixedMathSharp.Vector3d)
DoCollisionCheck(Gravitas.CollisionHandling.CollisionPair)
DoCollisionCheck(Gravitas.CollisionHandling.CollisionWorkItem)
DoCuboidSphereCheck(Gravitas.CollisionHandling.CollisionWorkItem)
DoCuboidCapsuleCheck(Gravitas.CollisionHandling.CollisionWorkItem)
DoCuboidsCheck(Gravitas.CollisionHandling.CollisionWorkItem)
CanBuildAxisAlignedManifold(Gravitas.Colliders.LSCuboidCollider)
TryBuildAxisAlignedCuboidManifold(Gravitas.CollisionHandling.CollisionWorkItem,Gravitas.Colliders.LSCuboidCollider,Gravitas.Colliders.LSCuboidCollider)
AddAxisAlignedCuboidContacts(Gravitas.CollisionHandling.ContactManifold,Gravitas.Colliders.LSCuboidCollider,Gravitas.Colliders.LSCuboidCollider,System.Int32,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d)
AddCuboidContact(Gravitas.CollisionHandling.ContactManifold,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64)
DoCylinderSphereCheck(Gravitas.CollisionHandling.CollisionWorkItem)
DoCylinderCapsuleCheck(Gravitas.CollisionHandling.CollisionWorkItem)
DoCylindersCheck(Gravitas.CollisionHandling.CollisionWorkItem)
DoCuboidCylinderCheck(Gravitas.CollisionHandling.CollisionWorkItem)
TryAddCylinderCylinderCapContacts(Gravitas.CollisionHandling.CollisionWorkItem,Gravitas.Colliders.LSCylinderCollider,Gravitas.Colliders.LSCylinderCollider,FixedMathSharp.Geometry.FixedContactAnchors)
TryAddCylinderCylinderCapContact(Gravitas.CollisionHandling.CollisionWorkItem,Gravitas.Colliders.LSCylinderCollider,Gravitas.Colliders.LSCylinderCollider,FixedMathSharp.Vector3d,FixedMathSharp.Geometry.FixedContactAnchors)
ResolvePenetrationDepth(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&,System.Boolean&)
ResolveNormal(FixedMathSharp.Vector3d)
SetContactInPairOrder(Gravitas.CollisionHandling.CollisionWorkItem,Gravitas.Colliders.LSCollider,Gravitas.CollisionHandling.ContactAnchor,Gravitas.Colliders.LSCollider,Gravitas.CollisionHandling.ContactAnchor,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,System.Boolean)
DoMeshSphereCheck(Gravitas.CollisionHandling.CollisionWorkItem)
DoMeshCapsuleCheck(Gravitas.CollisionHandling.CollisionWorkItem)
DoMeshCuboidCheck(Gravitas.CollisionHandling.CollisionWorkItem)
DoMeshCylinderCheck(Gravitas.CollisionHandling.CollisionWorkItem)
DoMeshesCheck(Gravitas.CollisionHandling.CollisionWorkItem)
DoSpheresCheck(Gravitas.CollisionHandling.CollisionWorkItem)
.ctor(Gravitas.GravitasWorldContext,Gravitas.Colliders.LSCollider,Gravitas.Colliders.LSCollider,Gravitas.Colliders.CollisionType,Gravitas.CollisionHandling.ContactManifold)
Create(Gravitas.CollisionHandling.CollisionPair)
GetSupportAnchor(Gravitas.Colliders.LSCollider,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
IsSupported(Gravitas.Colliders.LSCollider)
Intersects(Gravitas.Colliders.LSCollider,Gravitas.Colliders.LSCollider,System.Int32)
IntersectsConeVolume(Gravitas.Colliders.LSCollider,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,System.Int32)
TrySupportMinkowski(Gravitas.Colliders.LSCollider,Gravitas.Colliders.LSCollider,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&)
TrySupportMinkowskiConeCollider(Gravitas.Colliders.LSCollider,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&)
ResolveSupportDirection(FixedMathSharp.Vector3d)
GetLocalDisplacement(FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d)
IsAxisAligned(FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
GetCapBasis(Gravitas.Colliders.LSCylinderCollider,FixedMathSharp.Vector3d&,FixedMathSharp.Vector3d&)
AddPoint(System.Span`1<FixedMathSharp.Vector3d>,System.Int32&,FixedMathSharp.Vector3d)
Update(System.Span`1<FixedMathSharp.Vector3d>,System.Int32&,FixedMathSharp.Vector3d&)
UpdateLine(System.Span`1<FixedMathSharp.Vector3d>,System.Int32&,FixedMathSharp.Vector3d&)
UpdateTriangle(System.Span`1<FixedMathSharp.Vector3d>,System.Int32&,FixedMathSharp.Vector3d&)
UpdateTetrahedron(System.Span`1<FixedMathSharp.Vector3d>,System.Int32&,FixedMathSharp.Vector3d&)
SameDirection(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
TripleCross(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
OrientFaceNormal(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
Perpendicular(FixedMathSharp.Vector3d)
TryBuildMeshSphereManifold(Gravitas.CollisionHandling.CollisionWorkItem,Gravitas.Colliders.LSMeshCollider,Gravitas.Colliders.LSSphereCollider,SwiftCollections.SwiftList`1<System.Int32>)
TryBuildMeshCapsuleManifold(Gravitas.CollisionHandling.CollisionWorkItem,Gravitas.Colliders.LSMeshCollider,Gravitas.Colliders.LSCapsuleCollider,SwiftCollections.SwiftList`1<System.Int32>)
TryBuildMeshCuboidManifold(Gravitas.CollisionHandling.CollisionWorkItem,Gravitas.Colliders.LSMeshCollider,Gravitas.Colliders.LSCuboidCollider,SwiftCollections.SwiftList`1<System.Int32>)
TryBuildMeshCylinderManifold(Gravitas.CollisionHandling.CollisionWorkItem,Gravitas.Colliders.LSMeshCollider,Gravitas.Colliders.LSCylinderCollider,SwiftCollections.SwiftList`1<System.Int32>)
TryBuildMeshMeshManifold(Gravitas.CollisionHandling.CollisionWorkItem,Gravitas.Colliders.LSMeshCollider,Gravitas.Colliders.LSMeshCollider,SwiftCollections.SwiftList`1<System.Int32>,SwiftCollections.SwiftList`1<System.Int32>)
TryAddSphereTriangleContact(Gravitas.CollisionHandling.CollisionWorkItem,Gravitas.Colliders.LSMeshCollider,System.Int32,Gravitas.Colliders.LSSphereCollider)
TryAddCapsuleTriangleContact(Gravitas.CollisionHandling.CollisionWorkItem,Gravitas.Colliders.LSMeshCollider,System.Int32,Gravitas.Colliders.LSCapsuleCollider)
TryAddCuboidTriangleContact(Gravitas.CollisionHandling.CollisionWorkItem,Gravitas.Colliders.LSMeshCollider,System.Int32,Gravitas.Colliders.LSCuboidCollider)
AddReversedBoxTriangleContact(Gravitas.CollisionHandling.CollisionWorkItem,FixedMathSharp.Geometry.FixedContactAnchors)
AddReversedBoxTriangleContact(Gravitas.CollisionHandling.CollisionWorkItem,FixedMathSharp.Geometry.FixedContactAnchors,FixedMathSharp.Geometry.FixedContactLocalPoints)
TryAddCylinderTriangleContact(Gravitas.CollisionHandling.CollisionWorkItem,Gravitas.Colliders.LSMeshCollider,System.Int32,Gravitas.Colliders.LSCylinderCollider)
TryAddCylinderCapTriangleContacts(Gravitas.CollisionHandling.CollisionWorkItem,Gravitas.Colliders.LSMeshCollider,FixedMathSharp.Geometry.FixedTriangle,Gravitas.Colliders.LSCylinderCollider,FixedMathSharp.Vector3d)
TryAddCylinderCapTriangleContact(Gravitas.CollisionHandling.CollisionWorkItem,Gravitas.Colliders.LSMeshCollider,FixedMathSharp.Geometry.FixedTriangle,Gravitas.Colliders.LSCylinderCollider,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
GetTriangleBoundsInFrame(FixedMathSharp.Geometry.FixedBoundBox,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,FixedMathSharp.Vector3d,FixedMathSharp.FixedQuaternion,SwiftCollections.Query.FixedBoundVolume&)
CreateBounds(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
OrientNormal(FixedMathSharp.Geometry.FixedPointAnchor&,FixedMathSharp.Geometry.FixedPointAnchor&,FixedMathSharp.Vector3d)
AddContact(Gravitas.CollisionHandling.CollisionWorkItem,Gravitas.CollisionHandling.ContactAnchor,Gravitas.CollisionHandling.ContactAnchor,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,System.Boolean)
TryCompoundEmbedded2D(Gravitas.Colliders.LSCompoundCollider,Gravitas.Colliders.LSCollider2D,Gravitas.CollisionHandling.MixedContact&)
TryMeshEmbedded2D(Gravitas.Colliders.LSMeshCollider,Gravitas.Colliders.LSCollider2D,Gravitas.CollisionHandling.MixedContact&)
TryGetTriangleFiniteSurfaceContact(Gravitas.Colliders.LSMeshCollider,System.Int32,Gravitas.Colliders.LSCollider2D,Gravitas.CollisionHandling.MixedContact&)
TryCollide(Gravitas.Colliders.LSCollider,Gravitas.Colliders.LSCollider2D,Gravitas.CollisionHandling.MixedContact&)
TryEmbeddedCompound2D(Gravitas.Colliders.LSCollider,Gravitas.Colliders.LSCompoundCollider2D,Gravitas.CollisionHandling.MixedContact&)
TrySphereEmbedded2D(Gravitas.Colliders.LSSphereCollider,Gravitas.Colliders.LSCollider2D,Gravitas.CollisionHandling.MixedContact&)
TryCapsuleEmbedded2D(Gravitas.Colliders.LSCapsuleCollider,Gravitas.Colliders.LSCollider2D,Gravitas.CollisionHandling.MixedContact&)
TryCylinderEmbedded2D(Gravitas.Colliders.LSCylinderCollider,Gravitas.Colliders.LSCollider2D,Gravitas.CollisionHandling.MixedContact&)
TryConeEmbedded2D(Gravitas.Colliders.LSConeCollider,Gravitas.Colliders.LSCollider2D,Gravitas.CollisionHandling.MixedContact&)
TryTestCapsuleCircleSlab(Gravitas.Colliders.LSCapsuleCollider,Gravitas.Colliders.LSCircleCollider2D,Gravitas.CollisionHandling.AxisPenetration&)
TryTestCylinderCircleSlab(Gravitas.Colliders.LSCylinderCollider,Gravitas.Colliders.LSCircleCollider2D,Gravitas.CollisionHandling.AxisPenetration&)
TryTestConeCircleSlab(Gravitas.Colliders.LSConeCollider,Gravitas.Colliders.LSCircleCollider2D,Gravitas.CollisionHandling.AxisPenetration&)
TryTestCapsuleCapsuleSlab(Gravitas.Colliders.LSCapsuleCollider,Gravitas.Colliders.LSCapsuleCollider2D,Gravitas.CollisionHandling.AxisPenetration&)
TryTestCylinderCapsuleSlab(Gravitas.Colliders.LSCylinderCollider,Gravitas.Colliders.LSCapsuleCollider2D,Gravitas.CollisionHandling.AxisPenetration&)
TryTestConeCapsuleSlab(Gravitas.Colliders.LSConeCollider,Gravitas.Colliders.LSCapsuleCollider2D,Gravitas.CollisionHandling.AxisPenetration&)
CheckCapsuleCircleSlabAxis(Gravitas.Colliders.LSCapsuleCollider,Gravitas.Colliders.LSCircleCollider2D,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.CollisionHandling.AxisPenetration&)
CheckCylinderCircleSlabAxis(Gravitas.Colliders.LSCylinderCollider,Gravitas.Colliders.LSCircleCollider2D,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.CollisionHandling.AxisPenetration&)
CheckConeCircleSlabAxis(Gravitas.Colliders.LSConeCollider,Gravitas.Colliders.LSCircleCollider2D,FixedMathSharp.Vector3d,Gravitas.CollisionHandling.AxisPenetration&)
CheckCapsulePrismAxis(Gravitas.Colliders.LSCapsuleCollider,Gravitas.Colliders.LSCapsuleCollider2D,FixedMathSharp.Vector3d,FixedMathSharp.Vector2d,FixedMathSharp.Vector3d,Gravitas.CollisionHandling.AxisPenetration&)
CheckCylinderPrismAxis(Gravitas.Colliders.LSCylinderCollider,Gravitas.Colliders.LSCapsuleCollider2D,FixedMathSharp.Vector3d,FixedMathSharp.Vector2d,FixedMathSharp.Vector3d,Gravitas.CollisionHandling.AxisPenetration&)
CheckConePrismAxis(Gravitas.Colliders.LSConeCollider,Gravitas.Colliders.LSCapsuleCollider2D,FixedMathSharp.Vector2d,FixedMathSharp.Vector3d,Gravitas.CollisionHandling.AxisPenetration&)
KeepAxisPenetration(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,System.Boolean,Gravitas.CollisionHandling.AxisPenetration&)
CheckEmbeddedCapsuleAxes(Gravitas.Colliders.LSCapsuleCollider,FixedMathSharp.Vector3d,Gravitas.Colliders.LSCapsuleCollider2D,FixedMathSharp.Vector2d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.CollisionHandling.AxisPenetration&)
CheckEmbeddedCapsuleAxes(Gravitas.Colliders.LSCylinderCollider,FixedMathSharp.Vector3d,Gravitas.Colliders.LSCapsuleCollider2D,FixedMathSharp.Vector2d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.CollisionHandling.AxisPenetration&)
CheckEmbeddedCapsuleAxes(Gravitas.Colliders.LSConeCollider,Gravitas.Colliders.LSCapsuleCollider2D,FixedMathSharp.Vector2d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.CollisionHandling.AxisPenetration&)
CheckCapsuleEmbeddedCapsuleEdgeAxis(Gravitas.Colliders.LSCapsuleCollider,FixedMathSharp.Vector3d,Gravitas.Colliders.LSCapsuleCollider2D,FixedMathSharp.Vector2d,FixedMathSharp.Vector3d,Gravitas.CollisionHandling.AxisPenetration&)
CheckCylinderEmbeddedCapsuleEdgeAxis(Gravitas.Colliders.LSCylinderCollider,FixedMathSharp.Vector3d,Gravitas.Colliders.LSCapsuleCollider2D,FixedMathSharp.Vector2d,FixedMathSharp.Vector3d,Gravitas.CollisionHandling.AxisPenetration&)
CheckConeEmbeddedCapsuleEdgeAxis(Gravitas.Colliders.LSConeCollider,FixedMathSharp.Vector3d,Gravitas.Colliders.LSCapsuleCollider2D,FixedMathSharp.Vector2d,FixedMathSharp.Vector3d,Gravitas.CollisionHandling.AxisPenetration&)
BuildCapsuleContact(Gravitas.Colliders.LSCapsuleCollider,Gravitas.Colliders.LSCollider2D,Gravitas.CollisionHandling.AxisPenetration,Gravitas.CollisionHandling.MixedContact&)
BuildCylinderContact(Gravitas.Colliders.LSCylinderCollider,Gravitas.Colliders.LSCollider2D,Gravitas.CollisionHandling.AxisPenetration,Gravitas.CollisionHandling.MixedContact&)
BuildConeContact(Gravitas.Colliders.LSConeCollider,Gravitas.Colliders.LSCollider2D,Gravitas.CollisionHandling.AxisPenetration,Gravitas.CollisionHandling.MixedContact&)
BuildCanonicalSupportContact(Gravitas.Colliders.LSCollider,Gravitas.Colliders.LSCollider2D,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,System.Boolean)
TryCuboidEmbedded2D(Gravitas.Colliders.LSCuboidCollider,Gravitas.Colliders.LSCollider2D,Gravitas.CollisionHandling.MixedContact&)
TryGetCuboidPrismContact(Gravitas.Colliders.LSCuboidCollider,Gravitas.Colliders.LSCollider2D,System.ReadOnlySpan`1<FixedMathSharp.Vector2d>,Gravitas.CollisionHandling.MixedContact&)
TryGetCuboidRelationContact(System.Boolean,FixedMathSharp.Geometry.FixedContactAnchors&,Gravitas.CollisionHandling.MixedContact&)
TryGetTriangleConvexPrismContact(Gravitas.Colliders.LSMeshCollider,System.Int32,Gravitas.Colliders.LSCollider2D,Gravitas.CollisionHandling.MixedContact&)
TryGetSphereConvexPrismContact(Gravitas.Colliders.LSSphereCollider,Gravitas.Colliders.LSCollider2D,Gravitas.CollisionHandling.MixedContact&)
TryGetCapsuleConvexPrismContact(Gravitas.Colliders.LSCapsuleCollider,Gravitas.Colliders.LSCollider2D,Gravitas.CollisionHandling.MixedContact&)
TryGetCylinderConvexPrismContact(Gravitas.Colliders.LSCylinderCollider,Gravitas.Colliders.LSCollider2D,Gravitas.CollisionHandling.MixedContact&)
TryGetConeConvexPrismContact(Gravitas.Colliders.LSConeCollider,Gravitas.Colliders.LSCollider2D,Gravitas.CollisionHandling.MixedContact&)
GetConvexPrismOffsets(Gravitas.Colliders.LSCollider2D,System.Span`1<FixedMathSharp.Vector2d>)
TryBuildFinitePrismContact(System.Boolean,FixedMathSharp.Geometry.FixedContactAnchors&,Gravitas.CollisionHandling.MixedContact&)
TryGetRotationalSeparationGap(Gravitas.Colliders.LSCollider,Gravitas.Colliders.LSCollider2D,FixedMathSharp.Fixed64&,System.Boolean&)
TryGetSpherePolygonSlabSeparationGap(Gravitas.Colliders.LSSphereCollider,Gravitas.Colliders.LSPolygonCollider2D,FixedMathSharp.Fixed64&)
TryGetPolygonExteriorGap(Gravitas.Colliders.LSPolygonCollider2D,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64&)
TryGetPointIntervalGap(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
TryGetIntervalGap(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
TryNormalizeAxis(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&)
GetEmbeddedCenter3D(Gravitas.Colliders.LSCollider2D)
GetEmbeddedCapsuleAxes(Gravitas.Colliders.LSCapsuleCollider2D,FixedMathSharp.Vector2d&,FixedMathSharp.Vector3d&,FixedMathSharp.Vector3d&)
GetRigidUpAxis(FixedMathSharp.FixedQuaternion)
ResolveFallbackNormal(FixedMathSharp.Vector3d,Gravitas.Colliders.LSCollider2D)
NoContact(Gravitas.CollisionHandling.MixedContact&)
GetCenter3D(Gravitas.Colliders.LSCollider2D)
GetSupportAnchor(Gravitas.Colliders.LSCollider2D,FixedMathSharp.Vector3d)
GetClosestAnchorOnEmbeddedVolume(Gravitas.Colliders.LSCollider2D,FixedMathSharp.Vector3d)
ContainsPointInSlab(Gravitas.Colliders.LSCollider2D,FixedMathSharp.Fixed64)
TryGetPlanarBoundaryPoint(Gravitas.Colliders.LSCollider2D,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d&,FixedMathSharp.Fixed64&)
TryGetPlanarBoundaryAnchor(Gravitas.Colliders.LSCollider2D,FixedMathSharp.Vector2d,Gravitas.CollisionHandling.ContactAnchor2D&)
TryGetPlanarBoundaryAnchor(Gravitas.Colliders.LSCollider2D,FixedMathSharp.Vector2d,Gravitas.CollisionHandling.ContactAnchor2D&,FixedMathSharp.Fixed64&)
TryGetPlanarSupportAnchor(Gravitas.Colliders.LSCollider2D,FixedMathSharp.Vector2d,Gravitas.CollisionHandling.ContactAnchor2D&)
TryGetRoundSupportAnchor(FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,Gravitas.CollisionHandling.ContactAnchor2D&)
EmbedPlanarAnchor(Gravitas.CollisionHandling.ContactAnchor2D,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
GetClosestSlabOffset(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&,System.Boolean&,FixedMathSharp.Fixed64&)
.ctor(Gravitas.Colliders.LSCollider,Gravitas.Colliders.LSCollider)
get_World()
get_IsNotificationInProgress()
get_LifetimeVersion()
Initialize(Gravitas.Colliders.LSCollider,Gravitas.Colliders.LSCollider)
AssignPriority(Gravitas.Colliders.LSCollider,Gravitas.Colliders.LSCollider)
ShouldFirstColliderLead(Gravitas.Colliders.LSCollider,Gravitas.Colliders.LSCollider)
UpdateCollision()
UpdateCollisionDeferred()
UpdateCollision(Gravitas.CollisionHandling.CollisionResponseDispatchMode)
UpdateLastFrame()
DeactivateAndPoolIfRequired()
ProcessCollision(Gravitas.CollisionHandling.CollisionResponseDispatchMode)
WakeSleepingBodiesForCollision()
NotifyCollidersOfContact()
EndNotification(Gravitas.Colliders.ColliderLifetimeToken&,Gravitas.Colliders.ColliderLifetimeToken&,System.Boolean,System.Boolean,System.Boolean)
NotifySeparation(Gravitas.Colliders.ColliderLifetimeToken&,Gravitas.Colliders.ColliderLifetimeToken&,Gravitas.SolidBody,Gravitas.SolidBody,System.Boolean,System.Boolean,System.Boolean,System.Boolean)
ClearPendingNotificationState()
HandleCullingIfNotColliding()
TryPreserveSleepingRestingContact()
CheckCollision()
IsCullStateInvalidated()
BroadPhaseVersionChanged()
RefreshBroadPhaseVersions()
ShouldPerformCollisionCheck()
BoundsOverlapInclusive(Gravitas.Colliders.LSCollider,Gravitas.Colliders.LSCollider)
CalculateCullScore()
GetCullDistanceStep(GridForge.Grids.GridWorld)
Reset()
StoreWarmStartImpulse(System.UInt64,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
TryGetWarmStartImpulse(System.UInt64,Gravitas.CollisionHandling.ContactWarmStartImpulse&)
RemoveWarmStartImpulse(System.UInt64)
ClearWarmStart()
Deactivate()
ContributeReplayHash(Chronicler.ChronicleHashWriter&,Gravitas.GravitasReplayHashMode)
ContributeManifoldReplayHash(Chronicler.ChronicleHashWriter&,Gravitas.CollisionHandling.ContactManifold)
WriteMaterial(Chronicler.ChronicleHashWriter&,Gravitas.Materials.PhysicsMaterial)
ContributeWarmStartReplayHash(Chronicler.ChronicleHashWriter&,Gravitas.CollisionHandling.ContactWarmStartCache)
Capture(SwiftCollections.SwiftList`1<System.Exception>&,System.Exception)
ThrowIfAny(SwiftCollections.SwiftList`1<System.Exception>)
.ctor(Gravitas.Colliders.LSCollider,Gravitas.Colliders.LSCollider2D)
get_Context()
get_LifetimeVersion()
get_IsNotificationInProgress()
Initialize(Gravitas.Colliders.LSCollider,Gravitas.Colliders.LSCollider2D)
MarkColliding(System.Int32,Gravitas.CollisionHandling.MixedContact)
MarkResting(System.Int32)
MarkResting(System.Int32,Gravitas.CollisionHandling.MixedContact)
MarkSeparated()
EndNotification(Gravitas.Colliders.ColliderLifetimeToken&,Gravitas.Colliders.ColliderLifetimeToken2D&,System.Boolean)
NotifySeparation(Gravitas.Colliders.ColliderLifetimeToken&,Gravitas.Colliders.ColliderLifetimeToken2D&,System.Boolean)
WakeSleepingBodiesForCollision()
ContributeReplayHash(Chronicler.ChronicleHashWriter&,Gravitas.GravitasReplayHashMode)
WriteMaterial(Chronicler.ChronicleHashWriter&,Gravitas.Materials.PhysicsMaterial)
.cctor()
Resolve(Gravitas.CollisionPair2D)
Resolve(Gravitas.CollisionPair2D,System.Boolean,System.Boolean)
TryCreateBodyPair(Gravitas.CollisionPair2D,Gravitas.CollisionHandling.ResponseBody2D&,Gravitas.CollisionHandling.ResponseBody2D&)
BuildContactBuffer(Gravitas.CollisionPair2D,Gravitas.CollisionHandling.ResponseBody2D,Gravitas.CollisionHandling.ResponseBody2D,Gravitas.CollisionHandling.ContactAnchor2D&,Gravitas.CollisionHandling.ContactAnchor2D&)
TryCreateContact(Gravitas.CollisionPair2D,Gravitas.CollisionHandling.ResponseBody2D,Gravitas.CollisionHandling.ResponseBody2D,Gravitas.CollisionHandling.ContactAnchor2D&,Gravitas.CollisionHandling.ContactAnchor2D&,System.Int32,Gravitas.CollisionHandling.SolverContact2D&)
TryCalculateExactNormalResult(Gravitas.CollisionPair2D,Gravitas.CollisionHandling.SolverContact2D,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ContactNormalImpulseResult2D&)
ApplyPositionCorrection(Gravitas.CollisionHandling.SolverContact2D,FixedMathSharp.Fixed64)
TryApplyCachedImpulse(Gravitas.CollisionPair2D,Gravitas.CollisionHandling.SolverContact2D,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d)
TryApplyNormalImpulse(Gravitas.CollisionHandling.SolverContact2D,Gravitas.CollisionHandling.ContactNormalImpulseResult2D)
ApplyVelocityDelta(Gravitas.CollisionHandling.ResponseBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64)
TrySolveFrictionImpulse(Gravitas.CollisionPair2D,Gravitas.CollisionHandling.SolverContact2D,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ContactNormalImpulseResult2D,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
TrySolveFrictionImpulseExact(Gravitas.CollisionPair2D,Gravitas.CollisionHandling.SolverContact2D,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
CanUseCompactAxisResponse(Gravitas.CollisionHandling.SolverContact2D,FixedMathSharp.Vector2d)
ApplyPositionCorrection(Gravitas.CollisionHandling.ResponseBody2D,FixedMathSharp.Vector2d)
TryApplyContactImpulseCombination(Gravitas.CollisionPair2D,Gravitas.CollisionHandling.SolverContact2D,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64)
TryApplyCompactImpulse(Gravitas.CollisionHandling.SolverContact2D,FixedMathSharp.Vector2d)
TryGetLinearVelocityDelta(Gravitas.CollisionHandling.ResponseBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d&)
TryGetAngularVelocityDelta(Gravitas.CollisionHandling.ResponseBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64&)
TryApplyVelocityDeltas(Gravitas.CollisionHandling.SolverContact2D,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64)
ResolveLinearVelocity(Gravitas.SolidBody2D)
ResolveAngularVelocity(Gravitas.SolidBody2D)
ResolveContactNormal(FixedMathSharp.Vector2d,FixedMathSharp.Vector2d)
GetExactLevers(Gravitas.CollisionPair2D,Gravitas.CollisionHandling.SolverContact2D,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,Gravitas.CollisionHandling.ExactLever3D&,Gravitas.CollisionHandling.ExactLever3D&)
HasFailedResponse(System.Byte,System.Int32)
RejectResponse(Gravitas.CollisionPair2D,Gravitas.CollisionHandling.SolverContact2D,System.Int32,System.Byte&)
.ctor(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64)
.ctor(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,System.Boolean,System.Boolean,System.Boolean)
.ctor(FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64)
.ctor(FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,System.Boolean,System.Boolean)
TryCalculateVelocityDeltas(Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ContactNormalVelocityDeltaResult2D&)
TryCalculateAccumulatedDelta(Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ContactNormalImpulseResult2D&)
TryCalculateVelocityDeltasExact(Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ExactLever3D&,Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ExactLever3D&,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ContactNormalVelocityDeltaResult2D&)
TryCalculateAccumulatedDeltaExact(Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ExactLever3D&,Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ExactLever3D&,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ContactNormalImpulseResult2D&)
TryComputeNormalVelocity(FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64&)
TryComputeDenominator(Gravitas.SolidBody2D,FixedMathSharp.Vector2d,Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64&)
TryComputeAngularDenominator(Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64&)
TryGetConstrainedInverseMass(Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64&)
TryComputeLinearVelocityDelta(Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d&)
TryComputeAngularVelocityDelta(Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
TryResolveAngularVelocityDelta(Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
Zero(FixedMathSharp.Fixed64)
ZeroVelocityDelta(FixedMathSharp.Fixed64)
CanUseCompactResponse(Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d)
CreateResponseOperand(Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ExactLever3D&,FixedMathSharp.Vector3d)
TryGetNormalResponse(Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ExactLever3D&,Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ExactLever3D&,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ExactNormalResponse3D&)
TryGetAccumulatedNormalResponse(Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ExactLever3D&,Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ExactLever3D&,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ExactNormalResponse3D&)
ToSpatial(FixedMathSharp.Vector2d)
ToPlanar(FixedMathSharp.Vector3d)
ToPlanarAngular(FixedMathSharp.Vector3d)
TryGetImpulseVelocityDeltas(Gravitas.SolidBody2D,Gravitas.CollisionHandling.ExactLever3D&,Gravitas.SolidBody2D,Gravitas.CollisionHandling.ExactLever3D&,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d&,FixedMathSharp.Fixed64&,FixedMathSharp.Vector2d&,FixedMathSharp.Fixed64&)
TryGetParticipantVelocityDeltas(Gravitas.SolidBody2D,Gravitas.CollisionHandling.ExactLever3D&,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d&,FixedMathSharp.Fixed64&)
CreateInverseInertia(Gravitas.SolidBody2D)
.ctor(Gravitas.SolidBody2D,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
get_CanTranslate()
get_CanRotate()
get_HasSolverMobility()
GetConstrainedInverseMass(FixedMathSharp.Vector2d)
Create(Gravitas.Colliders.LSCollider2D)
.ctor(FixedMathSharp.Vector2d,System.Boolean)
get_Zero()
Create(Gravitas.CollisionHandling.ContactAnchor2D&,Gravitas.CollisionHandling.ContactAnchor2D&)
.ctor(System.Int32,System.UInt64,Gravitas.CollisionHandling.ResponseBody2D,Gravitas.CollisionHandling.ResponseBody2D,Gravitas.CollisionHandling.ContactLever2D,Gravitas.CollisionHandling.ContactLever2D,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,Gravitas.Materials.PhysicsMaterial,Gravitas.Materials.PhysicsMaterial,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
Add(Gravitas.CollisionHandling.SolverContact2D)
GetContact(System.Int32)
GetNormalImpulse(System.Int32)
GetNormalResult(System.Int32)
GetTangentImpulse(System.Int32)
SetNormalImpulse(System.Int32,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ContactNormalImpulseResult2D)
SetTangentImpulse(System.Int32,FixedMathSharp.Fixed64)
.cctor()
CalculateImpulse(Gravitas.CollisionHandling.CollisionPair)
CalculateImpulse(Gravitas.CollisionHandling.CollisionPair,System.Boolean,System.Boolean)
TryCreateBodyPair(Gravitas.CollisionHandling.CollisionPair,Gravitas.CollisionHandling.ResponseBody&,Gravitas.CollisionHandling.ResponseBody&)
BuildContactBuffer(Gravitas.CollisionHandling.CollisionPair,Gravitas.CollisionHandling.ResponseBody,Gravitas.CollisionHandling.ResponseBody,Gravitas.CollisionHandling.ContactAnchor&,Gravitas.CollisionHandling.ContactAnchor&)
TryCreateContact(Gravitas.CollisionHandling.CollisionPair,Gravitas.CollisionHandling.ResponseBody,Gravitas.CollisionHandling.ResponseBody,Gravitas.CollisionHandling.ContactAnchor&,Gravitas.CollisionHandling.ContactAnchor&,System.Int32,Gravitas.CollisionHandling.SolverContact&)
TryCalculateExactNormalResult(Gravitas.CollisionHandling.CollisionPair,Gravitas.CollisionHandling.SolverContact,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ContactNormalImpulseResult3D&)
ApplyPositionCorrection(Gravitas.CollisionHandling.SolverContact,FixedMathSharp.Fixed64)
TryApplyCachedImpulse(Gravitas.CollisionHandling.CollisionPair,Gravitas.CollisionHandling.SolverContact,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
TryApplyContactImpulseCombination(Gravitas.CollisionHandling.CollisionPair,Gravitas.CollisionHandling.SolverContact,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64)
TryApplyContactImpulse(Gravitas.CollisionHandling.CollisionPair,Gravitas.CollisionHandling.SolverContact,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
TryApplyContactImpulseExact(Gravitas.CollisionHandling.CollisionPair,Gravitas.CollisionHandling.SolverContact,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
TryApplyContactImpulseCombinationExact(Gravitas.CollisionHandling.CollisionPair,Gravitas.CollisionHandling.SolverContact,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64)
TryApplyNormalImpulse(Gravitas.CollisionHandling.CollisionPair,Gravitas.CollisionHandling.SolverContact,Gravitas.CollisionHandling.ContactNormalImpulseResult3D)
TrySolveFrictionImpulse(Gravitas.CollisionHandling.CollisionPair,Gravitas.CollisionHandling.SolverContact,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&,FixedMathSharp.Fixed64&)
TryGetCompactFrictionResponse(Gravitas.CollisionHandling.SolverContact,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&,FixedMathSharp.Fixed64&,FixedMathSharp.Fixed64&,FixedMathSharp.Fixed64&)
TrySolveFrictionImpulseExact(Gravitas.CollisionHandling.CollisionPair,Gravitas.CollisionHandling.SolverContact,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&,FixedMathSharp.Fixed64&)
TryGetCompactTangentImpulseDelta(Gravitas.CollisionHandling.SolverContact,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64&)
TryGetCompactConstrainedInverseMass(Gravitas.CollisionHandling.ResponseBody,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64&)
TryGetFrictionLimit(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
TryGetMagnitudeSquared(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
TryGetSquare(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
ResolveLinearVelocity(Gravitas.SolidBody)
ResolveAngularVelocity(Gravitas.SolidBody)
TryApplyExactImpulse(Gravitas.CollisionHandling.SolverContact,Gravitas.CollisionHandling.ExactLever3D&,Gravitas.CollisionHandling.ExactLever3D&,FixedMathSharp.Vector3d)
TryApplyCompactImpulse(Gravitas.CollisionHandling.SolverContact,FixedMathSharp.Vector3d)
CanNegate(FixedMathSharp.Vector3d)
TryGetLinearVelocityDelta(Gravitas.CollisionHandling.ResponseBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&)
TryGetAngularVelocityDelta(Gravitas.CollisionHandling.ResponseBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&)
TryPrepareVelocityStates(Gravitas.CollisionHandling.SolverContact,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&,FixedMathSharp.Vector3d&,FixedMathSharp.Vector3d&,FixedMathSharp.Vector3d&)
TryApplyVelocityDeltas(Gravitas.CollisionHandling.SolverContact,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
ApplyVelocityStates(Gravitas.CollisionHandling.SolverContact,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
GetExactLevers(Gravitas.CollisionHandling.CollisionPair,Gravitas.CollisionHandling.SolverContact,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.CollisionHandling.ExactLever3D&,Gravitas.CollisionHandling.ExactLever3D&)
HasFailedResponse(System.Byte,System.Int32)
RejectResponse(Gravitas.CollisionHandling.CollisionPair,Gravitas.CollisionHandling.SolverContact,System.Int32,System.Byte&)
ClearWarmStartImpulse(Gravitas.CollisionHandling.CollisionPair,Gravitas.CollisionHandling.SolverContact)
IsWarmStartCompatible(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
ResolveContactNormal(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
.ctor(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
.ctor(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,System.Boolean,System.Boolean)
.ctor(FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
.ctor(FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,System.Boolean,System.Boolean)
.ctor(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
get_SaturatedSum()
TryGetValue(FixedMathSharp.Fixed64&)
TryCalculateVelocityDeltas(Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ContactNormalVelocityDeltaResult3D&)
TryCalculateVelocityDeltasExact(Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.CollisionHandling.ExactLever3D&,Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.CollisionHandling.ExactLever3D&,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ContactNormalVelocityDeltaResult3D&)
CalculateAccumulatedDelta(Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
TryCalculateAccumulatedDelta(Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ContactNormalImpulseResult3D&)
TryCalculateAccumulatedDeltaExact(Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.CollisionHandling.ExactLever3D&,Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.CollisionHandling.ExactLever3D&,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ContactNormalImpulseResult3D&)
TryCalculateAccumulatedImpulseDelta(FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ContactEffectiveMassTerms3D&,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
GetConstrainedInverseMass(Gravitas.SolidBody,FixedMathSharp.Vector3d)
TryComputeNormalVelocity(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64&)
TryComputeDenominator(Gravitas.SolidBody,FixedMathSharp.Vector3d,Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.CollisionHandling.ContactEffectiveMassTerms3D&)
TryComputeAngularDenominator(Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64&)
TryComputeLinearVelocityDelta(Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&)
TryComputeAngularVelocityDelta(Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&)
TryResolveAngularVelocityDelta(Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ContactEffectiveMassTerms3D&,FixedMathSharp.Vector3d&)
TryResolveVelocityDelta(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ContactEffectiveMassTerms3D&,FixedMathSharp.Vector3d&)
Zero(FixedMathSharp.Fixed64)
ZeroVelocityDelta(FixedMathSharp.Fixed64)
CanUseFastPointVelocity(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
CanUseFastAngularResponse(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed3x3)
TryGetRelativePointVelocity(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&)
TryCross(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&)
TryDot(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64&)
TryTransformDirection(FixedMathSharp.Fixed3x3,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&)
TryLinearCombination(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d&)
TryScale(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d&)
PreservesNonzeroCrossProduct(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
PreservesNonzeroDotProduct(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64)
PreservesNonzeroTransformDirection(FixedMathSharp.Fixed3x3,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
PreservesNonzeroLinearCombination(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d)
PreservesExactValue(System.Boolean,FixedMathSharp.Signed192)
HasSafeProductInputs(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
HasSafeProductInputs(FixedMathSharp.Fixed3x3,FixedMathSharp.Vector3d)
HasSafeProductInputs(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64)
HasSafeProductInputs(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64)
HasNonzeroTerm(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
HasNonzeroDifferenceTerm(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
GetLinearCombinationComponent(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
IsSafeMagnitude(System.UInt64,System.Int32)
GetAggregateMagnitude(FixedMathSharp.Vector3d)
GetAggregateMagnitude(FixedMathSharp.Fixed3x3)
GetRawMagnitude(FixedMathSharp.Fixed64)
TryGetNormalResponse(Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.CollisionHandling.ExactLever3D&,Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.CollisionHandling.ExactLever3D&,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ExactNormalResponse3D&)
TryGetAccumulatedNormalResponse(Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.CollisionHandling.ExactLever3D&,Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.CollisionHandling.ExactLever3D&,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ExactNormalResponse3D&)
CreateResponseOperand(Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.CollisionHandling.ExactLever3D&,FixedMathSharp.Vector3d)
TryGetAngularVelocityDelta(Gravitas.SolidBody,Gravitas.CollisionHandling.ExactLever3D&,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&)
TryGetImpulseCombinationVelocityDeltas(Gravitas.SolidBody,Gravitas.CollisionHandling.ExactLever3D&,Gravitas.SolidBody,Gravitas.CollisionHandling.ExactLever3D&,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d&,FixedMathSharp.Vector3d&,FixedMathSharp.Vector3d&,FixedMathSharp.Vector3d&)
TryGetLinearCombinationVelocityDelta(Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d&)
TryGetAngularCombinationVelocityDelta(Gravitas.SolidBody,Gravitas.CollisionHandling.ExactLever3D&,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d&)
.ctor(Gravitas.SolidBody,FixedMathSharp.Fixed64)
get_HasSolverMobility()
get_CanRotate()
GetConstrainedInverseMass(FixedMathSharp.Vector3d)
Create(Gravitas.Colliders.LSCollider)
Create(Gravitas.SolidBody)
.ctor(FixedMathSharp.Vector3d,System.Boolean)
Create(Gravitas.CollisionHandling.ContactAnchor&,Gravitas.CollisionHandling.ContactAnchor&)
.ctor(System.Int32,System.UInt64,Gravitas.CollisionHandling.ResponseBody,Gravitas.CollisionHandling.ResponseBody,Gravitas.CollisionHandling.ContactLever3D,Gravitas.CollisionHandling.ContactLever3D,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,Gravitas.Materials.PhysicsMaterial,Gravitas.Materials.PhysicsMaterial,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
CreateTangent(FixedMathSharp.Vector3d)
Add(Gravitas.CollisionHandling.SolverContact)
GetContact(System.Int32)
GetNormalImpulse(System.Int32)
GetNormalResult(System.Int32)
GetTangentImpulse(System.Int32)
GetSecondaryTangentImpulse(System.Int32)
SetNormalImpulse(System.Int32,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ContactNormalImpulseResult3D)
SetTangentImpulse(System.Int32,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
TryGetCoulombLineResponse(Gravitas.CollisionHandling.ExactNormalConstraint3D&,Gravitas.CollisionHandling.ExactContactResponseOperand3D&,Gravitas.CollisionHandling.ExactContactResponseOperand3D&,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ExactCoulombResponse3D&)
TryGetCoulombDiskResponse(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ExactContactResponseOperand3D&,Gravitas.CollisionHandling.ExactContactResponseOperand3D&,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ExactContactResponseOperand3D&,Gravitas.CollisionHandling.ExactContactResponseOperand3D&,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ExactCoulombResponse3D&)
TryGetCoulombDiskResponse(Gravitas.CollisionHandling.ExactNormalConstraint3D&,Gravitas.CollisionHandling.ExactContactResponseOperand3D&,Gravitas.CollisionHandling.ExactContactResponseOperand3D&,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ExactContactResponseOperand3D&,Gravitas.CollisionHandling.ExactContactResponseOperand3D&,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ExactCoulombResponse3D&)
TryGetCoulombDiskResponseCore(System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,Gravitas.CollisionHandling.ExactContactResponseOperand3D&,Gravitas.CollisionHandling.ExactContactResponseOperand3D&,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ExactContactResponseOperand3D&,Gravitas.CollisionHandling.ExactContactResponseOperand3D&,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ExactCoulombResponse3D&)
AreCoulombDiskInputsValid(FixedMathSharp.Vector3d,Gravitas.CollisionHandling.ExactContactResponseOperand3D&,Gravitas.CollisionHandling.ExactContactResponseOperand3D&,FixedMathSharp.Vector3d,Gravitas.CollisionHandling.ExactContactResponseOperand3D&,Gravitas.CollisionHandling.ExactContactResponseOperand3D&,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
TryGetCompletedNormalAccumulatorRatio(Gravitas.CollisionHandling.ExactNormalConstraint3D&,System.Span`1<System.UInt64>,System.Span`1<System.UInt64>)
GetBilateralImpulseRatio(Gravitas.CollisionHandling.ExactContactResponseOperand3D&,Gravitas.CollisionHandling.ExactContactResponseOperand3D&,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,System.Span`1<System.UInt64>,System.Span`1<System.UInt64>,System.Int32&)
GetFrictionLimit(System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,FixedMathSharp.Fixed64,System.Span`1<System.UInt64>,System.Span`1<System.UInt64>)
AddFixedToRatio(System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.Int32,FixedMathSharp.Fixed64,System.Span`1<System.UInt64>,System.Int32&)
SubtractFixedFromRatio(System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.Int32,FixedMathSharp.Fixed64,System.Span`1<System.UInt64>,System.Span`1<System.UInt64>,System.Int32&)
CompareRatios(System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>)
IsVectorWithinLimit(System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>)
AddSignedMagnitudes(System.ReadOnlySpan`1<System.UInt64>,System.Int32,System.ReadOnlySpan`1<System.UInt64>,System.Int32,System.Span`1<System.UInt64>,System.Int32&)
TryGetSignedRatioOverSquareRoot(System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.Boolean,FixedMathSharp.Fixed64&)
CompareRatioOverSquareRoot(System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.UInt64)
CompareRatioOverSquareRoot(System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.UInt64)
HaveMatchingParticipants(Gravitas.CollisionHandling.ExactContactResponseOperand3D&,Gravitas.CollisionHandling.ExactContactResponseOperand3D&)
IsRadialProjectionEqualToFixed(System.ReadOnlySpan`1<System.UInt64>,System.Int32,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,FixedMathSharp.Fixed64)
TryGetSignedRadicalAndRationalSum(System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.Int32,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.Int32,FixedMathSharp.Fixed64&)
CompareSignedRadicalAndRationalSumToMagnitude(System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.Int32,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.Int32,System.UInt64)
CompareSignedRadicalAndRationalSumToMagnitude(System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.Int32,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.Int32,System.ReadOnlySpan`1<System.UInt64>,System.UInt64)
GetWeightedFixedSum(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Span`1<System.UInt64>,System.Int32&)
GetWeightedSignedWideSum(FixedMathSharp.Signed832,FixedMathSharp.Fixed64,FixedMathSharp.Signed832,FixedMathSharp.Fixed64,System.Span`1<System.UInt64>,System.Int32&)
GetProductSign(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
TrimMagnitude(System.ReadOnlySpan`1<System.UInt64>)
TryGetDiskVelocityDeltas(Gravitas.CollisionHandling.ExactContactResponseOperand3D&,Gravitas.CollisionHandling.ExactContactResponseOperand3D&,Gravitas.CollisionHandling.ExactContactResponseOperand3D&,Gravitas.CollisionHandling.ExactContactResponseOperand3D&,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,System.ReadOnlySpan`1<System.UInt64>,System.Int32,System.ReadOnlySpan`1<System.UInt64>,System.Int32,System.ReadOnlySpan`1<System.UInt64>,System.Boolean,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d&,FixedMathSharp.Vector3d&,FixedMathSharp.Vector3d&,FixedMathSharp.Vector3d&)
TryGetDiskLinearVelocityDelta(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,System.ReadOnlySpan`1<System.UInt64>,System.Int32,System.ReadOnlySpan`1<System.UInt64>,System.Int32,System.ReadOnlySpan`1<System.UInt64>,System.Boolean,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d&)
TryGetDiskLinearComponent(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.ReadOnlySpan`1<System.UInt64>,System.Int32,System.ReadOnlySpan`1<System.UInt64>,System.Int32,System.ReadOnlySpan`1<System.UInt64>,System.Boolean,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
TryGetDiskAngularVelocityDelta(Gravitas.CollisionHandling.ExactLever3D&,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed3x3,System.ReadOnlySpan`1<System.UInt64>,System.Int32,System.ReadOnlySpan`1<System.UInt64>,System.Int32,System.ReadOnlySpan`1<System.UInt64>,System.Boolean,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d&)
TryGetDiskAngularComponent(FixedMathSharp.Signed832,FixedMathSharp.Signed832,FixedMathSharp.Signed704,System.ReadOnlySpan`1<System.UInt64>,System.Int32,System.ReadOnlySpan`1<System.UInt64>,System.Int32,System.ReadOnlySpan`1<System.UInt64>,System.Boolean,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
GetWeightedSignedSum(FixedMathSharp.Fixed64,System.ReadOnlySpan`1<System.UInt64>,System.Int32,FixedMathSharp.Fixed64,System.ReadOnlySpan`1<System.UInt64>,System.Int32,System.Span`1<System.UInt64>,System.Int32&)
TryGetNormalResponse(Gravitas.CollisionHandling.ExactContactResponseOperand3D&,Gravitas.CollisionHandling.ExactContactResponseOperand3D&,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ExactNormalResponse3D&)
TryGetAccumulatedNormalResponse(Gravitas.CollisionHandling.ExactContactResponseOperand3D&,Gravitas.CollisionHandling.ExactContactResponseOperand3D&,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ExactNormalResponse3D&)
TryGetNormalResponseCore(Gravitas.CollisionHandling.ExactContactResponseOperand3D&,Gravitas.CollisionHandling.ExactContactResponseOperand3D&,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Boolean,Gravitas.CollisionHandling.ExactNormalResponse3D&)
CreateZeroResponse(System.Boolean,System.Boolean,FixedMathSharp.Fixed64,System.Boolean,FixedMathSharp.Fixed64)
GetEffectiveMassRatio(Gravitas.CollisionHandling.ExactContactResponseOperand3D&,Gravitas.CollisionHandling.ExactContactResponseOperand3D&,FixedMathSharp.Vector3d,System.Span`1<System.UInt64>,System.Span`1<System.UInt64>)
BuildImpulseRatio(FixedMathSharp.Signed832,FixedMathSharp.Signed832,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Span`1<System.UInt64>,System.Span`1<System.UInt64>)
ResolveUnilateralImpulse(System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.Int32,FixedMathSharp.Fixed64,System.Boolean,System.Span`1<System.UInt64>,System.Span`1<System.UInt64>,System.Int32&,System.Boolean&,System.Boolean&,FixedMathSharp.Fixed64&)
TryGetLinearVelocityDelta(FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.Int32,FixedMathSharp.Vector3d&)
TryGetLinearComponent(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.Int32,FixedMathSharp.Fixed64&)
TryGetAngularVelocityDelta(Gravitas.CollisionHandling.ExactLever3D&,FixedMathSharp.Vector3d,FixedMathSharp.Fixed3x3,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.Int32,FixedMathSharp.Vector3d&)
TryGetAngularComponent(FixedMathSharp.Signed832,FixedMathSharp.Signed704,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.Int32,FixedMathSharp.Fixed64&)
GetRawDot(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
SetNonNegativeRatio(FixedMathSharp.Signed832,FixedMathSharp.Signed832,System.Span`1<System.UInt64>,System.Span`1<System.UInt64>)
SetUnsignedSum(System.UInt64,System.UInt64,System.Span`1<System.UInt64>)
SetMagnitude(FixedMathSharp.Fixed64,System.Span`1<System.UInt64>)
SetMagnitude(FixedMathSharp.Signed192,System.Span`1<System.UInt64>)
SetMagnitude(FixedMathSharp.Signed320,System.Span`1<System.UInt64>)
SetMagnitude(FixedMathSharp.Signed704,System.Span`1<System.UInt64>)
SetMagnitude(FixedMathSharp.Signed832,System.Span`1<System.UInt64>)
Multiply3(System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.Span`1<System.UInt64>)
Multiply4(System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.ReadOnlySpan`1<System.UInt64>,System.Span`1<System.UInt64>)
.ctor(Gravitas.CollisionHandling.ExactLever3D&,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed3x3)
.ctor(System.Boolean,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,System.Boolean,FixedMathSharp.Fixed64,System.Boolean,FixedMathSharp.Fixed64)
TryGetPrimaryAccumulatedImpulse(FixedMathSharp.Fixed64&)
TryGetSecondaryAccumulatedImpulse(FixedMathSharp.Fixed64&)
GetRelativePointVelocityRatio(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.CollisionHandling.ExactLever3D&,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.CollisionHandling.ExactLever3D&,FixedMathSharp.Vector3d,FixedMathSharp.Signed832&,FixedMathSharp.Signed832&)
GetCrossProductQuadraticFormRatio(Gravitas.CollisionHandling.ExactLever3D&,FixedMathSharp.Vector3d,FixedMathSharp.Fixed3x3,FixedMathSharp.Signed832&,FixedMathSharp.Signed832&)
TryGetTransformedScaledCrossProduct(Gravitas.CollisionHandling.ExactLever3D&,FixedMathSharp.Vector3d,FixedMathSharp.Fixed3x3,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d&)
TryGetTransformedWeightedCrossProduct(Gravitas.CollisionHandling.ExactLever3D&,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed3x3,FixedMathSharp.Vector3d&)
GetTransformedCrossProduct(Gravitas.CollisionHandling.ExactLever3D&,FixedMathSharp.Vector3d,FixedMathSharp.Fixed3x3,FixedMathSharp.Signed832&,FixedMathSharp.Signed832&,FixedMathSharp.Signed832&)
TryGetScaledVector(FixedMathSharp.Signed832,FixedMathSharp.Signed832,FixedMathSharp.Signed832,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Signed832,FixedMathSharp.Vector3d&)
GetRawCrossProduct(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Signed320&,FixedMathSharp.Signed320&,FixedMathSharp.Signed320&)
GetCrossProductProjectionNumerator(Gravitas.CollisionHandling.ExactLever3D&,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
GetCrossProductNumerators(FixedMathSharp.Signed576,FixedMathSharp.Signed576,FixedMathSharp.Signed576,FixedMathSharp.Vector3d,FixedMathSharp.Signed704&,FixedMathSharp.Signed704&,FixedMathSharp.Signed704&)
TransformCrossProduct(FixedMathSharp.Signed704,FixedMathSharp.Signed704,FixedMathSharp.Signed704,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
GetTransformedCrossProductDenominator(Gravitas.CollisionHandling.ExactLever3D&,FixedMathSharp.Signed192)
TryGetScaledComponent(FixedMathSharp.Signed832,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Signed832,FixedMathSharp.Fixed64&)
TryGetWeightedComponent(FixedMathSharp.Signed832,FixedMathSharp.Fixed64,FixedMathSharp.Signed832,FixedMathSharp.Fixed64,FixedMathSharp.Signed832,FixedMathSharp.Fixed64,FixedMathSharp.Signed832,FixedMathSharp.Fixed64&)
GetQuadraticTerm(FixedMathSharp.Signed704,FixedMathSharp.Signed704,FixedMathSharp.Fixed64)
.ctor(FixedMathSharp.Signed576,FixedMathSharp.Signed576,FixedMathSharp.Signed576,FixedMathSharp.Signed576)
Create(FixedMathSharp.Geometry.FixedPointAnchor&,FixedMathSharp.Geometry.FixedPointAnchor&)
CreateXZ(FixedMathSharp.Geometry.FixedPointAnchor2d&,FixedMathSharp.Geometry.FixedPointAnchor2d&)
.ctor(Gravitas.CollisionHandling.ExactContactResponseOperand3D&,Gravitas.CollisionHandling.ExactContactResponseOperand3D&,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
.ctor(System.Boolean,System.Boolean,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,System.Boolean,FixedMathSharp.Fixed64,System.Boolean,FixedMathSharp.Fixed64,System.Boolean,FixedMathSharp.Fixed64)
TryGetNormalVelocity(FixedMathSharp.Fixed64&)
TryGetAppliedImpulse(FixedMathSharp.Fixed64&)
TryGetAccumulatedImpulse(FixedMathSharp.Fixed64&)
.cctor()
Resolve(Gravitas.CollisionHandling.CollisionPairMixed,Gravitas.CollisionHandling.MixedContact)
Resolve(Gravitas.CollisionHandling.CollisionPairMixed,Gravitas.CollisionHandling.MixedContact,System.Int32,System.Int32,System.Boolean)
HasPlanarResponseCoupling(Gravitas.CollisionHandling.CollisionPairMixed,Gravitas.CollisionHandling.MixedContact)
ApplyPositionCorrection(Gravitas.SolidBody,Gravitas.SolidBody2D,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
TryGetNormalImpulse(Gravitas.CollisionHandling.CollisionPairMixed,Gravitas.CollisionHandling.MixedContact,Gravitas.SolidBody,Gravitas.SolidBody2D,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector2d,System.Boolean,System.Boolean,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ContactNormalImpulseResultMixed&)
CanApplyNormalVelocityDeltas(Gravitas.SolidBody,Gravitas.SolidBody2D,Gravitas.CollisionHandling.ContactNormalImpulseResultMixed)
HasVelocityDelta(Gravitas.CollisionHandling.ContactNormalImpulseResultMixed)
ApplyNormalVelocityDeltas(Gravitas.SolidBody,Gravitas.SolidBody2D,Gravitas.CollisionHandling.ContactNormalImpulseResultMixed)
CanApplyVelocityDeltas(Gravitas.SolidBody,Gravitas.SolidBody2D,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64)
ApplyVelocityDeltas(Gravitas.SolidBody,Gravitas.SolidBody2D,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64)
TryApplyFrictionImpulse(Gravitas.CollisionHandling.CollisionPairMixed,Gravitas.CollisionHandling.MixedContact,Gravitas.SolidBody,Gravitas.SolidBody2D,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector2d,Gravitas.Materials.PhysicsMaterial,Gravitas.Materials.PhysicsMaterial,Gravitas.CollisionHandling.ContactNormalImpulseResultMixed,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Boolean,System.Boolean,System.Boolean,System.Boolean&)
TryGetCompactFrictionImpulse(Gravitas.SolidBody,Gravitas.SolidBody2D,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Boolean,FixedMathSharp.Vector3d&,FixedMathSharp.Fixed64&)
TryApplyFrictionImpulseExact(Gravitas.CollisionHandling.CollisionPairMixed,Gravitas.CollisionHandling.MixedContact,Gravitas.SolidBody,Gravitas.SolidBody2D,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.Boolean,System.Boolean&)
TryApplyImpulse(Gravitas.SolidBody,Gravitas.SolidBody2D,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,System.Boolean)
TryGetCompactTangent(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d&,System.Boolean&)
TryGetCompactConstrainedInverseMass(Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64&)
TryGetCompactConstrainedPlanarInverseMass(Gravitas.SolidBody2D,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64&)
ResolveLinearVelocity(Gravitas.SolidBody)
ResolveLinearVelocity(Gravitas.SolidBody2D)
ResolveAngularVelocity(Gravitas.SolidBody)
ResolveAngularVelocity(Gravitas.SolidBody2D)
ResolveNormal(Gravitas.CollisionHandling.CollisionPairMixed,Gravitas.CollisionHandling.MixedContact)
ResolveFallbackDirection(Gravitas.CollisionHandling.CollisionPairMixed,Gravitas.CollisionHandling.MixedContact)
GetExactLevers(Gravitas.CollisionHandling.CollisionPairMixed,Gravitas.CollisionHandling.MixedContact,Gravitas.SolidBody,Gravitas.SolidBody2D,Gravitas.CollisionHandling.ExactLever3D&,Gravitas.CollisionHandling.ExactLever3D&)
GetConstrainedInverseMass(Gravitas.SolidBody,FixedMathSharp.Vector3d)
GetConstrainedPlanarInverseMass(Gravitas.SolidBody2D,FixedMathSharp.Vector3d)
.ctor(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64)
.ctor(FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,System.Boolean,System.Boolean)
.ctor(FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64)
.ctor(FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,System.Boolean,System.Boolean)
CanUseCompactResponse(Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Vector3d)
TryCalculateVelocityDeltas(Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ContactNormalVelocityDeltaResultMixed&)
TryCalculateAccumulatedDelta(Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ContactNormalImpulseResultMixed&)
TryCalculateVelocityDeltasExact(Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.CollisionHandling.ExactLever3D&,Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ExactLever3D&,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ContactNormalVelocityDeltaResultMixed&)
TryCalculateAccumulatedDeltaExact(Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Gravitas.CollisionHandling.ExactLever3D&,Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ExactLever3D&,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,Gravitas.CollisionHandling.ContactNormalImpulseResultMixed&)
TryComputeNormalVelocity(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Vector2d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64&)
TryComputeDenominator(Gravitas.SolidBody,FixedMathSharp.Vector3d,Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64&)
GetConstrainedInverseMass3D(Gravitas.SolidBody,FixedMathSharp.Vector3d)
GetConstrainedInverseMass2D(Gravitas.SolidBody2D,FixedMathSharp.Vector3d)
TryResolveAngularVelocityDelta3D(Gravitas.SolidBody,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d&)
TryResolveAngularVelocityDelta2D(Gravitas.SolidBody2D,FixedMathSharp.Vector2d,FixedMathSharp.Vector2d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
ZeroImpulse(FixedMathSharp.Fixed64)
ZeroVelocityDelta(FixedMathSharp.Fixed64)