< Summary

Information
Class: Gravitas.Colliders.ColliderLifetimeToken2D
Assembly: Gravitas
File(s): /home/runner/work/Gravitas/Gravitas/src/Gravitas/Colliders/2D/LSCollider2D.cs
Line coverage
100%
Covered lines: 5
Uncovered lines: 0
Coverable lines: 5
Total lines: 912
Line coverage: 100%
Branch coverage
100%
Covered branches: 2
Total branches: 2
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
get_IsActive()100%22100%
get_IsCurrentLifetime()100%11100%

File(s)

/home/runner/work/Gravitas/Gravitas/src/Gravitas/Colliders/2D/LSCollider2D.cs

#LineLine coverage
 1//=======================================================================
 2// LSCCollider2D.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.Materials;
 12using Gravitas.Support;
 13using GridForge.Spatial;
 14using SwiftCollections;
 15using System.Runtime.CompilerServices;
 16
 17namespace Gravitas.Colliders;
 18
 19internal readonly struct ColliderLifetimeToken2D
 20{
 21    internal ColliderLifetimeToken2D(LSCollider2D collider)
 22    {
 658823        Collider = collider;
 658824        LifetimeVersion = collider.LifetimeVersion;
 658825    }
 26
 27    internal LSCollider2D Collider { get; }
 28
 29    internal long LifetimeVersion { get; }
 30
 1223631    internal bool IsActive => Collider.IsActive && IsCurrentLifetime;
 32
 1272733    internal bool IsCurrentLifetime => Collider.LifetimeVersion == LifetimeVersion;
 34}
 35
 36/// <summary>
 37/// Common runtime type for the closed set of Gravitas-owned pure 2D collider
 38/// shapes. Use <see cref="ColliderShapeDefinition2D.CreateCollider"/> when
 39/// constructing from engine-adapter shape data.
 40/// </summary>
 41public abstract partial class LSCollider2D : IRecordable, IColliderHierarchyNode, IPhysicsColliderRegistryItem
 42{
 43    internal LSCollider2D() { }
 44
 45    private SolidBody2D? _body;
 46    private IMatterAgent? _agent;
 47    private GravitasWorldContext? _context;
 48    private LSCompoundCollider2D? _compoundOwner;
 49    private Fixed64 _compoundLocalRotation;
 50    private Vector2d _compoundLocalScale = Vector2d.One;
 51    private int _id = -1;
 52    private int _serviceIndex = -1;
 53    private int _replayOrder = -1;
 54    private int _replayOrdinal = -1;
 55    private long _lifetimeVersion;
 56    private int _serviceRefreshIndex = -1;
 57    private bool _isActive = true;
 58    private bool _isTrigger;
 59    private PhysicsLayer _layer = new();
 60    private PhysicsLayerMask _ignoredCollisionLayers = PhysicsLayerMask.None;
 61    private PhysicsMaterial _material = PhysicsMaterial.Default;
 62    private Vector2d _localOffset;
 63    private FixedBoundArea _bounds;
 64    private FixedBoundBox _mixedBounds3D;
 65    private Fixed64? _mixedHalfThicknessOverride;
 66    private Fixed64 _mixedHalfThickness;
 67    private Fixed64 _mixedSlabCenterY;
 68    private uint _shapeVersion;
 69    private readonly ColliderRuntimeShapeState<ColliderShapeSnapshot2D> _runtimeShapeState = new();
 70    private ColliderPartitionState2D _partitionState;
 71    private ColliderPartitionState _mixedPartitionState;
 72    private ColliderQueryState _queryState;
 73    private ColliderPairState<CollisionPair2D> _pairState;
 74    private ColliderHierarchyState _hierarchyState;
 75
 76    /// <summary>Handles a pure 2D contact notification for the other body.</summary>
 77    public delegate void Body2DCollisionFunc(SolidBody2D other);
 78    /// <summary>Handles a pure 2D trigger notification for the other collider.</summary>
 79    public delegate void Trigger2DCollisionFunc(LSCollider2D other);
 80    /// <summary>Handles a mixed contact or trigger notification for the other 3D collider.</summary>
 81    public delegate void Mixed2DCollisionFunc(LSCollider other);
 82
 83    /// <summary>
 84    /// Raised while this collider is touching another collider that owns a 2D body.
 85    /// </summary>
 86    public event Body2DCollisionFunc? OnContact;
 87
 88    /// <summary>
 89    /// Raised on the first simulation frame this non-trigger collider touches another 2D body.
 90    /// </summary>
 91    public event Body2DCollisionFunc? OnContactEnter;
 92
 93    /// <summary>
 94    /// Raised when this collider stops touching another 2D body.
 95    /// </summary>
 96    public event Body2DCollisionFunc? OnContactExit;
 97
 98    /// <summary>
 99    /// Raised on the first simulation frame this collider participates in a valid trigger pair.
 100    /// </summary>
 101    public event Trigger2DCollisionFunc? OnTriggerEnter;
 102
 103    /// <summary>
 104    /// Raised each simulation frame this collider participates in an overlapped valid trigger pair.
 105    /// </summary>
 106    public event Trigger2DCollisionFunc? OnTriggerStay;
 107
 108    /// <summary>
 109    /// Raised when this collider stops participating in a valid trigger pair.
 110    /// </summary>
 111    public event Trigger2DCollisionFunc? OnTriggerExit;
 112
 113    /// <summary>Raised while this collider has a physical mixed contact with a 3D collider.</summary>
 114    public event Mixed2DCollisionFunc? OnMixedContact;
 115    /// <summary>Raised when this collider begins a physical mixed contact with a 3D collider.</summary>
 116    public event Mixed2DCollisionFunc? OnMixedContactEnter;
 117    /// <summary>Raised when this collider ends a physical mixed contact with a 3D collider.</summary>
 118    public event Mixed2DCollisionFunc? OnMixedContactExit;
 119
 120    /// <summary>
 121    /// Raised on the first mixed 2D/3D simulation frame this collider participates in a valid trigger pair.
 122    /// </summary>
 123    public event Mixed2DCollisionFunc? OnMixedTriggerEnter;
 124
 125    /// <summary>
 126    /// Raised each mixed 2D/3D simulation frame this collider participates in an overlapped valid trigger pair.
 127    /// </summary>
 128    public event Mixed2DCollisionFunc? OnMixedTriggerStay;
 129
 130    /// <summary>
 131    /// Raised when this collider stops participating in a valid mixed 2D/3D trigger pair.
 132    /// </summary>
 133    public event Mixed2DCollisionFunc? OnMixedTriggerExit;
 134
 135    /// <summary>Gets the context-local runtime collider identifier.</summary>
 136    public int Id => _id;
 137
 138    internal int ReplayOrdinal => _replayOrdinal;
 139
 140    internal long LifetimeVersion => _lifetimeVersion;
 141
 142    internal int ServiceRefreshIndex => _serviceRefreshIndex;
 143
 144    internal bool IsPartitioned => _partitionState.IsPartitioned;
 145
 146    internal SwiftList<WorldVoxelIndex>? PartitionCoordinates => _partitionState.Coordinates;
 147
 148    internal int PartitionKind => _partitionState.LastPartitionKind;
 149
 150    internal bool IsMixedPartitioned => _mixedPartitionState.IsPartitioned;
 151
 152    internal SwiftList<WorldVoxelIndex>? MixedPartitionCoordinates => _mixedPartitionState.Coordinates;
 153
 154    internal int MixedPartitionKind => _mixedPartitionState.LastPartitionKind;
 155
 156    internal uint BroadPhaseVersion => _partitionState.BroadPhaseVersion;
 157
 158    internal uint RuntimeShapeVersion => _runtimeShapeState.RuntimeVersion;
 159
 160    internal bool HasHostBinding => _agent != null;
 161
 162    internal int CollisionPairCount => _pairState.CollisionPairCount;
 163
 164    internal int CollisionPairHolderCount => _pairState.CollisionPairHolderCount;
 165
 166    internal SwiftDictionary<int, CollisionPair2D>? CollisionPairs => _pairState.CollisionPairs;
 167
 168    internal SwiftHashSet<int>? CollisionPairHolders => _pairState.CollisionPairHolders;
 169
 170    /// <summary>Gets the owning 2D body, or <see langword="null"/> for a bodyless collider.</summary>
 171    public SolidBody2D? Body => _body;
 172
 173    /// <summary>
 174    /// Gets whether this collider is bodyless or belongs to an explicit static
 175    /// body role.
 176    /// </summary>
 177    public bool IsStatic => _body == null || _body.IsStatic;
 178
 179    internal bool RequiresServiceSideRefresh => _body == null || _body.DynamicId < 0;
 180
 181    /// <summary>Gets the host agent to which this collider is bound.</summary>
 182    public IMatterAgent Agent
 183    {
 184        get
 185        {
 186            SwiftThrowHelper.ThrowIfTrue(
 187                _agent == null,
 188                nameof(LSCollider2D),
 189                "2D collider is not bound to an IMatterAgent.");
 190            return _agent!;
 191        }
 192    }
 193
 194    internal IMatterAgent? AgentOrNull => _agent;
 195
 196    /// <summary>Gets the world context to which this collider is bound.</summary>
 197    public GravitasWorldContext Context
 198    {
 199        get
 200        {
 201            SwiftThrowHelper.ThrowIfTrue(
 202                _context == null,
 203                nameof(LSCollider2D),
 204                "2D collider is not bound to a GravitasWorldContext.");
 205            return _context!;
 206        }
 207    }
 208
 209    /// <summary>Gets or sets whether this collider participates in runtime physics.</summary>
 210    public bool IsActive
 211    {
 212        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 213        get => _isActive;
 214        set
 215        {
 216            ThrowIfCompoundPartLifecycle(nameof(IsActive));
 217
 218            if (_isActive == value)
 219                return;
 220
 221            if (_context == null || _id < 0)
 222            {
 223                _isActive = value;
 224                return;
 225            }
 226
 227            if (value)
 228            {
 229                RebuildRuntimeShapeState();
 230                _isActive = true;
 231                _context.Collisions2D.RefreshColliderPartition(this);
 232                if (_context.Settings.RuntimeMode.RunsMixedContacts())
 233                    _context.MixedCollisions.Refresh2DColliderPartition(this);
 234            }
 235            else
 236            {
 237                _isActive = false;
 238                _context.Collisions2D.ClearPartitionedCollider(this, force: true);
 239                if (IsMixedPartitioned)
 240                    _context.MixedCollisions.ClearPartitioned2DCollider(this, force: true);
 241            }
 242        }
 243    }
 244
 245    /// <summary>
 246    /// Gets or sets whether this bodyless collider is a trigger volume.
 247    /// Trigger volumes raise trigger enter/stay/exit callbacks for valid overlap
 248    /// pairs and never apply physical response.
 249    /// </summary>
 250    public bool IsTrigger
 251    {
 252        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 253        get => _isTrigger;
 254        set => SetTrigger(value);
 255    }
 256
 257    /// <summary>
 258    /// Gets or sets the single physics layer used by 2D collision matrix and query-mask filtering.
 259    /// </summary>
 260    public PhysicsLayer Layer
 261    {
 262        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 263        get => _layer;
 264        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 265        set => _layer = value;
 266    }
 267
 268    /// <summary>
 269    /// Gets or sets physical layers this collider ignores for collider-to-collider
 270    /// interactions. Public queries continue to use the caller's query mask.
 271    /// </summary>
 272    public PhysicsLayerMask IgnoredCollisionLayers
 273    {
 274        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 275        get => _ignoredCollisionLayers;
 276        set
 277        {
 278            if (_ignoredCollisionLayers == value)
 279                return;
 280
 281            _ignoredCollisionLayers = value;
 282            _body?.Wake();
 283        }
 284    }
 285
 286    /// <summary>
 287    /// Gets or sets the deterministic surface material used by pure 2D and
 288    /// mixed collision response for this collider.
 289    /// </summary>
 290    public PhysicsMaterial Material
 291    {
 292        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 293        get => _material;
 294        set
 295        {
 296            if (_material == value)
 297                return;
 298
 299            _material = value;
 300            OnMaterialChanged();
 301            _body?.Wake();
 302        }
 303    }
 304
 305    /// <summary>Gets the runtime pure 2D shape family.</summary>
 306    public abstract ColliderType2D Shape { get; }
 307
 308    /// <summary>Gets the deterministic pure 2D narrow-phase ordering priority.</summary>
 309    public virtual int Priority => ColliderSettings2D.GetPriority(Shape);
 310
 311    internal uint RaycastVersion
 312    {
 313        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 314        get => _queryState.RaycastVersion;
 315        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 316        set => _queryState.RaycastVersion = value;
 317    }
 318
 319    internal uint CircleQueryVersion
 320    {
 321        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 322        get => _queryState.CircleQueryVersion;
 323        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 324        set => _queryState.CircleQueryVersion = value;
 325    }
 326
 327    /// <summary>Gets whether this collider has a hierarchy parent.</summary>
 328    public bool IsChild => _hierarchyState.IsChild;
 329
 330    /// <summary>Gets whether this collider is configured as or currently owns a hierarchy parent.</summary>
 331    public bool IsParent => _hierarchyState.IsParent;
 332
 333    /// <summary>Gets the context-local identifier of the parent collider, or -1 when unparented.</summary>
 334    public int ParentId => ParentKey.Id;
 335
 336    /// <summary>Gets the 2D parent collider, when the parent belongs to the 2D runtime.</summary>
 337    public LSCollider2D? Parent2D => _hierarchyState.Parent as LSCollider2D;
 338
 339    /// <summary>Gets the 3D parent collider, when the parent belongs to the 3D runtime.</summary>
 340    public LSCollider? Parent3D => _hierarchyState.Parent as LSCollider;
 341
 342    internal LSCollider2D? TopParent2D => _hierarchyState.TopParent as LSCollider2D;
 343
 344    internal LSCollider? TopParent3D => _hierarchyState.TopParent as LSCollider;
 345
 346    internal int HierarchyChildCount => _hierarchyState.ChildCount;
 347
 348    internal ColliderHierarchyKey HierarchyKey => Id >= 0 ? ColliderHierarchyKey.Create2D(Id) : ColliderHierarchyKey.Non
 349
 350    internal ColliderHierarchyKey ParentKey => _hierarchyState.ParentKey;
 351
 352    internal ColliderHierarchyKey TopParentKey => _hierarchyState.TopParentKey;
 353
 354    internal ColliderHierarchyState HierarchyState => _hierarchyState;
 355
 356    ColliderHierarchyKey IColliderHierarchyNode.HierarchyKey => HierarchyKey;
 357
 358    IColliderHierarchyNode? IColliderHierarchyNode.HierarchyParent => _hierarchyState.Parent;
 359
 360    /// <summary>Gets or sets the unscaled local center offset.</summary>
 361    public Vector2d LocalOffset
 362    {
 363        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 364        get => _localOffset;
 365        set
 366        {
 367            if (_localOffset == value)
 368                return;
 369
 370            _localOffset = value;
 371            MarkShapeDirty();
 372        }
 373    }
 374
 375    /// <summary>Gets the committed pure 2D world-space bounds.</summary>
 376    public FixedBoundArea Bounds => _bounds;
 377
 378    internal FixedBoundBox MixedBounds3D => _mixedBounds3D;
 379
 380    internal Fixed64 MixedHalfThickness => _mixedHalfThickness;
 381
 382    internal Fixed64 MixedSlabCenterY => _mixedSlabCenterY;
 383
 384    /// <summary>
 385    /// Gets or sets the optional half-thickness used when this 2D collider is embedded into mixed 2D/3D contacts.
 386    /// </summary>
 387    public Fixed64? MixedHalfThicknessOverride
 388    {
 389        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 390        get => _mixedHalfThicknessOverride;
 391        set
 392        {
 393            if (value.HasValue)
 394            {
 395                SwiftThrowHelper.ThrowIfArgument(
 396                    value.Value <= Fixed64.Zero,
 397                    nameof(value),
 398                    "2D mixed half-thickness override must be greater than zero.");
 399            }
 400
 401            if (_mixedHalfThicknessOverride == value)
 402                return;
 403
 404            _mixedHalfThicknessOverride = value;
 405            MarkShapeDirty();
 406        }
 407    }
 408
 409    /// <summary>Gets the minimum world X coordinate of <see cref="Bounds"/>.</summary>
 410    public Fixed64 MinX => _bounds.Min.X;
 411
 412    /// <summary>Gets the maximum world X coordinate of <see cref="Bounds"/>.</summary>
 413    public Fixed64 MaxX => _bounds.Max.X;
 414
 415    /// <summary>Gets the minimum planar Y coordinate of <see cref="Bounds"/>.</summary>
 416    public Fixed64 MinY => _bounds.Min.Y;
 417
 418    /// <summary>Gets the maximum planar Y coordinate of <see cref="Bounds"/>.</summary>
 419    public Fixed64 MaxY => _bounds.Max.Y;
 420
 421    internal void Initialize(SolidBody2D body)
 422    {
 423        InitCore(body.Agent, body);
 424    }
 425
 426    /// <summary>Binds and registers this collider as a bodyless pure 2D collider.</summary>
 427    public void InitializeWithNoBody(IMatterAgent agent)
 428    {
 429        ThrowIfCompoundPartLifecycle(nameof(InitializeWithNoBody));
 430        SwiftThrowHelper.ThrowIfNull(agent, nameof(agent));
 431        SwiftThrowHelper.ThrowIfArgument(
 432            Id >= 0 || (HasHostBinding && !ReferenceEquals(_agent, agent)),
 433            nameof(agent),
 434            "2D collider is already registered or bound to another host agent.");
 435        PreflightInitialization(agent);
 436        InitCore(agent, null);
 437        Context.Physics2D.AssimilateCollider(this);
 438    }
 439
 440    internal void PreflightBodyInitialization(
 441        SolidBody2D body,
 442        Vector2d requestedPosition,
 443        Fixed64 requestedRotation)
 444    {
 445        ThrowIfCompoundPartLifecycle(nameof(Initialize));
 446        SwiftThrowHelper.ThrowIfNull(body, nameof(body));
 447        ThrowIfTriggerWouldAttachToBody(nameof(Initialize));
 448        PreflightInitialization(
 449            body.Agent,
 450            useRequestedPose: true,
 451            requestedPosition,
 452            requestedRotation);
 453    }
 454
 455    private void PreflightInitialization(
 456        IMatterAgent agent,
 457        bool useRequestedPose = false,
 458        Vector2d requestedPosition = default,
 459        Fixed64 requestedRotation = default)
 460    {
 461        SwiftThrowHelper.ThrowIfNull(agent, nameof(agent));
 462        OnBeforeInitialize(agent);
 463        PrepareStandaloneInitialization(
 464            agent,
 465            useRequestedPose,
 466            requestedPosition,
 467            requestedRotation);
 468    }
 469
 470    private void InitCore(IMatterAgent agent, SolidBody2D? body)
 471    {
 472        _lifetimeVersion++;
 473        _body = body;
 474        _agent = agent;
 475        _context = agent.Context;
 476        _isActive = true;
 477        _queryState.Reset();
 478        _hierarchyState.Initialize(agent.IsParent);
 479        PublishPreparedShape();
 480    }
 481
 482    /// <summary>Validates or prepares derived shape state before runtime registration.</summary>
 483    protected virtual void OnBeforeInitialize(IMatterAgent agent) { }
 484
 485    internal void SetPhysicsState(int id, int serviceIndex, int replayOrder)
 486    {
 487        SwiftThrowHelper.ThrowIfNegative(id, nameof(id));
 488        SwiftThrowHelper.ThrowIfNegative(serviceIndex, nameof(serviceIndex));
 489        SwiftThrowHelper.ThrowIfNegative(replayOrder, nameof(replayOrder));
 490        _id = id;
 491        _serviceIndex = serviceIndex;
 492        _replayOrder = replayOrder;
 493        _replayOrdinal = -1;
 494    }
 495
 496    internal void SetServiceIndex(int serviceIndex)
 497    {
 498        SwiftThrowHelper.ThrowIfNegative(serviceIndex, nameof(serviceIndex));
 499        _serviceIndex = serviceIndex;
 500    }
 501
 502    internal void SetReplayOrdinal(int replayOrdinal)
 503    {
 504        SwiftThrowHelper.ThrowIfNegative(replayOrdinal, nameof(replayOrdinal));
 505        _replayOrdinal = replayOrdinal;
 506    }
 507
 508    internal void SetServiceRefreshIndex(int serviceRefreshIndex)
 509    {
 510        SwiftThrowHelper.ThrowIfNegative(serviceRefreshIndex, nameof(serviceRefreshIndex));
 511        _serviceRefreshIndex = serviceRefreshIndex;
 512    }
 513
 514    internal void ClearServiceRefreshIndex()
 515    {
 516        _serviceRefreshIndex = -1;
 517    }
 518
 519    internal void ClearPhysicsState()
 520    {
 521        _partitionState.MarkUnpartitioned();
 522        _partitionState.ClearCoordinates();
 523        _mixedPartitionState.MarkUnpartitioned();
 524        _mixedPartitionState.ClearCoordinates();
 525        _pairState.ClearCollisionPairs();
 526        _pairState.ClearCollisionPairHolders();
 527        _id = -1;
 528        _serviceIndex = -1;
 529        _replayOrder = -1;
 530        _replayOrdinal = -1;
 531        _serviceRefreshIndex = -1;
 532    }
 533
 534    void IPhysicsColliderRegistryItem.SetRegistryState(int id, int serviceIndex, int replayOrder) =>
 535        SetPhysicsState(id, serviceIndex, replayOrder);
 536
 537    int IPhysicsColliderRegistryItem.ServiceIndex => _serviceIndex;
 538
 539    int IPhysicsColliderRegistryItem.ReplayOrder => _replayOrder;
 540
 541    void IPhysicsColliderRegistryItem.SetRegistryServiceIndex(int serviceIndex) =>
 542        SetServiceIndex(serviceIndex);
 543
 544    void IPhysicsColliderRegistryItem.SetRegistryReplayOrdinal(int replayOrdinal) =>
 545        SetReplayOrdinal(replayOrdinal);
 546
 547    void IPhysicsColliderRegistryItem.ClearRegistryState() => ClearPhysicsState();
 548
 549    internal void ClearBindingState()
 550    {
 551        _body = null;
 552        _agent = null;
 553        _context = null;
 554        _preparedContext = null;
 555    }
 556
 557    /// <summary>Unregisters this collider, or deactivates its owning body.</summary>
 558    public void Deactivate()
 559    {
 560        ThrowIfCompoundPartLifecycle(nameof(Deactivate));
 561
 562        if (_body != null)
 563        {
 564            _body.Deactivate();
 565            return;
 566        }
 567
 568        if (_id >= 0)
 569            _context!.Physics2D.DessimilateCollider(this);
 570
 571        _isActive = false;
 572        ClearBindingState();
 573    }
 574
 575    /// <summary>Refreshes bodyless collider shape and broad-phase state for one simulation step.</summary>
 576    public void Simulate()
 577    {
 578        ThrowIfCompoundPartLifecycle(nameof(Simulate));
 579
 580        if (!IsActive)
 581            return;
 582
 583        Rebuild();
 584    }
 585
 586    internal bool Rebuild()
 587    {
 588        if (!RebuildRuntimeShapeState())
 589            return false;
 590
 591        if (_id >= 0)
 592            return _context!.Collisions2D.RefreshColliderPartitionAfterShapeChange(this);
 593
 594        return true;
 595    }
 596
 597    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 598    internal bool RebuildRuntimeShapeOnly() => RebuildRuntimeShapeState();
 599
 600    internal void ValidateCurrentRuntimeTransform() =>
 601        PrepareRuntimeShape(
 602            CaptureShapeSnapshot(),
 603            requireRepresentableMassPoint: true);
 604
 605    /// <summary>Assigns a 2D hierarchy parent used for collision exclusion.</summary>
 606    public void SetParent(LSCollider2D parent)
 607    {
 608        ThrowIfCompoundPartLifecycle(nameof(SetParent));
 609        _hierarchyState.SetParent(this, parent);
 610    }
 611
 612    /// <summary>Assigns a 3D hierarchy parent used for mixed collision exclusion.</summary>
 613    public void SetParent(LSCollider parent)
 614    {
 615        ThrowIfCompoundPartLifecycle(nameof(SetParent));
 616        _hierarchyState.SetParent(this, parent);
 617    }
 618
 619    /// <summary>Removes this collider from its current hierarchy parent.</summary>
 620    public void ClearParent()
 621    {
 622        ThrowIfCompoundPartLifecycle(nameof(ClearParent));
 623        _hierarchyState.ClearParent(this);
 624    }
 625
 626    /// <summary>Gets whether hierarchy rules exclude collision with another 2D collider.</summary>
 627    public bool IsSibling(LSCollider2D other) =>
 628        _hierarchyState.ExcludesCollisionWith(other._hierarchyState, HierarchyKey, other.HierarchyKey);
 629
 630    internal bool ExcludesMixedCollisionWith(LSCollider other) =>
 631        _hierarchyState.ExcludesCollisionWith(other.HierarchyState, HierarchyKey, other.HierarchyKey);
 632
 633    internal SwiftList<WorldVoxelIndex> GetOrCreatePartitionCoordinates()
 634    {
 635        _partitionState.Coordinates ??= new();
 636        return _partitionState.Coordinates;
 637    }
 638
 639    internal SwiftList<WorldVoxelIndex> GetOrCreateMixedPartitionCoordinates()
 640    {
 641        _mixedPartitionState.Coordinates ??= new();
 642        return _mixedPartitionState.Coordinates;
 643    }
 644
 645    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 646    internal bool MatchesPartitionGridBounds(Vector2d min, Vector2d max, int partitionKind) =>
 647        _partitionState.MatchesGridBounds(min, max, partitionKind);
 648
 649    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 650    internal bool MatchesMixedPartitionGridBounds(Vector3d min, Vector3d max, int partitionKind) =>
 651        _mixedPartitionState.IsPartitioned
 652        && _mixedPartitionState.LastGridBoundsMin == min
 653        && _mixedPartitionState.LastGridBoundsMax == max
 654        && _mixedPartitionState.LastPartitionKind == partitionKind;
 655
 656    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 657    internal void MarkPartitioned(Vector2d min, Vector2d max, int partitionKind)
 658    {
 659        _partitionState.SetPreviousGridBounds(min, max, partitionKind);
 660        _partitionState.MarkPartitioned();
 661    }
 662
 663    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 664    internal void MarkMixedPartitioned(Vector3d min, Vector3d max, int partitionKind)
 665    {
 666        _mixedPartitionState.SetPreviousGridBounds(min, max, partitionKind);
 667        _mixedPartitionState.MarkPartitioned();
 668    }
 669
 670    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 671    internal void MarkUnpartitioned() => _partitionState.MarkUnpartitioned();
 672
 673    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 674    internal void MarkMixedUnpartitioned() => _mixedPartitionState.MarkUnpartitioned();
 675
 676    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 677    internal void ClearPartitionCoordinates() => _partitionState.ClearCoordinates();
 678
 679    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 680    internal void ClearMixedPartitionCoordinates() => _mixedPartitionState.ClearCoordinates();
 681
 682    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 683    internal bool IsPositionInPlanarBounds(Fixed64 cellEdge, Vector3d worldPosition)
 684    {
 685        Fixed64 padding = cellEdge * Fixed64.Half;
 686        return worldPosition.X >= MinX - padding
 687            && worldPosition.X <= MaxX + padding
 688            && worldPosition.Z >= MinY - padding
 689            && worldPosition.Z <= MaxY + padding;
 690    }
 691
 692    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 693    internal bool IsPositionInMixedBounds(Fixed64 cellEdge, Vector3d worldPosition)
 694    {
 695        Fixed64 padding = cellEdge * Fixed64.Half;
 696        return worldPosition.X >= _mixedBounds3D.Min.X - padding
 697            && worldPosition.X <= _mixedBounds3D.Max.X + padding
 698            && worldPosition.Y >= _mixedBounds3D.Min.Y - padding
 699            && worldPosition.Y <= _mixedBounds3D.Max.Y + padding
 700            && worldPosition.Z >= _mixedBounds3D.Min.Z - padding
 701            && worldPosition.Z <= _mixedBounds3D.Max.Z + padding;
 702    }
 703
 704    internal bool TryGetCollisionPair(int otherId, out CollisionPair2D? collisionPair) =>
 705        _pairState.TryGetCollisionPair(otherId, out collisionPair);
 706
 707    internal bool TryAddCollisionPair(int otherId, CollisionPair2D collisionPair) =>
 708        _pairState.TryAddCollisionPair(otherId, collisionPair);
 709
 710    internal bool TryRemoveCollisionPair(int otherId, out CollisionPair2D? collisionPair) =>
 711        _pairState.TryRemoveCollisionPair(otherId, out collisionPair);
 712
 713    internal bool TryAddCollisionPairHolder(int otherId) => _pairState.TryAddCollisionPairHolder(otherId);
 714
 715    internal bool TryRemoveCollisionPairHolder(int otherId) => _pairState.TryRemoveCollisionPairHolder(otherId);
 716
 717    internal void ClearCollisionPairState()
 718    {
 719        _pairState.ClearCollisionPairs();
 720        _pairState.ClearCollisionPairHolders();
 721    }
 722
 723    internal void ClearRuntimeRelationships()
 724    {
 725        ClearChildParentReferences();
 726        ClearParent();
 727    }
 728
 729    private Fixed64 ResolveAgentRotation()
 730    {
 731        return _agent == null
 732            ? Fixed64.Zero
 733            : _agent.Transform.WorldRotationXZRadians;
 734    }
 735
 736    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 737    internal void BindContext(GravitasWorldContext context)
 738    {
 739        SwiftThrowHelper.ThrowIfNull(context, nameof(context));
 740        SwiftThrowHelper.ThrowIfArgument(
 741            _context != null && !ReferenceEquals(_context, context),
 742            nameof(context),
 743            "2D collider is already bound to a different GravitasWorldContext.");
 744        _context = context;
 745    }
 746
 747    internal void ReserveCompoundPart(
 748        LSCompoundCollider2D owner,
 749        Fixed64 localRotation,
 750        Vector2d localScale)
 751    {
 752        SwiftThrowHelper.ThrowIfNull(owner, nameof(owner));
 753        SwiftThrowHelper.ThrowIfArgument(
 754            HasHostBinding,
 755            nameof(owner),
 756            "2D compound collider parts cannot be initialized as standalone colliders.");
 757        SwiftThrowHelper.ThrowIfArgument(
 758            _compoundOwner != null && !ReferenceEquals(_compoundOwner, owner),
 759            nameof(owner),
 760            "2D compound collider part is already owned by another compound collider.");
 761
 762        _compoundOwner = owner;
 763        _compoundLocalRotation = localRotation;
 764        _compoundLocalScale = localScale;
 765    }
 766
 767    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 768    private void ThrowIfCompoundPartLifecycle(string operation)
 769    {
 770        SwiftThrowHelper.ThrowIfTrue(
 771            _compoundOwner != null,
 772            operation,
 773            "2D compound collider parts are geometry owned by LSCompoundCollider2D and cannot run standalone lifecycle o
 774    }
 775
 776    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 777    internal ExactMassPoint2D TransformRelativeMassPropertyPointExact(
 778        Vector2d partRelativePoint)
 779    {
 780        GetCurrentScaleFactors(
 781            out Vector2d ownerScale,
 782            out _);
 783        return _compoundOwner == null
 784            ? ExactMassPoint2D.CreateScaledLocalComposition(
 785                LocalOffset,
 786                ownerScale,
 787                Vector2d.Zero,
 788                Vector2d.One,
 789                partRelativePoint,
 790                Fixed64.Zero)
 791            : ExactMassPoint2D.CreateScaledLocalComposition(
 792                _compoundOwner.LocalOffset,
 793                ownerScale,
 794                LocalOffset,
 795                ownerScale,
 796                partRelativePoint,
 797                _compoundLocalRotation);
 798    }
 799
 800    /// <summary>Rotates a planar vector and removes fixed-point epsilon residue.</summary>
 801    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 802    protected static Vector2d Rotate(Vector2d value, Fixed64 radians)
 803    {
 804        if (radians == Fixed64.Zero)
 805            return value;
 806
 807        return ClampNearZero(Vector2d.Rotate(value, radians));
 808    }
 809
 810    /// <summary>Replaces vector components within fixed-point epsilon of zero.</summary>
 811    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 812    protected static Vector2d ClampNearZero(Vector2d value)
 813    {
 814        Fixed64 x = value.X.Abs() <= Fixed64.Epsilon ? Fixed64.Zero : value.X;
 815        Fixed64 y = value.Y.Abs() <= Fixed64.Epsilon ? Fixed64.Zero : value.Y;
 816        return new Vector2d(x, y);
 817    }
 818
 819    private void SetTrigger(bool value)
 820    {
 821        if (_isTrigger == value)
 822            return;
 823
 824        if (value)
 825            ThrowIfCannotEnableTrigger(nameof(IsTrigger));
 826
 827        _isTrigger = value;
 828    }
 829
 830    private void ThrowIfCannotEnableTrigger(string operation)
 831    {
 832        SwiftThrowHelper.ThrowIfArgument(
 833            _body != null,
 834            operation,
 835            "2D trigger colliders must be initialized without a SolidBody2D. Use InitializeWithNoBody for trigger volume
 836        SwiftThrowHelper.ThrowIfArgument(
 837            _compoundOwner != null,
 838            operation,
 839            "2D compound collider parts are not trigger identities. Set IsTrigger on the owning compound collider.");
 840    }
 841
 842    private void ThrowIfTriggerWouldAttachToBody(string operation)
 843    {
 844        SwiftThrowHelper.ThrowIfArgument(
 845            _isTrigger,
 846            operation,
 847            "2D trigger colliders must be initialized without a SolidBody2D. Use InitializeWithNoBody for trigger volume
 848    }
 849
 850    private void ThrowIfLoadedTriggerHasBody(string operation)
 851    {
 852        SwiftThrowHelper.ThrowIfArgument(
 853            _isTrigger && _body != null,
 854            operation,
 855            "Loaded 2D trigger state is invalid for a collider attached to a SolidBody2D.");
 856    }
 857
 858    /// <inheritdoc/>
 859    public void RecordData(IChronicler chronicler)
 860    {
 861        RecordValues.Look(chronicler, ref _isActive, "Active", true);
 862        RecordValues.Look(chronicler, ref _isTrigger, "IsTrigger", false);
 863        RecordValues.Look(chronicler, ref _layer, "Layer", new());
 864        RecordValues.Look(chronicler, ref _ignoredCollisionLayers, "IgnoredCollisionLayers", PhysicsLayerMask.None);
 865        RecordValues.Look(chronicler, ref _material, "Material", PhysicsMaterial.Default);
 866        RecordValues.Look(chronicler, ref _localOffset, "LocalOffset", Vector2d.Zero);
 867        RecordValues.Look(chronicler, ref _mixedHalfThicknessOverride, "MixedHalfThicknessOverride");
 868        RecordShapeData(chronicler);
 869
 870        if (chronicler.Mode == SerializationMode.Loading)
 871        {
 872            ThrowIfLoadedTriggerHasBody(nameof(IsTrigger));
 873            ApplyLoadedState();
 874        }
 875    }
 876
 877    /// <summary>Records derived authored shape data.</summary>
 878    protected virtual void RecordShapeData(IChronicler chronicler) { }
 879
 880    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 881    internal bool IgnoresCollisionLayer(PhysicsLayer layer) => _ignoredCollisionLayers.Includes(layer);
 882
 883    private void ApplyLoadedState()
 884    {
 885        _runtimeShapeState.MarkDirty();
 886        if (_context == null)
 887            return;
 888
 889        RebuildRuntimeShapeState();
 890
 891        if (_id < 0)
 892            return;
 893
 894        if (!_isActive)
 895        {
 896            if (IsPartitioned)
 897                _context.Collisions2D.ClearPartitionedCollider(this, force: true);
 898            MarkUnpartitioned();
 899            ClearPartitionCoordinates();
 900
 901            if (IsMixedPartitioned)
 902                _context.MixedCollisions.ClearPartitioned2DCollider(this, force: true);
 903            MarkMixedUnpartitioned();
 904            ClearMixedPartitionCoordinates();
 905            return;
 906        }
 907
 908        _context.Collisions2D.RefreshColliderPartition(this);
 909        if (_context.Settings.RuntimeMode.RunsMixedContacts())
 910            _context.MixedCollisions.Refresh2DColliderPartition(this);
 911    }
 912}