| | | 1 | | //======================================================================= |
| | | 2 | | // PhysicsMixedHit.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 FixedMathSharp; |
| | | 9 | | using Gravitas.Colliders; |
| | | 10 | | using Gravitas.CollisionHandling; |
| | | 11 | | using System; |
| | | 12 | | using System.Runtime.CompilerServices; |
| | | 13 | | |
| | | 14 | | namespace Gravitas.Queries; |
| | | 15 | | |
| | | 16 | | /// <summary> |
| | | 17 | | /// Result from an explicit mixed 3D/2D query. |
| | | 18 | | /// </summary> |
| | | 19 | | public readonly struct PhysicsMixedHit |
| | | 20 | | { |
| | | 21 | | /// <summary> |
| | | 22 | | /// Creates a mixed query hit from world-space surface witnesses. |
| | | 23 | | /// </summary> |
| | | 24 | | public PhysicsMixedHit( |
| | | 25 | | LSCollider? collider3D, |
| | | 26 | | LSCollider2D? collider2D, |
| | | 27 | | Vector3d point3D, |
| | | 28 | | Vector3d point2D, |
| | | 29 | | Vector3d normal3DTo2D, |
| | | 30 | | PhysicsQueryReducerKind reducerKind, |
| | | 31 | | Fixed64 distance, |
| | | 32 | | Vector3d direction3D) |
| | 27 | 33 | | : this( |
| | 27 | 34 | | collider3D, |
| | 27 | 35 | | collider2D, |
| | 27 | 36 | | ContactAnchor.FromWorldPoint(point3D), |
| | 27 | 37 | | ContactAnchor.FromWorldPoint(point2D), |
| | 27 | 38 | | normal3DTo2D, |
| | 27 | 39 | | reducerKind, |
| | 27 | 40 | | distance, |
| | 27 | 41 | | direction3D) |
| | | 42 | | { |
| | 27 | 43 | | } |
| | | 44 | | |
| | | 45 | | /// <summary> |
| | | 46 | | /// Creates a mixed query hit with rigid-frame surface witnesses. |
| | | 47 | | /// </summary> |
| | | 48 | | /// <exception cref="ArgumentException"> |
| | | 49 | | /// Either rigid-frame anchor is invalid. |
| | | 50 | | /// </exception> |
| | | 51 | | public PhysicsMixedHit( |
| | | 52 | | LSCollider? collider3D, |
| | | 53 | | LSCollider2D? collider2D, |
| | | 54 | | ContactAnchor anchor3D, |
| | | 55 | | ContactAnchor anchor2D, |
| | | 56 | | Vector3d normal3DTo2D, |
| | | 57 | | PhysicsQueryReducerKind reducerKind, |
| | | 58 | | Fixed64 distance, |
| | | 59 | | Vector3d direction3D) |
| | | 60 | | { |
| | 1609 | 61 | | if (!anchor3D.IsValid) |
| | 1 | 62 | | throw new ArgumentException("The 3D hit anchor must be valid.", nameof(anchor3D)); |
| | 1608 | 63 | | if (!anchor2D.IsValid) |
| | 1 | 64 | | throw new ArgumentException("The 2D hit anchor must be valid.", nameof(anchor2D)); |
| | | 65 | | |
| | 1607 | 66 | | Collider3D = collider3D; |
| | 1607 | 67 | | Collider2D = collider2D; |
| | 1607 | 68 | | Body3D = collider3D?.Body; |
| | 1607 | 69 | | Body2D = collider2D?.Body; |
| | 1607 | 70 | | Anchor3D = anchor3D; |
| | 1607 | 71 | | Anchor2D = anchor2D; |
| | 1607 | 72 | | Normal3DTo2D = normal3DTo2D; |
| | 1607 | 73 | | ReducerKind = reducerKind; |
| | 1607 | 74 | | Distance = distance; |
| | 1607 | 75 | | Direction3D = direction3D; |
| | 1607 | 76 | | } |
| | | 77 | | |
| | | 78 | | /// <summary> |
| | | 79 | | /// Gets the 3D collider participating in the hit, if registered. |
| | | 80 | | /// </summary> |
| | | 81 | | public LSCollider? Collider3D { get; } |
| | | 82 | | |
| | | 83 | | /// <summary> |
| | | 84 | | /// Gets the 2D collider participating in the hit, if registered. |
| | | 85 | | /// </summary> |
| | | 86 | | public LSCollider2D? Collider2D { get; } |
| | | 87 | | |
| | | 88 | | /// <summary> |
| | | 89 | | /// Gets the 3D collider's body, if any. |
| | | 90 | | /// </summary> |
| | | 91 | | public SolidBody? Body3D { get; } |
| | | 92 | | |
| | | 93 | | /// <summary> |
| | | 94 | | /// Gets the 2D collider's body, if any. |
| | | 95 | | /// </summary> |
| | | 96 | | public SolidBody2D? Body2D { get; } |
| | | 97 | | |
| | | 98 | | /// <summary> |
| | | 99 | | /// Gets the rigid-frame witness on the 3D shape. |
| | | 100 | | /// </summary> |
| | | 101 | | public ContactAnchor Anchor3D { get; } |
| | | 102 | | |
| | | 103 | | /// <summary> |
| | | 104 | | /// Gets the rigid-frame witness on the embedded 2D volume. |
| | | 105 | | /// </summary> |
| | | 106 | | public ContactAnchor Anchor2D { get; } |
| | | 107 | | |
| | | 108 | | /// <summary> |
| | | 109 | | /// Gets the materialized 3D witness. |
| | | 110 | | /// </summary> |
| | | 111 | | /// <exception cref="InvalidOperationException"> |
| | | 112 | | /// The conceptual witness lies outside the representable coordinate range. |
| | | 113 | | /// </exception> |
| | 22 | 114 | | public Vector3d Point3D => GetRequiredWorldPoint(Anchor3D, nameof(Point3D)); |
| | | 115 | | |
| | | 116 | | /// <summary> |
| | | 117 | | /// Gets the materialized embedded-2D witness. |
| | | 118 | | /// </summary> |
| | | 119 | | /// <exception cref="InvalidOperationException"> |
| | | 120 | | /// The conceptual witness lies outside the representable coordinate range. |
| | | 121 | | /// </exception> |
| | 9 | 122 | | public Vector3d Point2D => GetRequiredWorldPoint(Anchor2D, nameof(Point2D)); |
| | | 123 | | |
| | | 124 | | /// <summary> |
| | | 125 | | /// Attempts to materialize the 3D witness without saturation. |
| | | 126 | | /// </summary> |
| | | 127 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | 5 | 128 | | public bool TryGetPoint3D(out Vector3d point) => Anchor3D.TryGetWorldPoint(out point); |
| | | 129 | | |
| | | 130 | | /// <summary> |
| | | 131 | | /// Attempts to materialize the embedded-2D witness without saturation. |
| | | 132 | | /// </summary> |
| | | 133 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | 5 | 134 | | public bool TryGetPoint2D(out Vector3d point) => Anchor2D.TryGetWorldPoint(out point); |
| | | 135 | | |
| | | 136 | | /// <summary> |
| | | 137 | | /// Normal pointing from the 3D collider or swept 3D source toward the embedded 2D volume. |
| | | 138 | | /// </summary> |
| | | 139 | | public Vector3d Normal3DTo2D { get; } |
| | | 140 | | |
| | | 141 | | /// <summary> |
| | | 142 | | /// Gets whether this mixed query hit was produced by an exact shape reducer or a conservative fallback. |
| | | 143 | | /// </summary> |
| | | 144 | | public PhysicsQueryReducerKind ReducerKind { get; } |
| | | 145 | | |
| | | 146 | | /// <summary> |
| | | 147 | | /// Distance travelled by the swept source center before impact. |
| | | 148 | | /// </summary> |
| | | 149 | | public Fixed64 Distance { get; } |
| | | 150 | | |
| | | 151 | | /// <summary> |
| | | 152 | | /// Gets the world-space sweep direction associated with the hit. |
| | | 153 | | /// </summary> |
| | | 154 | | public Vector3d Direction3D { get; } |
| | | 155 | | |
| | | 156 | | /// <summary> |
| | | 157 | | /// Surface normal suitable for clamping a 3D source moving into an embedded 2D target. |
| | | 158 | | /// </summary> |
| | | 159 | | public Vector3d NormalFor3DSource |
| | | 160 | | { |
| | | 161 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | 25 | 162 | | get => -Normal3DTo2D; |
| | | 163 | | } |
| | | 164 | | |
| | | 165 | | /// <summary> |
| | | 166 | | /// Planar normal suitable for clamping a 2D source moving into a 3D target. |
| | | 167 | | /// </summary> |
| | | 168 | | public Vector2d NormalFor2DSource |
| | | 169 | | { |
| | | 170 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 171 | | get |
| | | 172 | | { |
| | 49 | 173 | | Vector2d normal = new(Normal3DTo2D.X, Normal3DTo2D.Z); |
| | 49 | 174 | | return normal.MagnitudeSquared > Fixed64.Epsilon ? normal.Normalized : Vector2d.Zero; |
| | | 175 | | } |
| | | 176 | | } |
| | | 177 | | |
| | | 178 | | private static Vector3d GetRequiredWorldPoint(ContactAnchor anchor, string propertyName) |
| | | 179 | | { |
| | 31 | 180 | | if (anchor.TryGetWorldPoint(out Vector3d point)) |
| | 29 | 181 | | return point; |
| | | 182 | | |
| | 2 | 183 | | throw new InvalidOperationException( |
| | 2 | 184 | | $"{propertyName} is outside the representable coordinate range. Use the corresponding TryGet method."); |
| | | 185 | | } |
| | | 186 | | } |