< Summary

Information
Class: Gravitas.CollisionHandling.ContactManifold
Assembly: Gravitas
File(s): /home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Contacts/3D/ContactManifold.cs
Line coverage
100%
Covered lines: 190
Uncovered lines: 0
Coverable lines: 190
Total lines: 459
Line coverage: 100%
Branch coverage
100%
Covered branches: 56
Total branches: 56
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/3D/ContactManifold.cs

#LineLine coverage
 1//=======================================================================
 2// ContactManifold.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 contact manifold owned by one collision pair.
 18/// </summary>
 19public sealed class ContactManifold : IEnumerable<ManifoldContact>
 20{
 21    /// <summary>Maximum number of contacts retained by a 3D manifold.</summary>
 22    public const int MaxContactCount = 4;
 23
 24    private ManifoldContact _contact0;
 25    private ManifoldContact _contact1;
 26    private ManifoldContact _contact2;
 27    private ManifoldContact _contact3;
 28    private int _count;
 1086629    private int _lastUpdatedFrame = -1;
 30
 31    /// <summary>
 32    /// Number of active contacts in this manifold.
 33    /// </summary>
 34    public int Count
 35    {
 36        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 2078137        get => _count;
 38    }
 39
 40    /// <summary>
 41    /// Gets whether this manifold currently contains narrow-phase contact data.
 42    /// </summary>
 43    public bool HasContact
 44    {
 45        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 1702646        get => _count > 0;
 47    }
 48
 49    /// <summary>
 50    /// Simulation frame in which the active contacts were last rebuilt.
 51    /// </summary>
 52    public int LastUpdatedFrame
 53    {
 54        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 4455        get => _lastUpdatedFrame;
 56    }
 57
 58    /// <summary>
 59    /// Deepest contact in the manifold. Ties use the lowest contact identity.
 60    /// </summary>
 61    public ManifoldContact PrimaryContact
 62    {
 63        get
 64        {
 156665            SwiftThrowHelper.ThrowIfListIndexInvalid(0, _count);
 66
 156667            int bestIndex = 0;
 156668            ManifoldContact best = _contact0;
 357469            for (int i = 1; i < _count; i++)
 70            {
 22171                ManifoldContact candidate = this[i];
 22172                if (candidate.Depth > best.Depth
 22173                    || candidate.Depth == best.Depth && candidate.ContactId < best.ContactId)
 74                {
 875                    best = candidate;
 876                    bestIndex = i;
 77                }
 78            }
 79
 156680            return this[bestIndex];
 81        }
 82    }
 83
 84    /// <summary>Gets the contact at the specified deterministic order index.</summary>
 85    public ManifoldContact this[int index]
 86    {
 87        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 88        get
 89        {
 1301390            SwiftThrowHelper.ThrowIfListIndexInvalid(index, _count);
 1301391            return GetContactUnchecked(index);
 92        }
 93    }
 94
 95    /// <summary>
 96    /// Clears contacts and records the frame for a new narrow-phase pass.
 97    /// </summary>
 98    public void BeginUpdate(int frame)
 99    {
 10052100        _count = 0;
 10052101        _lastUpdatedFrame = frame;
 10052102    }
 103
 104    /// <summary>
 105    /// Clears all contact data.
 106    /// </summary>
 107    public void Reset()
 108    {
 2534109        _count = 0;
 2534110        _lastUpdatedFrame = -1;
 2534111        _contact0 = default;
 2534112        _contact1 = default;
 2534113        _contact2 = default;
 2534114        _contact3 = default;
 2534115    }
 116
 117    /// <summary>
 118    /// Replaces the manifold with one contact.
 119    /// </summary>
 120    public void SetContact(Vector3d pointA, Vector3d pointB, Fixed64 depth, Vector3d normal)
 121    {
 64122        _count = 0;
 64123        AddContact(pointA, pointB, depth, normal);
 64124    }
 125
 126    /// <summary>
 127    /// Replaces the manifold with one rigid-frame contact.
 128    /// </summary>
 129    public void SetContact(
 130        ContactAnchor anchorA,
 131        ContactAnchor anchorB,
 132        Fixed64 depth,
 133        Vector3d normal,
 134        bool depthIsClamped = false)
 135    {
 6189136        _count = 0;
 6189137        AddContact(anchorA, anchorB, depth, normal, depthIsClamped);
 6189138    }
 139
 140    /// <summary>
 141    /// Adds a contact, keeping the deepest four contacts and exposing them by stable contact identity.
 142    /// </summary>
 143    public void AddContact(Vector3d pointA, Vector3d pointB, Fixed64 depth, Vector3d normal)
 144    {
 1091145        AddContact(
 1091146            ContactAnchor.FromWorldPoint(pointA),
 1091147            ContactAnchor.FromWorldPoint(pointB),
 1091148            depth,
 1091149            normal);
 1091150    }
 151
 152    /// <summary>
 153    /// Adds a rigid-frame contact, keeping the deepest four contacts and
 154    /// exposing them by stable anchor identity.
 155    /// </summary>
 156    public void AddContact(
 157        ContactAnchor anchorA,
 158        ContactAnchor anchorB,
 159        Fixed64 depth,
 160        Vector3d normal,
 161        bool depthIsClamped = false)
 162    {
 30360163        AddContactCore(
 30360164            anchorA,
 30360165            anchorB,
 30360166            depth,
 30360167            normal,
 30360168            hasMaterialOverride: false,
 30360169            default,
 30360170            default,
 30360171            depthIsClamped,
 30360172            featureNamespaceA: 0,
 30360173            featureNamespaceB: 0);
 30360174    }
 175
 176    internal void AddContact(
 177        Vector3d pointA,
 178        Vector3d pointB,
 179        Fixed64 depth,
 180        Vector3d normal,
 181        PhysicsMaterial materialA,
 182        PhysicsMaterial materialB,
 183        bool depthIsClamped = false)
 184    {
 1185        AddContactCore(
 1186            ContactAnchor.FromWorldPoint(pointA),
 1187            ContactAnchor.FromWorldPoint(pointB),
 1188            depth,
 1189            normal,
 1190            hasMaterialOverride: true,
 1191            materialA,
 1192            materialB,
 1193            depthIsClamped,
 1194            featureNamespaceA: 0,
 1195            featureNamespaceB: 0);
 1196    }
 197
 198    internal void AddContact(
 199        ContactAnchor anchorA,
 200        ContactAnchor anchorB,
 201        Fixed64 depth,
 202        Vector3d normal,
 203        PhysicsMaterial materialA,
 204        PhysicsMaterial materialB,
 205        bool depthIsClamped = false,
 206        int featureNamespaceA = 0,
 207        int featureNamespaceB = 0)
 208    {
 19209        AddContactCore(
 19210            anchorA,
 19211            anchorB,
 19212            depth,
 19213            normal,
 19214            hasMaterialOverride: true,
 19215            materialA,
 19216            materialB,
 19217            depthIsClamped,
 19218            featureNamespaceA,
 19219            featureNamespaceB);
 19220    }
 221
 222    private void AddContactCore(
 223        ContactAnchor anchorA,
 224        ContactAnchor anchorB,
 225        Fixed64 depth,
 226        Vector3d normal,
 227        bool hasMaterialOverride,
 228        PhysicsMaterial materialA,
 229        PhysicsMaterial materialB,
 230        bool depthIsClamped,
 231        int featureNamespaceA,
 232        int featureNamespaceB)
 233    {
 30380234        ulong contactId = CreateContactId(
 30380235            anchorA,
 30380236            featureNamespaceA,
 30380237            anchorB,
 30380238            featureNamespaceB);
 30380239        var contact = new ManifoldContact(
 30380240            contactId,
 30380241            anchorA,
 30380242            anchorB,
 30380243            depth,
 30380244            normal,
 30380245            hasMaterialOverride,
 30380246            materialA,
 30380247            materialB,
 30380248            depthIsClamped,
 30380249            featureNamespaceA,
 30380250            featureNamespaceB);
 251
 212098252        for (int i = 0; i < _count; i++)
 253        {
 76959254            ManifoldContact existing = GetContactUnchecked(i);
 76959255            if (existing.ContactId != contactId)
 256                continue;
 257
 1290258            if (IsDeeper(contact, existing))
 3259                SetContactUnchecked(i, contact);
 1290260            SortContactsById();
 1290261            return;
 262        }
 263
 29090264        if (_count < MaxContactCount)
 265        {
 12682266            SetContactUnchecked(_count, contact);
 12682267            _count++;
 12682268            SortContactsById();
 12682269            return;
 270        }
 271
 16408272        int replaceIndex = FindShallowestReplacementIndex(contact);
 16408273        if (replaceIndex < 0)
 12382274            return;
 275
 4026276        SetContactUnchecked(replaceIndex, contact);
 4026277        SortContactsById();
 4026278    }
 279
 280    /// <summary>Returns an allocation-free enumerator over the active contacts.</summary>
 51281    public Enumerator GetEnumerator() => new(this);
 282
 50283    IEnumerator<ManifoldContact> IEnumerable<ManifoldContact>.GetEnumerator() => GetEnumerator();
 284
 1285    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
 286
 287    private int FindShallowestReplacementIndex(ManifoldContact candidate)
 288    {
 16408289        int replaceIndex = 0;
 16408290        ManifoldContact shallowest = _contact0;
 291
 131264292        for (int i = 1; i < _count; i++)
 293        {
 49224294            ManifoldContact contact = GetContactUnchecked(i);
 49224295            if (contact.Depth <= shallowest.Depth)
 296            {
 49211297                shallowest = contact;
 49211298                replaceIndex = i;
 299            }
 300        }
 301
 16408302        if (IsDeeper(candidate, shallowest))
 6303            return replaceIndex;
 304
 16402305        if (HasEqualDepth(candidate, shallowest) && candidate.ContactId < shallowest.ContactId)
 4020306            return replaceIndex;
 307
 12382308        return -1;
 309    }
 310
 311    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 312    private static bool IsDeeper(ManifoldContact candidate, ManifoldContact existing) =>
 17698313        candidate.Depth > existing.Depth
 17698314        || candidate.Depth == existing.Depth
 17698315        && candidate.DepthIsClamped
 17698316        && !existing.DepthIsClamped;
 317
 318    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 319    private static bool HasEqualDepth(ManifoldContact left, ManifoldContact right) =>
 16402320        left.Depth == right.Depth
 16402321        && left.DepthIsClamped == right.DepthIsClamped;
 322
 323    private void SortContactsById()
 324    {
 84484325        for (int i = 1; i < _count; i++)
 326        {
 24244327            ManifoldContact contact = GetContactUnchecked(i);
 24244328            int j = i - 1;
 34363329            while (j >= 0 && GetContactUnchecked(j).ContactId > contact.ContactId)
 330            {
 10119331                SetContactUnchecked(j + 1, GetContactUnchecked(j));
 10119332                j--;
 333            }
 334
 24244335            SetContactUnchecked(j + 1, contact);
 336        }
 17998337    }
 338
 339    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 340    private ManifoldContact GetContactUnchecked(int index) =>
 205530341        index switch
 205530342        {
 49972343            0 => _contact0,
 62320344            1 => _contact1,
 53808345            2 => _contact2,
 39430346            _ => _contact3
 205530347        };
 348
 349    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 350    private void SetContactUnchecked(int index, ManifoldContact contact)
 351    {
 352        switch (index)
 353        {
 354            case 0:
 10482355                _contact0 = contact;
 10482356                break;
 357            case 1:
 14478358                _contact1 = contact;
 14478359                break;
 360            case 2:
 14163361                _contact2 = contact;
 14163362                break;
 363            default:
 11951364                _contact3 = contact;
 365                break;
 366        }
 11951367    }
 368
 369    private static ulong CreateContactId(
 370        ContactAnchor anchorA,
 371        int featureNamespaceA,
 372        ContactAnchor anchorB,
 373        int featureNamespaceB)
 374    {
 30380375        if (CompareLocalFeature(
 30380376                featureNamespaceB,
 30380377                anchorB,
 30380378                featureNamespaceA,
 30380379                anchorA) < 0)
 380        {
 27632381            (anchorA, anchorB) = (anchorB, anchorA);
 27632382            (featureNamespaceA, featureNamespaceB) =
 27632383                (featureNamespaceB, featureNamespaceA);
 384        }
 385
 30380386        ulong hash = 14695981039346656037UL;
 30380387        Mix(ref hash, featureNamespaceA);
 30380388        MixLocalFeature(ref hash, anchorA);
 30380389        Mix(ref hash, featureNamespaceB);
 30380390        MixLocalFeature(ref hash, anchorB);
 30380391        return hash;
 392    }
 393
 394    private static int CompareLocalFeature(
 395        int leftNamespace,
 396        ContactAnchor left,
 397        int rightNamespace,
 398        ContactAnchor right)
 399    {
 30380400        int comparison = leftNamespace.CompareTo(rightNamespace);
 30380401        return comparison != 0
 30380402            ? comparison
 30380403            : left.CompareLocalFeature(right);
 404    }
 405
 406    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 407    private static void Mix(ref ulong hash, long value)
 408    {
 409        unchecked
 410        {
 121520411            hash ^= (ulong)value;
 121520412            hash *= 1099511628211UL;
 413        }
 121520414    }
 415
 416    private static void MixLocalFeature(
 417        ref ulong hash,
 418        ContactAnchor anchor)
 419    {
 60760420        Mix(
 60760421            ref hash,
 60760422            unchecked((long)anchor.GetLocalFeatureHash64()));
 60760423    }
 424
 425    /// <summary>Enumerates the active contacts in deterministic order.</summary>
 426    public struct Enumerator : IEnumerator<ManifoldContact>
 427    {
 428        private readonly ContactManifold _manifold;
 429        private int _index;
 430
 431        internal Enumerator(ContactManifold manifold)
 432        {
 51433            _manifold = manifold;
 51434            _index = -1;
 51435        }
 436
 437        /// <summary>Gets the current contact.</summary>
 177438        public ManifoldContact Current => _manifold[_index];
 439
 3440        object IEnumerator.Current => Current;
 441
 442        /// <summary>Advances to the next active contact.</summary>
 443        public bool MoveNext()
 444        {
 227445            int next = _index + 1;
 227446            if (next >= _manifold._count)
 50447                return false;
 448
 177449            _index = next;
 177450            return true;
 451        }
 452
 453        /// <summary>Resets the enumerator to its initial position.</summary>
 1454        public void Reset() => _index = -1;
 455
 456        /// <summary>Releases enumerator resources.</summary>
 50457        public void Dispose() { }
 458    }
 459}

Methods/Properties

.ctor()
get_Count()
get_HasContact()
get_LastUpdatedFrame()
get_PrimaryContact()
get_Item(System.Int32)
BeginUpdate(System.Int32)
Reset()
SetContact(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d)
SetContact(Gravitas.CollisionHandling.ContactAnchor,Gravitas.CollisionHandling.ContactAnchor,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,System.Boolean)
AddContact(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d)
AddContact(Gravitas.CollisionHandling.ContactAnchor,Gravitas.CollisionHandling.ContactAnchor,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,System.Boolean)
AddContact(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,Gravitas.Materials.PhysicsMaterial,Gravitas.Materials.PhysicsMaterial,System.Boolean)
AddContact(Gravitas.CollisionHandling.ContactAnchor,Gravitas.CollisionHandling.ContactAnchor,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,Gravitas.Materials.PhysicsMaterial,Gravitas.Materials.PhysicsMaterial,System.Boolean,System.Int32,System.Int32)
AddContactCore(Gravitas.CollisionHandling.ContactAnchor,Gravitas.CollisionHandling.ContactAnchor,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,System.Boolean,Gravitas.Materials.PhysicsMaterial,Gravitas.Materials.PhysicsMaterial,System.Boolean,System.Int32,System.Int32)
GetEnumerator()
System.Collections.Generic.IEnumerable<Gravitas.CollisionHandling.ManifoldContact>.GetEnumerator()
System.Collections.IEnumerable.GetEnumerator()
FindShallowestReplacementIndex(Gravitas.CollisionHandling.ManifoldContact)
IsDeeper(Gravitas.CollisionHandling.ManifoldContact,Gravitas.CollisionHandling.ManifoldContact)
HasEqualDepth(Gravitas.CollisionHandling.ManifoldContact,Gravitas.CollisionHandling.ManifoldContact)
SortContactsById()
GetContactUnchecked(System.Int32)
SetContactUnchecked(System.Int32,Gravitas.CollisionHandling.ManifoldContact)
CreateContactId(Gravitas.CollisionHandling.ContactAnchor,System.Int32,Gravitas.CollisionHandling.ContactAnchor,System.Int32)
CompareLocalFeature(System.Int32,Gravitas.CollisionHandling.ContactAnchor,System.Int32,Gravitas.CollisionHandling.ContactAnchor)
Mix(System.UInt64&,System.Int64)
MixLocalFeature(System.UInt64&,Gravitas.CollisionHandling.ContactAnchor)
.ctor(Gravitas.CollisionHandling.ContactManifold)
get_Current()
System.Collections.IEnumerator.get_Current()
MoveNext()
Reset()
Dispose()