| | | 1 | | //======================================================================= |
| | | 2 | | // LSAABBoxCollider2D.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; |
| | | 12 | | using System.Runtime.CompilerServices; |
| | | 13 | | |
| | | 14 | | namespace Gravitas.Colliders; |
| | | 15 | | |
| | | 16 | | /// <summary> |
| | | 17 | | /// Pure 2D axis-aligned box collider. |
| | | 18 | | /// </summary> |
| | | 19 | | public sealed class LSAABBoxCollider2D : LSCollider2D, IConvexVertexSource2D |
| | | 20 | | { |
| | | 21 | | private Vector2d _size; |
| | | 22 | | private Vector2d _halfExtents; |
| | | 23 | | private Vector2d _scaledHalfExtents; |
| | | 24 | | private Vector2d _preparedHalfExtents; |
| | | 25 | | |
| | | 26 | | /// <summary>Creates a pure 2D axis-aligned box with an authored size.</summary> |
| | 794 | 27 | | public LSAABBoxCollider2D(Vector2d size) |
| | | 28 | | { |
| | 794 | 29 | | Size = size; |
| | 792 | 30 | | } |
| | | 31 | | |
| | | 32 | | /// <summary>Creates a runtime box from an authored shape definition.</summary> |
| | 41 | 33 | | public LSAABBoxCollider2D(ColliderShapeDefinition2D definition) |
| | | 34 | | { |
| | 41 | 35 | | definition.EnsureKind(ColliderShapeDefinition2DKind.AABBox); |
| | 41 | 36 | | Material = definition.Material; |
| | 41 | 37 | | Size = definition.Size; |
| | 41 | 38 | | } |
| | | 39 | | |
| | | 40 | | /// <inheritdoc/> |
| | 3275 | 41 | | public override ColliderType2D Shape => ColliderType2D.AABox; |
| | | 42 | | |
| | | 43 | | /// <summary>Gets or sets the unscaled full box size.</summary> |
| | | 44 | | public Vector2d Size |
| | | 45 | | { |
| | | 46 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | 186 | 47 | | get => _size; |
| | | 48 | | set |
| | | 49 | | { |
| | 838 | 50 | | SwiftThrowHelper.ThrowIfArgument( |
| | 838 | 51 | | value.X <= Fixed64.Zero || value.Y <= Fixed64.Zero, |
| | 838 | 52 | | nameof(value), |
| | 838 | 53 | | "2D AABB size components must be greater than zero."); |
| | 836 | 54 | | if (_size == value) |
| | 2 | 55 | | return; |
| | | 56 | | |
| | 834 | 57 | | _size = value; |
| | 834 | 58 | | _halfExtents = value * Fixed64.Half; |
| | 834 | 59 | | MarkShapeDirty(); |
| | 834 | 60 | | } |
| | | 61 | | } |
| | | 62 | | |
| | | 63 | | /// <summary>Gets half of the authored unscaled size.</summary> |
| | 4 | 64 | | public Vector2d HalfExtents => _halfExtents; |
| | | 65 | | |
| | | 66 | | /// <summary>Gets the half extents after owner and compound-part scaling.</summary> |
| | | 67 | | public Vector2d ScaledHalfExtents |
| | | 68 | | { |
| | | 69 | | get |
| | | 70 | | { |
| | 23948 | 71 | | if (HasCommittedShape) |
| | 16637 | 72 | | return _scaledHalfExtents; |
| | | 73 | | |
| | 7311 | 74 | | GetCurrentScaleFactors( |
| | 7311 | 75 | | out Vector2d ownerScale, |
| | 7311 | 76 | | out Vector2d partScale); |
| | 7311 | 77 | | return ColliderScalePolicy.Scale( |
| | 7311 | 78 | | _size, |
| | 7311 | 79 | | ownerScale, |
| | 7311 | 80 | | partScale, |
| | 7311 | 81 | | Fixed64.Two); |
| | | 82 | | } |
| | | 83 | | } |
| | | 84 | | |
| | 12976 | 85 | | int IConvexVertexSource2D.VertexCount => 4; |
| | | 86 | | |
| | 5950 | 87 | | Fixed64 IConvexVertexSource2D.Rotation => Fixed64.Zero; |
| | | 88 | | |
| | | 89 | | /// <inheritdoc/> |
| | | 90 | | public override bool ContainsPoint(Vector2d point) |
| | | 91 | | { |
| | 1027 | 92 | | Span<Vector2d> offsets = stackalloc Vector2d[4]; |
| | 1027 | 93 | | GetCenterRelativeVertices(offsets); |
| | 1027 | 94 | | return FixedConvex2dRelations.ContainsPoint(point, Center, offsets); |
| | | 95 | | } |
| | | 96 | | |
| | | 97 | | /// <inheritdoc/> |
| | | 98 | | public override Vector2d GetClosestPoint(Vector2d point) |
| | | 99 | | { |
| | 2 | 100 | | Span<Vector2d> offsets = stackalloc Vector2d[4]; |
| | 2 | 101 | | GetCenterRelativeVertices(offsets); |
| | 2 | 102 | | if (FixedConvex2dRelations.ContainsPoint(point, Center, offsets)) |
| | 1 | 103 | | return point; |
| | | 104 | | |
| | 1 | 105 | | Vector2d offset = |
| | 1 | 106 | | FixedConvex2dRelations.GetClosestPointOffset( |
| | 1 | 107 | | point, |
| | 1 | 108 | | Center, |
| | 1 | 109 | | offsets); |
| | | 110 | | // An exterior closest point lies on the segment from the representable |
| | | 111 | | // query to the representable box center, so its final sum is in range. |
| | 1 | 112 | | _ = Vector2d.TryAdd(Center, offset, out Vector2d closest); |
| | 1 | 113 | | return closest; |
| | | 114 | | } |
| | | 115 | | |
| | | 116 | | /// <inheritdoc/> |
| | | 117 | | public override Vector2d GetSupportPoint(Vector2d direction) |
| | | 118 | | { |
| | 8 | 119 | | Vector2d halfExtents = ScaledHalfExtents; |
| | 8 | 120 | | Vector2d offset = new( |
| | 8 | 121 | | direction.X >= Fixed64.Zero |
| | 8 | 122 | | ? halfExtents.X |
| | 8 | 123 | | : -halfExtents.X, |
| | 8 | 124 | | direction.Y >= Fixed64.Zero |
| | 8 | 125 | | ? halfExtents.Y |
| | 8 | 126 | | : -halfExtents.Y); |
| | 8 | 127 | | if (Vector2d.TryAdd(Center, offset, out Vector2d support)) |
| | 6 | 128 | | return support; |
| | | 129 | | |
| | 2 | 130 | | throw new System.InvalidOperationException( |
| | 2 | 131 | | "The box support point is outside the Fixed64 coordinate domain."); |
| | | 132 | | } |
| | | 133 | | |
| | | 134 | | Vector2d IConvexVertexSource2D.GetScaledLocalVertexUnchecked(int index) |
| | | 135 | | { |
| | 20342 | 136 | | Vector2d halfExtents = ScaledHalfExtents; |
| | 20342 | 137 | | return index switch |
| | 20342 | 138 | | { |
| | 5087 | 139 | | 0 => new Vector2d(-halfExtents.X, -halfExtents.Y), |
| | 5085 | 140 | | 1 => new Vector2d(halfExtents.X, -halfExtents.Y), |
| | 5085 | 141 | | 2 => new Vector2d(halfExtents.X, halfExtents.Y), |
| | 5085 | 142 | | _ => new Vector2d(-halfExtents.X, halfExtents.Y) |
| | 20342 | 143 | | }; |
| | | 144 | | } |
| | | 145 | | |
| | | 146 | | FixedPointAnchor2d IConvexVertexSource2D.GetSupportAnchor(Vector2d direction) |
| | | 147 | | { |
| | 54 | 148 | | Vector2d halfExtents = ScaledHalfExtents; |
| | 54 | 149 | | return new FixedPointAnchor2d( |
| | 54 | 150 | | Center, |
| | 54 | 151 | | Fixed64.Zero, |
| | 54 | 152 | | new Vector2d( |
| | 54 | 153 | | direction.X >= Fixed64.Zero |
| | 54 | 154 | | ? halfExtents.X |
| | 54 | 155 | | : -halfExtents.X, |
| | 54 | 156 | | direction.Y >= Fixed64.Zero |
| | 54 | 157 | | ? halfExtents.Y |
| | 54 | 158 | | : -halfExtents.Y)); |
| | | 159 | | } |
| | | 160 | | |
| | | 161 | | private void GetCenterRelativeVertices(Span<Vector2d> offsets) |
| | | 162 | | { |
| | 1029 | 163 | | Vector2d halfExtents = ScaledHalfExtents; |
| | 1029 | 164 | | offsets[0] = new Vector2d(-halfExtents.X, -halfExtents.Y); |
| | 1029 | 165 | | offsets[1] = new Vector2d(halfExtents.X, -halfExtents.Y); |
| | 1029 | 166 | | offsets[2] = new Vector2d(halfExtents.X, halfExtents.Y); |
| | 1029 | 167 | | offsets[3] = new Vector2d(-halfExtents.X, halfExtents.Y); |
| | 1029 | 168 | | } |
| | | 169 | | |
| | | 170 | | internal override ExactMassWeight CalculateAreaForMassProperties() |
| | | 171 | | { |
| | 160 | 172 | | Vector2d halfExtents = ScaledHalfExtents; |
| | 160 | 173 | | return ExactMassWeight.FromProduct( |
| | 160 | 174 | | (Fixed64)4, |
| | 160 | 175 | | halfExtents.X, |
| | 160 | 176 | | halfExtents.Y); |
| | | 177 | | } |
| | | 178 | | |
| | | 179 | | internal override ExactMassWeight CalculatePreparedAreaForMassProperties() => |
| | 82 | 180 | | ExactMassWeight.FromProduct( |
| | 82 | 181 | | (Fixed64)4, |
| | 82 | 182 | | _preparedHalfExtents.X, |
| | 82 | 183 | | _preparedHalfExtents.Y); |
| | | 184 | | |
| | | 185 | | internal override Fixed64 CalculateCenterOfMassMoment(Fixed64 mass) |
| | | 186 | | { |
| | 1460 | 187 | | Vector2d halfExtents = ScaledHalfExtents; |
| | 1460 | 188 | | bool representable = Fixed64.TryMultiplyDivide( |
| | 1460 | 189 | | mass, |
| | 1460 | 190 | | halfExtents.X, |
| | 1460 | 191 | | halfExtents.X, |
| | 1460 | 192 | | (Fixed64)3, |
| | 1460 | 193 | | out Fixed64 xMoment) |
| | 1460 | 194 | | & Fixed64.TryMultiplyDivide( |
| | 1460 | 195 | | mass, |
| | 1460 | 196 | | halfExtents.Y, |
| | 1460 | 197 | | halfExtents.Y, |
| | 1460 | 198 | | (Fixed64)3, |
| | 1460 | 199 | | out Fixed64 yMoment) |
| | 1460 | 200 | | & Fixed64.TryAdd(xMoment, yMoment, out Fixed64 momentAboutCenterOfMass); |
| | 1460 | 201 | | if (!representable) |
| | 1 | 202 | | momentAboutCenterOfMass = Fixed64.MaxValue; |
| | | 203 | | |
| | 1460 | 204 | | return momentAboutCenterOfMass; |
| | | 205 | | } |
| | | 206 | | |
| | | 207 | | private protected override void PrepareShape(in ColliderShapeSnapshot2D snapshot) |
| | | 208 | | { |
| | 882 | 209 | | _preparedHalfExtents = ColliderScalePolicy.ScalePositive( |
| | 882 | 210 | | _size, |
| | 882 | 211 | | snapshot.OwnerScale, |
| | 882 | 212 | | snapshot.PartScale, |
| | 882 | 213 | | Fixed64.Two); |
| | 882 | 214 | | SetPreparedBounds(FixedBoundArea.FromCenterAndScopeClippedToDomain( |
| | 882 | 215 | | snapshot.Center, |
| | 882 | 216 | | _preparedHalfExtents)); |
| | 882 | 217 | | } |
| | | 218 | | |
| | | 219 | | private protected override void PublishShape() => |
| | 877 | 220 | | _scaledHalfExtents = _preparedHalfExtents; |
| | | 221 | | |
| | | 222 | | /// <inheritdoc/> |
| | | 223 | | protected override void RecordShapeData(IChronicler chronicler) |
| | | 224 | | { |
| | 7 | 225 | | Vector2d size = _size; |
| | 7 | 226 | | RecordValues.Look(chronicler, ref size, "Size", Vector2d.One); |
| | 7 | 227 | | if (chronicler.Mode == SerializationMode.Loading) |
| | | 228 | | { |
| | 5 | 229 | | SwiftThrowHelper.ThrowIfArgument( |
| | 5 | 230 | | size.X <= Fixed64.Zero || size.Y <= Fixed64.Zero, |
| | 5 | 231 | | nameof(size), |
| | 5 | 232 | | "2D AABB size components must be greater than zero."); |
| | 3 | 233 | | _size = size; |
| | 3 | 234 | | _halfExtents = size * Fixed64.Half; |
| | | 235 | | } |
| | 5 | 236 | | } |
| | | 237 | | } |