< Summary

Information
Class: Gravitas.CollisionHandling.MixedEmbedded2DGeometry
Assembly: Gravitas
File(s): /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Detection/Mixed/MixedEmbedded2DGeometry.cs
Line coverage
100%
Covered lines: 173
Uncovered lines: 0
Coverable lines: 173
Total lines: 313
Line coverage: 100%
Branch coverage
100%
Covered branches: 46
Total branches: 46
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/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}