< Summary

Line coverage
100%
Covered lines: 185
Uncovered lines: 0
Coverable lines: 185
Total lines: 325
Line coverage: 100%
Branch coverage
100%
Covered branches: 40
Total branches: 40
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/Pairs/Mixed/CollisionPairMixed.cs

#LineLine coverage
 1//=======================================================================
 2// CollisionPairMixed.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 Gravitas.Colliders;
 9using SwiftCollections;
 10using System;
 11using System.Runtime.CompilerServices;
 12
 13namespace Gravitas.CollisionHandling;
 14
 15/// <summary>
 16/// Owns one stable mixed 3D/2D collision pair identity and contact lifecycle.
 17/// </summary>
 18internal sealed partial class CollisionPairMixed
 19{
 20    private bool _isColliding;
 21    private bool _isTriggerPair;
 22    private bool _notificationInProgress;
 23    private bool _separationPending;
 24    private bool _collider3DNotified;
 25    private bool _collider2DNotified;
 26    private long _lifetimeVersion;
 27
 21228    public CollisionPairMixed(LSCollider collider3D, LSCollider2D collider2D)
 29    {
 21230        Collider3D = collider3D;
 21231        Collider2D = collider2D;
 21232        Initialize(collider3D, collider2D);
 21233    }
 34
 35    public LSCollider Collider3D { get; private set; }
 36
 37    public LSCollider2D Collider2D { get; private set; }
 38
 39    public int Collider3DId { get; private set; }
 40
 41    public int Collider2DId { get; private set; }
 42
 43    public ulong Key { get; private set; }
 44
 89445    public GravitasWorldContext Context => Collider3D.Context;
 46
 47    public int LastFrame { get; private set; } = -1;
 48
 49    public MixedContact Contact { get; private set; }
 50
 42051    internal long LifetimeVersion => _lifetimeVersion;
 52
 3253    internal bool IsNotificationInProgress => _notificationInProgress;
 54
 55    public void Initialize(LSCollider collider3D, LSCollider2D collider2D)
 56    {
 21457        SwiftThrowHelper.ThrowIfNull(collider3D, nameof(collider3D));
 21458        SwiftThrowHelper.ThrowIfNull(collider2D, nameof(collider2D));
 59
 21460        _lifetimeVersion++;
 21461        Collider3D = collider3D;
 21462        Collider2D = collider2D;
 21463        Collider3DId = collider3D.Id;
 21464        Collider2DId = collider2D.Id;
 21465        Key = MixedColliderKey.CreateKey(Collider3DId, Collider2DId);
 21466        LastFrame = -1;
 21467        _isColliding = false;
 21468        _isTriggerPair = collider3D.IsTrigger || collider2D.IsTrigger;
 21469        _notificationInProgress = false;
 21470        _separationPending = false;
 21471        _collider3DNotified = false;
 21472        _collider2DNotified = false;
 21473        Contact = default;
 21474    }
 75
 76    public void MarkColliding(int frame, MixedContact contact)
 77    {
 18478        bool changed = !_isColliding;
 18479        _isColliding = true;
 18480        _isTriggerPair = Collider3D.IsTrigger || Collider2D.IsTrigger;
 18481        Contact = contact;
 18482        LastFrame = frame;
 83
 18484        Context.Diagnostics.EmitMixedContact(this, contact);
 85
 18486        var registration3D = new ColliderLifetimeToken(Collider3D);
 18487        var registration2D = new ColliderLifetimeToken2D(Collider2D);
 18488        bool shouldRaiseTrigger = ColliderTriggerEventPolicy.ShouldRaise(Collider3D, Collider2D);
 18489        _notificationInProgress = true;
 18490        SwiftList<Exception>? notificationExceptions = null;
 91        try
 92        {
 18493            bool notifyEnter3D = changed || !_collider3DNotified;
 18494            _collider3DNotified = true;
 18495            Collider3D.NotifyMixedContact(
 18496                Collider2D,
 18497                true,
 18498                notifyEnter3D,
 18499                _isTriggerPair,
 184100                allowInactive: false,
 184101                registration3D,
 184102                registration2D,
 184103                shouldRaiseTrigger);
 183104            if (_isColliding && registration3D.IsActive && registration2D.IsActive && !_separationPending)
 105            {
 179106                bool notifyEnter2D = changed || !_collider2DNotified;
 179107                _collider2DNotified = true;
 179108                Collider2D.NotifyMixedContact(
 179109                    Collider3D,
 179110                    true,
 179111                    notifyEnter2D,
 179112                    _isTriggerPair,
 179113                    allowInactive: false,
 179114                    registration2D,
 179115                    registration3D,
 179116                    shouldRaiseTrigger);
 117            }
 183118        }
 1119        catch (Exception exception)
 120        {
 1121            CollisionNotificationExceptions.Capture(ref notificationExceptions, exception);
 1122        }
 123
 124        try
 125        {
 184126            EndNotification(
 184127                registration3D,
 184128                registration2D,
 184129                shouldRaiseTrigger);
 183130        }
 1131        catch (Exception exception)
 132        {
 1133            CollisionNotificationExceptions.Capture(ref notificationExceptions, exception);
 1134        }
 135
 184136        CollisionNotificationExceptions.ThrowIfAny(notificationExceptions);
 182137    }
 138
 139    public void MarkResting(int frame)
 140    {
 6141        LastFrame = frame;
 6142    }
 143
 144    public void MarkResting(int frame, MixedContact contact)
 145    {
 2146        Contact = contact;
 2147        LastFrame = frame;
 2148    }
 149
 150    public void MarkSeparated()
 151    {
 152        // The 2D side is admitted only after the 3D side, so it cannot be the sole notified side.
 33153        if (!_isColliding && !_collider3DNotified)
 2154            return;
 155
 31156        _isColliding = false;
 31157        Contact = default;
 31158        if (_notificationInProgress)
 159        {
 9160            _separationPending = true;
 9161            return;
 162        }
 163
 22164        var registration3D = new ColliderLifetimeToken(Collider3D);
 22165        var registration2D = new ColliderLifetimeToken2D(Collider2D);
 22166        _notificationInProgress = true;
 167        try
 168        {
 22169            NotifySeparation(
 22170                registration3D,
 22171                registration2D,
 22172                ColliderTriggerEventPolicy.ShouldRaise(Collider3D, Collider2D));
 22173        }
 174        finally
 175        {
 22176            _separationPending = false;
 22177            _notificationInProgress = false;
 22178        }
 22179    }
 180
 181    private void EndNotification(
 182        in ColliderLifetimeToken registration3D,
 183        in ColliderLifetimeToken2D registration2D,
 184        bool shouldRaiseTrigger)
 185    {
 186        try
 187        {
 184188            if (!_separationPending)
 175189                return;
 190
 9191            _separationPending = false;
 9192            NotifySeparation(registration3D, registration2D, shouldRaiseTrigger);
 8193        }
 194        finally
 195        {
 184196            _separationPending = false;
 184197            _notificationInProgress = false;
 184198        }
 183199    }
 200
 201    private void NotifySeparation(
 202        in ColliderLifetimeToken registration3D,
 203        in ColliderLifetimeToken2D registration2D,
 204        bool shouldRaiseTrigger)
 205    {
 31206        bool notify2D = _collider2DNotified;
 31207        _collider3DNotified = false;
 31208        _collider2DNotified = false;
 31209        SwiftList<Exception>? notificationExceptions = null;
 210        try
 211        {
 31212            Collider3D.NotifyMixedContact(
 31213                Collider2D,
 31214                false,
 31215                true,
 31216                _isTriggerPair,
 31217                allowInactive: true,
 31218                registration3D,
 31219                registration2D,
 31220                shouldRaiseTrigger);
 30221        }
 1222        catch (Exception exception)
 223        {
 1224            CollisionNotificationExceptions.Capture(ref notificationExceptions, exception);
 1225        }
 226
 31227        if (notify2D && registration2D.IsCurrentLifetime)
 228        {
 229            try
 230            {
 25231                Collider2D.NotifyMixedContact(
 25232                    Collider3D,
 25233                    false,
 25234                    true,
 25235                    _isTriggerPair,
 25236                    allowInactive: true,
 25237                    registration2D,
 25238                    registration3D,
 25239                    shouldRaiseTrigger);
 24240            }
 1241            catch (Exception exception)
 242            {
 1243                CollisionNotificationExceptions.Capture(ref notificationExceptions, exception);
 1244            }
 245        }
 246
 31247        CollisionNotificationExceptions.ThrowIfAny(notificationExceptions);
 30248    }
 249
 250    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 251    internal void WakeSleepingBodiesForCollision()
 252    {
 41253        SolidBody? body3D = Collider3D.Body;
 41254        SolidBody2D? body2D = Collider2D.Body;
 41255        if (body3D == null || body2D == null)
 3256            return;
 257
 38258        bool body3DAwake = body3D.IsAwakeForCollision;
 38259        bool body2DAwake = body2D.IsAwakeForCollision;
 38260        if (body3D.IsSleeping && body2DAwake)
 1261            body3D.Wake();
 38262        if (body2D.IsSleeping && body3DAwake)
 1263            body2D.Wake();
 38264    }
 265}

/home/runner/work/Gravitas/Gravitas/src/Gravitas/CollisionHandling/Pairs/Mixed/CollisionPairMixed.ReplayHash.cs

#LineLine coverage
 1//=======================================================================
 2// CollisionPairMixed.ReplayHash.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.Chronicler;
 10using Gravitas.Materials;
 11
 12namespace Gravitas.CollisionHandling;
 13
 14internal sealed partial class CollisionPairMixed
 15{
 16    internal void ContributeReplayHash(
 17        ref ChronicleHashWriter writer,
 18        GravitasReplayHashMode mode)
 19    {
 720        writer.WriteSection("pair.mixed", 5);
 721        writer.WriteInt32(Collider3D.ReplayOrdinal);
 722        writer.WriteInt32(Collider2D.ReplayOrdinal);
 723        writer.WriteUInt64(MixedColliderKey.CreateKey(Collider3D.ReplayOrdinal, Collider2D.ReplayOrdinal));
 724        writer.WriteInt32(LastFrame);
 725        writer.WriteBool(_isColliding);
 726        writer.WriteBool(_isTriggerPair);
 727        writer.WriteBool(Contact.HasContact);
 728        if (!Contact.HasContact)
 129            return;
 30
 631        writer.WriteVector3d(Contact.Anchor3D.Origin);
 632        writer.WriteQuaternion(Contact.Anchor3D.Rotation);
 633        writer.WriteVector3d(Contact.Anchor3D.LocalPoint);
 634        writer.WriteVector3d(Contact.Anchor3D.LocalDisplacement);
 635        writer.WriteUInt64(Contact.Anchor3D.GetLocalFeatureHash64());
 636        writer.WriteVector3d(Contact.Anchor2D.Origin);
 637        writer.WriteQuaternion(Contact.Anchor2D.Rotation);
 638        writer.WriteVector3d(Contact.Anchor2D.LocalPoint);
 639        writer.WriteVector3d(Contact.Anchor2D.LocalDisplacement);
 640        writer.WriteUInt64(Contact.Anchor2D.GetLocalFeatureHash64());
 641        writer.WriteVector3d(Contact.Normal3DTo2D);
 642        writer.WriteFixed64(Contact.Depth);
 643        writer.WriteBool(Contact.DepthIsClamped);
 644        writer.WriteBool(Contact.HasMaterialOverride);
 645        if (!Contact.HasMaterialOverride)
 546            return;
 47
 148        WriteMaterial(ref writer, Contact.Material3D);
 149        WriteMaterial(ref writer, Contact.Material2D);
 150    }
 51
 52    private static void WriteMaterial(ref ChronicleHashWriter writer, PhysicsMaterial material)
 53    {
 254        writer.WriteFixed64(material.StaticFriction);
 255        writer.WriteFixed64(material.DynamicFriction);
 256        writer.WriteFixed64(material.Restitution);
 257        writer.WriteEnum(material.FrictionCombine);
 258        writer.WriteEnum(material.RestitutionCombine);
 259    }
 60}