< Summary

Information
Class: Gravitas.Colliders.ColliderCanonicalBounds
Assembly: Gravitas
File(s): /home/runner/work/Gravitas/Gravitas/src/Gravitas/Colliders/ColliderCanonicalBounds.cs
Line coverage
100%
Covered lines: 268
Uncovered lines: 0
Coverable lines: 268
Total lines: 424
Line coverage: 100%
Branch coverage
100%
Covered branches: 82
Total branches: 82
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Colliders/ColliderCanonicalBounds.cs

#LineLine coverage
 1//=======================================================================
 2// ColliderCanonicalBounds.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.Colliders;
 12
 13/// <summary>
 14/// Contains methods for computing canonical bounds of colliders,
 15/// including proxy radii and relative bounds, based on collider type and properties.
 16/// </summary>
 17internal static class ColliderCanonicalBounds
 18{
 19    internal static Fixed64 GetCurrentCenteredProxyRadius(
 20        LSCollider collider)
 21    {
 22        switch (collider)
 23        {
 24            case LSSphereCollider:
 225                return collider.GetCurrentScaledRadius();
 26            case LSCapsuleCollider capsule:
 227                return GetCurrentCapsuleCenteredProxyRadius(capsule);
 28            case LSCuboidCollider cuboid:
 329                return GetCurrentCuboidCenteredProxyRadius(cuboid);
 30            case LSCylinderCollider cylinder:
 231                return GetCurrentFiniteAxisCenteredProxyRadius(
 232                    cylinder,
 233                    cylinder.Size.Y);
 34            case LSConeCollider cone:
 135                return GetCurrentFiniteAxisCenteredProxyRadius(
 136                    cone,
 137                    cone.Size.Y);
 38            case LSMeshCollider mesh:
 39                {
 240                    mesh.GetCurrentShapeScales(
 241                        out Vector3d ownerScale,
 242                        out Vector3d partScale);
 243                    return mesh.Mesh.GetScaledLocalRadius(
 244                        ownerScale,
 245                        partScale);
 46                }
 47            case LSCompoundCollider compound:
 648                return GetCurrentCompoundCenteredProxyRadius(
 649                    compound);
 50            default:
 151                return Fixed64.MaxValue;
 52        }
 53    }
 54
 55    internal static FixedBoundBox GetRelativeBounds(
 56        LSCollider collider,
 57        Vector3d referenceOrigin,
 58        FixedQuaternion referenceRotation = default)
 59    {
 840560        if (referenceRotation == default)
 808661            referenceRotation = FixedQuaternion.Identity;
 840562        if (collider is LSCompoundCollider compound)
 63        {
 16564            return GetCompoundRelativeBounds(
 16565                compound,
 16566                referenceOrigin,
 16567                referenceRotation);
 68        }
 824069        if (!TryGetLocalBounds(
 824070            collider,
 824071            out Vector3d localMin,
 824072            out Vector3d localMax))
 73        {
 174            return FixedBoundBox.FromMinMax(
 175                new Vector3d(
 176                    Fixed64.MinValue,
 177                    Fixed64.MinValue,
 178                    Fixed64.MinValue),
 179                new Vector3d(
 180                    Fixed64.MaxValue,
 181                    Fixed64.MaxValue,
 182                    Fixed64.MaxValue));
 83        }
 823984        Vector3d sourceOrigin = collider is LSMeshCollider mesh
 823985            ? mesh.Mesh.Origin
 823986            : collider.CanonicalCenter;
 823987        FixedQuaternion sourceRotation = collider is LSMeshCollider meshCollider
 823988            ? meshCollider.Mesh.Rotation
 823989            : collider.CanonicalRotation;
 823990        return FixedBoundBox.FromRelativeRotatedBoundsClippedToDomain(
 823991            sourceOrigin,
 823992            sourceRotation,
 823993            localMin,
 823994            localMax,
 823995            referenceOrigin,
 823996            referenceRotation);
 97    }
 98
 99    internal static Fixed64 GetCenteredProxyRadius(
 100        LSCollider collider)
 101    {
 102        switch (collider)
 103        {
 104            case LSSphereCollider sphere:
 21759105                return sphere.ScaledRadius;
 106            case LSCapsuleCollider capsule:
 107                {
 575108                    FixedBoundBox localBounds =
 575109                        FixedBoundBox.FromCenteredCapsuleClippedToDomain(
 575110                            Vector3d.Zero,
 575111                            Vector3d.Up,
 575112                            capsule.AxisLength,
 575113                            capsule.ScaledRadius);
 575114                    return localBounds.Max.Y;
 115                }
 116            case LSCuboidCollider cuboid:
 7920117                return cuboid.OrientedBox.HalfExtents
 7920118                    .TryGetMagnitudeCeiling(
 7920119                        out Fixed64 cuboidRadius)
 7920120                    ? cuboidRadius
 7920121                    : Fixed64.MaxValue;
 122            case LSCylinderCollider cylinder:
 578123                return GetFiniteAxisLocalRadius(
 578124                    FixedBoundBox.FromCenteredFiniteCylinderClippedToDomain(
 578125                        Vector3d.Zero,
 578126                        Vector3d.Up,
 578127                        cylinder.Height,
 578128                        cylinder.ScaledRadius));
 129            case LSConeCollider cone:
 530130                return GetFiniteAxisLocalRadius(
 530131                    FixedBoundBox.FromCenteredFiniteConeClippedToDomain(
 530132                        Vector3d.Zero,
 530133                        Vector3d.Up,
 530134                        cone.Height,
 530135                        cone.ScaledRadius));
 136            case LSMeshCollider mesh:
 284137                return mesh.Mesh.ScaledLocalRadius;
 138            case LSCompoundCollider compound:
 165139                return GetCompoundCenteredProxyRadius(compound);
 140            default:
 69141                return Fixed64.MaxValue;
 142        }
 143    }
 144
 145    internal static Fixed64 GetGroundProbeRadius(
 146        LSCollider collider)
 147    {
 31878148        if (collider is LSSphereCollider sphere)
 21759149            return sphere.ScaledRadius;
 10119150        if (collider is LSCapsuleCollider capsule)
 575151            return capsule.ScaledRadius;
 9544152        if (collider is LSCylinderCollider cylinder)
 578153            return cylinder.ScaledRadius;
 8966154        if (collider is not (LSCuboidCollider or LSCompoundCollider))
 882155            return Fixed64.Zero;
 156
 8084157        Vector3d extents = GetMaximumAbsoluteExtents(
 8084158            collider,
 8084159            collider.CanonicalCenter);
 8084160        return FixedMath.Min(extents.X, extents.Z);
 161    }
 162
 163    internal static Vector3d GetMaximumAbsoluteExtents(
 164        LSCollider collider,
 165        Vector3d referenceOrigin)
 166    {
 8085167        FixedBoundBox bounds = GetRelativeBounds(
 8085168            collider,
 8085169            referenceOrigin);
 8085170        return new Vector3d(
 8085171            FixedMath.Max(bounds.Min.X.Abs(), bounds.Max.X.Abs()),
 8085172            FixedMath.Max(bounds.Min.Y.Abs(), bounds.Max.Y.Abs()),
 8085173            FixedMath.Max(bounds.Min.Z.Abs(), bounds.Max.Z.Abs()));
 174    }
 175
 176    private static FixedBoundBox GetCompoundRelativeBounds(
 177        LSCompoundCollider compound,
 178        Vector3d referenceOrigin,
 179        FixedQuaternion referenceRotation)
 180    {
 165181        FixedBoundBox bounds = GetRelativeBounds(
 165182            compound.GetPartCollider(0),
 165183            referenceOrigin,
 165184            referenceRotation);
 165185        Vector3d minimum = bounds.Min;
 165186        Vector3d maximum = bounds.Max;
 614187        for (int index = 1; index < compound.PartCount; index++)
 188        {
 142189            bounds = GetRelativeBounds(
 142190                compound.GetPartCollider(index),
 142191                referenceOrigin,
 142192                referenceRotation);
 142193            minimum = Vector3d.Min(minimum, bounds.Min);
 142194            maximum = Vector3d.Max(maximum, bounds.Max);
 195        }
 196
 165197        return FixedBoundBox.FromMinMax(minimum, maximum);
 198    }
 199
 200    private static bool TryGetLocalBounds(
 201        LSCollider collider,
 202        out Vector3d minimum,
 203        out Vector3d maximum)
 204    {
 205        Vector3d halfExtents;
 206        switch (collider)
 207        {
 208            case LSSphereCollider sphere:
 162209                halfExtents = Vector3d.One * sphere.ScaledRadius;
 162210                break;
 211            case LSCapsuleCollider capsule:
 212                {
 23213                    FixedBoundBox bounds =
 23214                        FixedBoundBox.FromCenteredCapsuleClippedToDomain(
 23215                            Vector3d.Zero,
 23216                            Vector3d.Up,
 23217                            capsule.AxisLength,
 23218                            capsule.ScaledRadius);
 23219                    minimum = bounds.Min;
 23220                    maximum = bounds.Max;
 23221                    return true;
 222                }
 223            case LSCuboidCollider cuboid:
 7965224                halfExtents = cuboid.OrientedBox.HalfExtents;
 7965225                break;
 226            case LSCylinderCollider cylinder:
 227                {
 23228                    FixedBoundBox bounds =
 23229                        FixedBoundBox.FromCenteredFiniteCylinderClippedToDomain(
 23230                            Vector3d.Zero,
 23231                            Vector3d.Up,
 23232                            cylinder.Height,
 23233                            cylinder.ScaledRadius);
 23234                    minimum = bounds.Min;
 23235                    maximum = bounds.Max;
 23236                    return true;
 237                }
 238            case LSConeCollider cone:
 239                {
 27240                    FixedBoundBox bounds =
 27241                        FixedBoundBox.FromCenteredFiniteConeClippedToDomain(
 27242                            Vector3d.Zero,
 27243                            Vector3d.Up,
 27244                            cone.Height,
 27245                            cone.ScaledRadius);
 27246                    minimum = bounds.Min;
 27247                    maximum = bounds.Max;
 27248                    return true;
 249                }
 250            case LSMeshCollider mesh:
 39251                minimum = mesh.Mesh.ScaledLocalBounds.Min;
 39252                maximum = mesh.Mesh.ScaledLocalBounds.Max;
 39253                return true;
 254            default:
 1255                minimum = default;
 1256                maximum = default;
 1257                return false;
 258        }
 259
 8127260        minimum = -halfExtents;
 8127261        maximum = halfExtents;
 8127262        return true;
 263    }
 264
 265    private static Fixed64 GetFiniteAxisLocalRadius(
 266        FixedBoundBox localBounds)
 267    {
 1111268        Vector3d extents = new(
 1111269            FixedMath.Max(localBounds.Min.X.Abs(), localBounds.Max.X.Abs()),
 1111270            FixedMath.Max(localBounds.Min.Y.Abs(), localBounds.Max.Y.Abs()),
 1111271            FixedMath.Max(localBounds.Min.Z.Abs(), localBounds.Max.Z.Abs()));
 1111272        return extents.TryGetMagnitudeCeiling(
 1111273            out Fixed64 radius)
 1111274            ? radius
 1111275            : Fixed64.MaxValue;
 276    }
 277
 278    private static Fixed64 GetCurrentCuboidCenteredProxyRadius(
 279        LSCuboidCollider cuboid)
 280    {
 3281        cuboid.GetCurrentShapeScales(
 3282            out Vector3d ownerScale,
 3283            out Vector3d partScale);
 3284        Vector3d halfExtents =
 3285            ColliderScalePolicy.ScalePositive(
 3286                cuboid.Size,
 3287                ownerScale,
 3288                partScale,
 3289                Fixed64.Two);
 3290        return halfExtents.TryGetMagnitudeCeiling(
 3291            out Fixed64 radius)
 3292            ? radius
 3293            : Fixed64.MaxValue;
 294    }
 295
 296    private static Fixed64 GetCurrentCapsuleCenteredProxyRadius(
 297        LSCapsuleCollider capsule)
 298    {
 2299        capsule.GetCurrentShapeScales(
 2300            out Vector3d ownerScale,
 2301            out Vector3d partScale);
 2302        Fixed64 radiusX = ColliderScalePolicy.ScalePositive(
 2303            capsule.Radius,
 2304            ownerScale.X,
 2305            partScale.X);
 2306        Fixed64 radiusZ = ColliderScalePolicy.ScalePositive(
 2307            capsule.Radius,
 2308            ownerScale.Z,
 2309            partScale.Z);
 2310        if (!Fixed64.TryMultiplySubtractClamped(
 2311                capsule.Size.Y,
 2312                ownerScale.Y,
 2313                partScale.Y,
 2314                Fixed64.One,
 2315                capsule.Radius,
 2316                Fixed64.Two,
 2317                ownerScale.X,
 2318                partScale.X,
 2319                out Fixed64 axisLengthX)
 2320            || !Fixed64.TryMultiplySubtractClamped(
 2321                capsule.Size.Y,
 2322                ownerScale.Y,
 2323                partScale.Y,
 2324                Fixed64.One,
 2325                capsule.Radius,
 2326                Fixed64.Two,
 2327                ownerScale.Z,
 2328                partScale.Z,
 2329                out Fixed64 axisLengthZ))
 330        {
 1331            return Fixed64.MaxValue;
 332        }
 333
 1334        FixedBoundBox localBounds =
 1335            FixedBoundBox.FromCenteredCapsuleClippedToDomain(
 1336                Vector3d.Zero,
 1337                Vector3d.Up,
 1338                FixedMath.Min(axisLengthX, axisLengthZ),
 1339                FixedMath.Max(radiusX, radiusZ));
 1340        return localBounds.Max.Y;
 341    }
 342
 343    private static Fixed64 GetCurrentFiniteAxisCenteredProxyRadius(
 344        LSCollider collider,
 345        Fixed64 authoredHeight)
 346    {
 3347        collider.GetCurrentShapeScales(
 3348            out Vector3d ownerScale,
 3349            out Vector3d partScale);
 3350        Fixed64 radiusX = ColliderScalePolicy.ScalePositive(
 3351            collider.Radius,
 3352            ownerScale.X,
 3353            partScale.X);
 3354        Fixed64 radiusZ = ColliderScalePolicy.ScalePositive(
 3355            collider.Radius,
 3356            ownerScale.Z,
 3357            partScale.Z);
 3358        Fixed64 height = ColliderScalePolicy.ScalePositive(
 3359            authoredHeight,
 3360            ownerScale.Y,
 3361            partScale.Y);
 3362        return GetFiniteAxisLocalRadius(
 3363            FixedBoundBox.FromCenteredFiniteCylinderClippedToDomain(
 3364                Vector3d.Zero,
 3365                Vector3d.Up,
 3366                height,
 3367                FixedMath.Max(radiusX, radiusZ)));
 368    }
 369
 370    private static Fixed64 GetCompoundCenteredProxyRadius(
 371        LSCompoundCollider compound)
 372    {
 165373        Fixed64 bestRadius = Fixed64.Zero;
 916374        for (int index = 0; index < compound.PartCount; index++)
 375        {
 307376            LSCollider part = compound.GetPartCollider(index);
 307377            bool offsetResolved = Vector3d.TrySubtract(
 307378                part.CanonicalCenter,
 307379                compound.CanonicalCenter,
 307380                out Vector3d offset);
 307381            bool distanceResolved =
 307382                offset.TryGetMagnitudeCeiling(
 307383                    out Fixed64 distance);
 307384            bool radiusResolved = Fixed64.TryAdd(
 307385                distance,
 307386                part.CanonicalCenteredProxyRadius,
 307387                out Fixed64 radius);
 307388            if (!(offsetResolved & distanceResolved & radiusResolved))
 14389                return Fixed64.MaxValue;
 390
 293391            bestRadius = FixedMath.Max(bestRadius, radius);
 392        }
 393
 151394        return bestRadius;
 395    }
 396
 397    private static Fixed64 GetCurrentCompoundCenteredProxyRadius(
 398        LSCompoundCollider compound)
 399    {
 6400        Fixed64 bestRadius = Fixed64.Zero;
 30401        for (int index = 0; index < compound.PartCount; index++)
 402        {
 10403            LSCollider part = compound.GetPartCollider(index);
 10404            bool distanceResolved =
 10405                part.TryGetCurrentScaledOffset(
 10406                    out Vector3d centerOffset)
 10407                & centerOffset.TryGetMagnitudeCeiling(
 10408                    out Fixed64 distance);
 10409            bool radiusResolved = Fixed64.TryAdd(
 10410                distance,
 10411                GetCurrentCenteredProxyRadius(part),
 10412                out Fixed64 radius);
 10413            if (!(distanceResolved
 10414                & radiusResolved))
 415            {
 1416                return Fixed64.MaxValue;
 417            }
 418
 9419            bestRadius = FixedMath.Max(bestRadius, radius);
 420        }
 421
 5422        return bestRadius;
 423    }
 424}