< Summary

Information
Class: Gravitas.CollisionHandling.ConvexColliderSupport
Assembly: Gravitas
File(s): /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/3D/ConvexColliderSupport.cs
Line coverage
100%
Covered lines: 162
Uncovered lines: 0
Coverable lines: 162
Total lines: 274
Line coverage: 100%
Branch coverage
100%
Covered branches: 78
Total branches: 78
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
GetSupportAnchor(...)100%1414100%
IsSupported(...)100%1414100%
Intersects(...)100%2020100%
IntersectsConeVolume(...)100%1818100%
TrySupportMinkowski(...)100%44100%
TrySupportMinkowskiConeCollider(...)100%44100%
ResolveSupportDirection(...)100%22100%
GetLocalDisplacement(...)100%22100%

File(s)

/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}