< Summary

Information
Class: Gravitas.Colliders.LSPolygonCollider2D
Assembly: Gravitas
File(s): /home/runner/work/Gravitas/Gravitas/src/Gravitas/Colliders/2D/LSPolygonCollider2D.cs
Line coverage
100%
Covered lines: 153
Uncovered lines: 0
Coverable lines: 153
Total lines: 299
Line coverage: 100%
Branch coverage
100%
Covered branches: 26
Total branches: 26
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/2D/LSPolygonCollider2D.cs

#LineLine coverage
 1//=======================================================================
 2// LSPolygonCollider2D.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2026–present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using Chronicler;
 9using FixedMathSharp;
 10using FixedMathSharp.Geometry;
 11using Gravitas.CollisionHandling;
 12using System;
 13
 14namespace Gravitas.Colliders;
 15
 16/// <summary>
 17/// Pure 2D convex polygon collider with deterministic vertex ordering.
 18/// </summary>
 19public sealed class LSPolygonCollider2D : LSCollider2D, IConvexVertexSource2D
 20{
 21    private Vector2d[] _localVertices;
 22    private Vector2d[] _scaledLocalVertices;
 23    private Vector2d[] _scaledLocalVerticesScratch;
 24
 25    /// <summary>Creates a pure 2D convex polygon from authored local vertices.</summary>
 62226    public LSPolygonCollider2D(params Vector2d[] vertices)
 27    {
 62228        _localVertices = Array.Empty<Vector2d>();
 62229        _scaledLocalVertices = Array.Empty<Vector2d>();
 62230        _scaledLocalVerticesScratch = Array.Empty<Vector2d>();
 62231        SetLocalVertices(vertices, markDirty: true);
 61832    }
 33
 34    /// <summary>Creates a runtime convex polygon from an authored shape definition.</summary>
 2535    public LSPolygonCollider2D(ColliderShapeDefinition2D definition)
 36    {
 2537        definition.EnsureKind(ColliderShapeDefinition2DKind.ConvexPolygon);
 2538        Material = definition.Material;
 2539        _localVertices = Array.Empty<Vector2d>();
 2540        _scaledLocalVertices = Array.Empty<Vector2d>();
 2541        _scaledLocalVerticesScratch = Array.Empty<Vector2d>();
 2542        SetLocalVertices(definition.GetPolygonVerticesForRuntime(), markDirty: true);
 2543    }
 44
 45    /// <inheritdoc/>
 291846    public override ColliderType2D Shape => ColliderType2D.ConvexPolygon;
 47
 48    /// <summary>Gets the polygon vertex count.</summary>
 1649    public int Count => _scaledLocalVertices.Length;
 50
 51    /// <summary>
 52    /// Gets the committed vertices in the collider's scaled local frame.
 53    /// </summary>
 648654    internal ReadOnlySpan<Vector2d> ScaledLocalVertices => _scaledLocalVertices;
 55
 6008056    int IConvexVertexSource2D.VertexCount => _scaledLocalVertices.Length;
 57
 2582058    Fixed64 IConvexVertexSource2D.Rotation => Rotation;
 59
 60    /// <summary>
 61    /// Gets a world-space vertex when the conceptual point is representable.
 62    /// </summary>
 63    /// <exception cref="InvalidOperationException">
 64    /// Thrown when the conceptual vertex lies outside the fixed-point scalar
 65    /// domain. Use <see cref="TryGetWorldVertex(int, out Vector2d)"/> when
 66    /// querying geometry near a scalar boundary.
 67    /// </exception>
 68    public Vector2d GetWorldVertex(int index)
 69    {
 3070        if (TryGetWorldVertex(index, out Vector2d vertex))
 2871            return vertex;
 72
 273        throw new InvalidOperationException(
 274            "The polygon vertex is outside the representable coordinate range. Use TryGetWorldVertex.");
 75    }
 76
 77    /// <summary>
 78    /// Attempts to materialize a committed polygon vertex in world space
 79    /// without saturation.
 80    /// </summary>
 81    public bool TryGetWorldVertex(int index, out Vector2d vertex)
 82    {
 4083        SwiftThrowHelper.ThrowIfArrayIndexInvalid(
 4084            index,
 4085            _scaledLocalVertices.Length,
 4086            nameof(index));
 4087        return TryGetVertex(index, out vertex);
 88    }
 89
 90    /// <inheritdoc/>
 91    public override bool ContainsPoint(Vector2d point) =>
 112692        FixedConvex2dRelations.ContainsPoint(
 112693            point,
 112694            Center,
 112695            Rotation,
 112696            _scaledLocalVertices);
 97
 98    /// <inheritdoc/>
 99    public override Vector2d GetClosestPoint(Vector2d point)
 100    {
 4101        if (ContainsPoint(point))
 2102            return point;
 103
 2104        FixedPointAnchor2d anchor =
 2105            FixedConvex2dRelations.GetClosestPointAnchor(
 2106                point,
 2107                Center,
 2108                Rotation,
 2109                _scaledLocalVertices);
 2110        if (anchor.TryGetPoint(out Vector2d closest))
 111        {
 1112            return closest;
 113        }
 114
 1115        throw new InvalidOperationException(
 1116            "The closest polygon point is outside the Fixed64 coordinate domain.");
 117    }
 118
 119    /// <inheritdoc/>
 120    public override Vector2d GetSupportPoint(Vector2d direction)
 121    {
 6122        FixedPointAnchor2d anchor = FixedConvex2dRelations.GetSupportAnchor(
 6123            Center,
 6124            Rotation,
 6125            _scaledLocalVertices,
 6126            direction);
 6127        if (anchor.TryGetPoint(out Vector2d support))
 128        {
 4129            return support;
 130        }
 131
 2132        throw new InvalidOperationException(
 2133            "The polygon support point is outside the Fixed64 coordinate domain.");
 134    }
 135
 136    Vector2d IConvexVertexSource2D.GetScaledLocalVertexUnchecked(int index) =>
 44897137        _scaledLocalVertices[index];
 138
 139    FixedPointAnchor2d IConvexVertexSource2D.GetSupportAnchor(Vector2d direction) =>
 49140        FixedConvex2dRelations.GetSupportAnchor(
 49141            Center,
 49142            Rotation,
 49143            _scaledLocalVertices,
 49144            direction);
 145
 146    internal override ExactMassPoint2D CalculateLocalMassPoint()
 147    {
 12001148        _ = TryCalculateIntrinsicSignedAreaAndCentroid(
 12001149            out _,
 12001150            out Vector2d intrinsicCentroid);
 12001151        return TransformRelativeMassPropertyPointExact(intrinsicCentroid);
 152    }
 153
 154    internal override ExactMassPoint2D CalculatePreparedLocalMassPoint()
 155    {
 5448156        _ = PolygonMassProperties2D.TryGetWeightAndCentroid(
 5448157            _scaledLocalVerticesScratch,
 5448158            out _,
 5448159            out Vector2d intrinsicCentroid);
 5448160        return TransformPreparedRelativeMassPropertyPointExact(
 5448161            intrinsicCentroid);
 162    }
 163
 164    internal override ExactMassWeight CalculateAreaForMassProperties()
 165    {
 53166        ReadOnlySpan<Vector2d> vertices = GetMassPropertyVertices();
 53167        _ = PolygonMassProperties2D.TryGetWeightAndCentroid(
 53168            vertices,
 53169            out ExactMassWeight weight,
 53170            out _);
 53171        return weight;
 172    }
 173
 174    internal override ExactMassWeight CalculatePreparedAreaForMassProperties()
 175    {
 49176        _ = PolygonMassProperties2D.TryGetWeightAndCentroid(
 49177            _scaledLocalVerticesScratch,
 49178            out ExactMassWeight weight,
 49179            out _);
 49180        return weight;
 181    }
 182
 183    internal override Fixed64 CalculateCenterOfMassMoment(Fixed64 mass)
 184    {
 6028185        ReadOnlySpan<Vector2d> vertices = GetMassPropertyVertices();
 6028186        _ = FixedConvex2dRelations.TryGetAreaAndCentroid(
 6028187            vertices,
 6028188            out Fixed64 area,
 6028189            out Vector2d intrinsicCenterOfMass);
 6028190        if (area <= Fixed64.Zero)
 4191            return Fixed64.Zero;
 192
 6024193        Fixed64 density = mass / area;
 6024194        Fixed64 centeredIntegral = Fixed64.Zero;
 60180195        for (int i = 0; i < vertices.Length; i++)
 196        {
 24066197            Vector2d a = vertices[i] - intrinsicCenterOfMass;
 24066198            Vector2d b =
 24066199                vertices[(i + 1) % vertices.Length] - intrinsicCenterOfMass;
 24066200            Fixed64 cross = Vector2d.CrossProduct(a, b);
 24066201            Fixed64 term =
 24066202                a.MagnitudeSquared +
 24066203                Vector2d.Dot(a, b) +
 24066204                b.MagnitudeSquared;
 24066205            centeredIntegral += cross * term;
 206        }
 207
 6024208        return (density * centeredIntegral).Abs() / (Fixed64)12;
 209    }
 210
 211    private protected override void PrepareShape(in ColliderShapeSnapshot2D snapshot)
 212    {
 54502213        for (int i = 0; i < _localVertices.Length; i++)
 214        {
 21796215            Vector2d scaledVertex = ColliderScalePolicy.Scale(
 21796216                _localVertices[i],
 21796217                snapshot.OwnerScale,
 21796218                snapshot.PartScale);
 21796219            _scaledLocalVerticesScratch[i] = scaledVertex;
 220        }
 221
 5455222        SetPreparedBounds(FixedBoundArea.FromRotatedOffsetsClippedToDomain(
 5455223            snapshot.Center,
 5455224            snapshot.Rotation,
 5455225            _scaledLocalVerticesScratch));
 5455226    }
 227
 228    private protected override void PublishShape()
 229    {
 5443230        Vector2d[] offsets = _scaledLocalVertices;
 5443231        _scaledLocalVertices = _scaledLocalVerticesScratch;
 5443232        _scaledLocalVerticesScratch = offsets;
 5443233    }
 234
 235    /// <inheritdoc/>
 236    protected override void RecordShapeData(IChronicler chronicler)
 237    {
 6238        Vector2d[] vertices = _localVertices;
 6239        RecordValues.Look(chronicler, ref vertices, "Vertices", Array.Empty<Vector2d>());
 6240        if (chronicler.Mode == SerializationMode.Loading && vertices.Length > 0)
 3241            SetLocalVertices(vertices, markDirty: false);
 6242    }
 243
 244    private void SetLocalVertices(Vector2d[] vertices, bool markDirty)
 245    {
 650246        SwiftThrowHelper.ThrowIfNull(vertices, nameof(vertices));
 650247        SwiftThrowHelper.ThrowIfArgument(vertices.Length < 3, nameof(vertices), "2D polygon must contain at least three 
 649248        ValidateConvexPolygon(vertices);
 249
 646250        if (_localVertices.Length != vertices.Length)
 251        {
 645252            _localVertices = new Vector2d[vertices.Length];
 645253            _scaledLocalVertices = new Vector2d[vertices.Length];
 645254            _scaledLocalVerticesScratch = new Vector2d[vertices.Length];
 255        }
 256
 646257        Array.Copy(vertices, _localVertices, vertices.Length);
 646258        if (markDirty)
 643259            MarkShapeDirty();
 646260    }
 261
 262    internal static void ValidateConvexPolygon(Vector2d[] vertices)
 263    {
 685264        SwiftThrowHelper.ThrowIfArgument(vertices.Length < 3, nameof(vertices), "2D polygon must contain at least three 
 685265        SwiftThrowHelper.ThrowIfArgument(
 685266            !FixedConvex2dRelations.IsStrictlyConvex(vertices),
 685267            nameof(vertices),
 685268            "2D polygon vertices must form a strictly convex boundary.");
 681269    }
 270
 271    private bool TryCalculateIntrinsicSignedAreaAndCentroid(
 272        out Fixed64 signedDoubleArea,
 273        out Vector2d centroid)
 274    {
 12001275        return FixedConvex2dRelations.TryGetAreaAndCentroid(
 12001276            GetMassPropertyVertices(),
 12001277            out signedDoubleArea,
 12001278            out centroid);
 279    }
 280
 281    private ReadOnlySpan<Vector2d> GetMassPropertyVertices()
 282    {
 18082283        if (HasCommittedShape)
 16226284            return _scaledLocalVertices;
 285
 1856286        GetCurrentScaleFactors(
 1856287            out Vector2d ownerScale,
 1856288            out Vector2d partScale);
 18464289        for (int i = 0; i < _localVertices.Length; i++)
 290        {
 7376291            _scaledLocalVerticesScratch[i] = ColliderScalePolicy.Scale(
 7376292                _localVertices[i],
 7376293                ownerScale,
 7376294                partScale);
 295        }
 296
 1856297        return _scaledLocalVerticesScratch;
 298    }
 299}