| | | 1 | | //======================================================================= |
| | | 2 | | // LSCircleCollider2D.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 | | |
| | | 8 | | using Chronicler; |
| | | 9 | | using FixedMathSharp; |
| | | 10 | | using FixedMathSharp.Geometry; |
| | | 11 | | using System.Runtime.CompilerServices; |
| | | 12 | | |
| | | 13 | | namespace Gravitas.Colliders; |
| | | 14 | | |
| | | 15 | | /// <summary> |
| | | 16 | | /// Pure 2D circle collider. |
| | | 17 | | /// </summary> |
| | | 18 | | public sealed class LSCircleCollider2D : LSCollider2D |
| | | 19 | | { |
| | | 20 | | private Fixed64 _radius; |
| | | 21 | | private Fixed64 _scaledRadius; |
| | | 22 | | private Fixed64 _preparedRadius; |
| | | 23 | | |
| | | 24 | | /// <summary>Creates a pure 2D circle with an authored radius.</summary> |
| | 2977 | 25 | | public LSCircleCollider2D(Fixed64 radius) |
| | | 26 | | { |
| | 2977 | 27 | | Radius = radius; |
| | 2977 | 28 | | } |
| | | 29 | | |
| | | 30 | | /// <summary>Creates a runtime circle from an authored shape definition.</summary> |
| | 168 | 31 | | public LSCircleCollider2D(ColliderShapeDefinition2D definition) |
| | | 32 | | { |
| | 168 | 33 | | definition.EnsureKind(ColliderShapeDefinition2DKind.Circle); |
| | 167 | 34 | | Material = definition.Material; |
| | 167 | 35 | | Radius = definition.Radius; |
| | 167 | 36 | | } |
| | | 37 | | |
| | | 38 | | /// <inheritdoc/> |
| | 26748 | 39 | | public override ColliderType2D Shape => ColliderType2D.Circle; |
| | | 40 | | |
| | | 41 | | /// <summary>Gets or sets the unscaled circle radius.</summary> |
| | | 42 | | public Fixed64 Radius |
| | | 43 | | { |
| | | 44 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | 600 | 45 | | get => _radius; |
| | | 46 | | set |
| | | 47 | | { |
| | 3160 | 48 | | SwiftThrowHelper.ThrowIfArgument(value <= Fixed64.Zero, nameof(value), "2D circle radius must be greater tha |
| | 3160 | 49 | | if (_radius == value) |
| | 2 | 50 | | return; |
| | | 51 | | |
| | 3158 | 52 | | _radius = value; |
| | 3158 | 53 | | MarkShapeDirty(); |
| | 3158 | 54 | | } |
| | | 55 | | } |
| | | 56 | | |
| | | 57 | | /// <summary>Gets the radius after owner and compound-part scaling.</summary> |
| | | 58 | | public Fixed64 ScaledRadius |
| | | 59 | | { |
| | | 60 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | 118784 | 61 | | get => GetMassPropertyRadius(); |
| | | 62 | | } |
| | | 63 | | |
| | | 64 | | /// <inheritdoc/> |
| | | 65 | | public override bool ContainsPoint(Vector2d point) => |
| | 1496 | 66 | | FixedSegment2d.ContainsPointInCenteredCapsule( |
| | 1496 | 67 | | point, |
| | 1496 | 68 | | Center, |
| | 1496 | 69 | | Vector2d.Forward, |
| | 1496 | 70 | | Fixed64.Zero, |
| | 1496 | 71 | | ScaledRadius, |
| | 1496 | 72 | | Fixed64.Zero); |
| | | 73 | | |
| | | 74 | | /// <inheritdoc/> |
| | | 75 | | public override Vector2d GetClosestPoint(Vector2d point) |
| | | 76 | | { |
| | 3 | 77 | | if (TryGetClosestBoundaryAnchor( |
| | 3 | 78 | | point, |
| | 3 | 79 | | out FixedPointAnchor2d anchor) |
| | 3 | 80 | | && anchor.TryGetPoint(out Vector2d closest)) |
| | | 81 | | { |
| | 2 | 82 | | return closest; |
| | | 83 | | } |
| | | 84 | | |
| | 1 | 85 | | throw new System.InvalidOperationException( |
| | 1 | 86 | | "The closest circle surface point is outside the Fixed64 coordinate domain."); |
| | | 87 | | } |
| | | 88 | | |
| | | 89 | | /// <inheritdoc/> |
| | | 90 | | public override Vector2d GetSupportPoint(Vector2d direction) |
| | | 91 | | { |
| | 7 | 92 | | if (direction == Vector2d.Zero) |
| | 1 | 93 | | direction = Vector2d.Right; |
| | | 94 | | |
| | 7 | 95 | | if (Vector2d.TryRotate( |
| | 7 | 96 | | direction.Normalized, |
| | 7 | 97 | | -Rotation, |
| | 7 | 98 | | out Vector2d localDirection)) |
| | | 99 | | { |
| | 7 | 100 | | FixedPointAnchor2d anchor = |
| | 7 | 101 | | FixedSegment2d.GetCenteredCapsuleSupportAnchor( |
| | 7 | 102 | | Center, |
| | 7 | 103 | | Rotation, |
| | 7 | 104 | | Vector2d.Forward, |
| | 7 | 105 | | Fixed64.Zero, |
| | 7 | 106 | | ScaledRadius, |
| | 7 | 107 | | localDirection); |
| | 7 | 108 | | if (anchor.TryGetPoint(out Vector2d support)) |
| | 5 | 109 | | return support; |
| | | 110 | | } |
| | | 111 | | |
| | 2 | 112 | | throw new System.InvalidOperationException( |
| | 2 | 113 | | "The circle support point is outside the Fixed64 coordinate domain."); |
| | | 114 | | } |
| | | 115 | | |
| | | 116 | | internal override ExactMassWeight CalculateAreaForMassProperties() |
| | | 117 | | { |
| | 1262 | 118 | | Fixed64 radius = GetMassPropertyRadius(); |
| | 1262 | 119 | | return ExactMassWeight.FromProduct( |
| | 1262 | 120 | | Fixed64.Pi, |
| | 1262 | 121 | | radius, |
| | 1262 | 122 | | radius); |
| | | 123 | | } |
| | | 124 | | |
| | | 125 | | internal override ExactMassWeight CalculatePreparedAreaForMassProperties() => |
| | 418 | 126 | | ExactMassWeight.FromProduct( |
| | 418 | 127 | | Fixed64.Pi, |
| | 418 | 128 | | _preparedRadius, |
| | 418 | 129 | | _preparedRadius); |
| | | 130 | | |
| | | 131 | | internal override Fixed64 CalculateCenterOfMassMoment(Fixed64 mass) |
| | | 132 | | { |
| | 8442 | 133 | | Fixed64 radius = GetMassPropertyRadius(); |
| | 8442 | 134 | | return Fixed64.TryMultiplyDivide( |
| | 8442 | 135 | | mass, |
| | 8442 | 136 | | radius, |
| | 8442 | 137 | | radius, |
| | 8442 | 138 | | Fixed64.Two, |
| | 8442 | 139 | | out Fixed64 moment) |
| | 8442 | 140 | | ? moment |
| | 8442 | 141 | | : Fixed64.MaxValue; |
| | | 142 | | } |
| | | 143 | | |
| | | 144 | | private Fixed64 GetMassPropertyRadius() |
| | | 145 | | { |
| | 128488 | 146 | | if (HasCommittedShape) |
| | 118966 | 147 | | return _scaledRadius; |
| | | 148 | | |
| | 9522 | 149 | | GetCurrentScaleFactors( |
| | 9522 | 150 | | out Vector2d ownerScale, |
| | 9522 | 151 | | out Vector2d partScale); |
| | 9522 | 152 | | Fixed64 radiusX = ColliderScalePolicy.Scale( |
| | 9522 | 153 | | _radius, |
| | 9522 | 154 | | ownerScale.X, |
| | 9522 | 155 | | partScale.X); |
| | 9522 | 156 | | Fixed64 radiusY = ColliderScalePolicy.Scale( |
| | 9522 | 157 | | _radius, |
| | 9522 | 158 | | ownerScale.Y, |
| | 9522 | 159 | | partScale.Y); |
| | 9522 | 160 | | return FixedMath.Max(radiusX, radiusY); |
| | | 161 | | } |
| | | 162 | | |
| | | 163 | | private protected override void PrepareShape(in ColliderShapeSnapshot2D snapshot) |
| | | 164 | | { |
| | 6286 | 165 | | Fixed64 radiusX = ColliderScalePolicy.ScalePositive( |
| | 6286 | 166 | | _radius, |
| | 6286 | 167 | | snapshot.OwnerScale.X, |
| | 6286 | 168 | | snapshot.PartScale.X); |
| | 6284 | 169 | | Fixed64 radiusY = ColliderScalePolicy.ScalePositive( |
| | 6284 | 170 | | _radius, |
| | 6284 | 171 | | snapshot.OwnerScale.Y, |
| | 6284 | 172 | | snapshot.PartScale.Y); |
| | 6284 | 173 | | _preparedRadius = FixedMath.Max(radiusX, radiusY); |
| | 6284 | 174 | | SetPreparedBounds(FixedBoundArea.FromCenterAndScopeClippedToDomain( |
| | 6284 | 175 | | snapshot.Center, |
| | 6284 | 176 | | new Vector2d(_preparedRadius, _preparedRadius))); |
| | 6284 | 177 | | } |
| | | 178 | | |
| | | 179 | | private protected override void PublishShape() => |
| | 6175 | 180 | | _scaledRadius = _preparedRadius; |
| | | 181 | | |
| | | 182 | | /// <inheritdoc/> |
| | | 183 | | protected override void RecordShapeData(IChronicler chronicler) |
| | | 184 | | { |
| | 119 | 185 | | Fixed64 radius = _radius; |
| | 119 | 186 | | RecordValues.Look(chronicler, ref radius, "Radius", Fixed64.Half); |
| | 119 | 187 | | if (chronicler.Mode == SerializationMode.Loading) |
| | | 188 | | { |
| | 69 | 189 | | SwiftThrowHelper.ThrowIfArgument(radius <= Fixed64.Zero, nameof(radius), "2D circle radius must be greater t |
| | 69 | 190 | | _radius = radius; |
| | | 191 | | } |
| | 119 | 192 | | } |
| | | 193 | | } |