< Summary

Information
Class: Gravitas.CollisionHandling.ContactManifold2D
Assembly: Gravitas
File(s): /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Contacts/2D/ContactManifold2D.cs
Line coverage
100%
Covered lines: 169
Uncovered lines: 0
Coverable lines: 169
Total lines: 413
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/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}

Methods/Properties

.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()