GeometricPrimitives.cs 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. using System;
  2. using System.Runtime.CompilerServices;
  3. using Microsoft.Xna.Framework;
  4. namespace MonoGame.Extended.Collisions;
  5. internal static class GeometricPrimitives
  6. {
  7. #region Projection Utilities (for SAT)
  8. /// <summary>
  9. /// Projects a set of vertices onto an axis and returns the minium and maximum extents.
  10. /// </summary>
  11. /// <param name="vertices">The vertices to project.</param>
  12. /// <param name="axis">The axis to project onto. Does not need to be normalized.</param>
  13. /// <param name="min">
  14. /// When this method returns, contains the minimum projection value.
  15. /// </param>
  16. /// <param name="max">
  17. /// When this method returns, contains the maximum projection value.
  18. /// </param>
  19. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  20. internal static void ProjectOntoAxis(ReadOnlySpan<Vector2> vertices, Vector2 axis, out float min, out float max)
  21. {
  22. min = float.MaxValue;
  23. max = float.MinValue;
  24. for (int i = 0; i < vertices.Length; i++)
  25. {
  26. float projection = Vector2.Dot(vertices[i], axis);
  27. if (projection < min) min = projection;
  28. if (projection > max) max = projection;
  29. }
  30. }
  31. /// <summary>
  32. /// Projects a single point onto an axis.
  33. /// </summary>
  34. /// <param name="point">The point to project.</param>
  35. /// <param name="axis">The axis to project onto. Does not need to be normalized.</param>
  36. /// <returns>The scalar projection value.</returns>
  37. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  38. internal static float ProjectPointOntoAxis(Vector2 point, Vector2 axis)
  39. {
  40. return Vector2.Dot(point, axis);
  41. }
  42. /// <summary>
  43. /// Projects an OBB onto an axis and returns the minimum and maximum extents.
  44. /// </summary>
  45. /// <param name="obb">The oriented bounding rectangle to project.</param>
  46. /// <param name="axis">The axis to project onto. Does not need to be normalized.</param>
  47. /// <param name="min">
  48. /// When this method returns, contains the minimum projection value.
  49. /// </param>
  50. /// <param name="max">
  51. /// When this method returns, contains the maximum projection value.
  52. /// </param>
  53. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  54. internal static void ProjectOBBOntoAxis(in OrientedBoundingRectangle obb, Vector2 axis, out float min, out float max)
  55. {
  56. // Project center onto axis
  57. float centerProjection = Vector2.Dot(obb.Center, axis);
  58. // Project half-extents onto axis
  59. float extentProjection = MathF.Abs(Vector2.Dot(obb.AxisX * obb.HalfExtents.X, axis))
  60. + MathF.Abs(Vector2.Dot(obb.AxisY * obb.HalfExtents.Y, axis));
  61. min = centerProjection - extentProjection;
  62. max = centerProjection + extentProjection;
  63. }
  64. /// <summary>
  65. /// Tests if two 1D intervals overlap.
  66. /// </summary>
  67. /// <param name="min1">Minimum of first interval.</param>
  68. /// <param name="max1">Maximum of first interval.</param>
  69. /// <param name="min2">Minimum of second intervale.</param>
  70. /// <param name="max2">Maximum of second interval.</param>
  71. /// <returns><see langword="true"/>if the intervals overlap; otherwise, <see langword="false"/>.</returns>
  72. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  73. internal static bool IntervalsOverlap(float min1, float max1, float min2, float max2)
  74. {
  75. return !(max1 < min2 || max2 < min1);
  76. }
  77. #endregion
  78. #region Distance Calculations
  79. /// <summary>
  80. /// Computes the squared distance between points.
  81. /// </summary>
  82. /// <param name="a">The first point.</param>
  83. /// <param name="b">The second point.</param>
  84. /// <returns>The squared Euclidean distance between points.</returns>
  85. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  86. internal static float DistanceSquared(Vector2 a, Vector2 b)
  87. {
  88. return Vector2.DistanceSquared(a, b);
  89. }
  90. /// <summary>
  91. /// Computes the squared distances from a point to a line segment.
  92. /// </summary>
  93. /// <param name="point">The point.</param>
  94. /// <param name="segmentStart">The segment start point.</param>
  95. /// <param name="segmentEnd">The segment end point.</param>
  96. /// <returns>The squared distance from point to the closest point on the segment.</returns>
  97. /// <remarks>
  98. /// More efficient than computing actual distance when only comparison is needed. <br/>
  99. /// Used on capsule intersection tests (Phase 3).
  100. /// </remarks>
  101. internal static float DistanceSquaredPointToSegment(Vector2 point, Vector2 segmentStart, Vector2 segmentEnd)
  102. {
  103. Vector2 ab = segmentEnd - segmentStart;
  104. Vector2 ap = point - segmentStart;
  105. float t = Vector2.Dot(ap, ab);
  106. if (t <= 0.0f)
  107. {
  108. // point projects before segment start
  109. return Vector2.DistanceSquared(point, segmentStart);
  110. }
  111. float denom = Vector2.Dot(ab, ab);
  112. if (t >= denom)
  113. {
  114. // Pont projects beyond segment
  115. return Vector2.DistanceSquared(point, segmentEnd);
  116. }
  117. // Point projects on segment interior
  118. t /= denom;
  119. Vector2 projection = segmentStart + t * ab;
  120. return Vector2.DistanceSquared(point, projection);
  121. }
  122. #endregion
  123. #region Closest Point Queries
  124. /// <summary>
  125. /// Finds the closest point on an axis-aligned bounding rectangle to a given point.
  126. /// </summary>
  127. /// <param name="rect">The AABB.</param>
  128. /// <param name="point">The query point.</param>
  129. /// <returns>The closest point on or inside the rectangle.</returns>
  130. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  131. internal static Vector2 ClosestPointOnAABB(in BoundingRectangle rect, Vector2 point)
  132. {
  133. return new Vector2(
  134. Math.Clamp(point.X, rect.Min.X, rect.Max.X),
  135. Math.Clamp(point.Y, rect.Min.Y, rect.Max.Y)
  136. );
  137. }
  138. /// <summary>
  139. /// Finds the closest point on an oriented bounding rectangle to a given point.
  140. /// </summary>
  141. /// <param name="obb">The OBB.</param>
  142. /// <param name="point">The query point.</param>
  143. /// <returns>The closets point on or inside the rectangle.</returns>
  144. internal static Vector2 ClosestPointOnOBB(in OrientedBoundingRectangle obb, Vector2 point)
  145. {
  146. // Transform point into OBB's local coordinate system
  147. Vector2 d = point - obb.Center;
  148. // Project onto local axes and clamp to extents
  149. float distX = Vector2.Dot(d, obb.AxisX);
  150. float distY = Vector2.Dot(d, obb.AxisY);
  151. distX = Math.Clamp(distX, -obb.HalfExtents.X, obb.HalfExtents.X);
  152. distY = Math.Clamp(distY, -obb.HalfExtents.Y, obb.HalfExtents.Y);
  153. // Transform back to world space
  154. return obb.Center + distX * obb.AxisX + distY * obb.AxisY;
  155. }
  156. #endregion
  157. #region Rotation and Perpendicular Vectors
  158. /// <summary>
  159. /// Computes the perpendicular vector o the given 2D vector, rotated 90 degrees counter-clockwise.
  160. /// </summary>
  161. /// <param name="vector">The input vector.</param>
  162. /// <returns>The perpendicular vector (-vector.Y, vector.X).</returns>
  163. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  164. internal static Vector2 Perpendicular(Vector2 vector)
  165. {
  166. return new Vector2(-vector.Y, vector.X);
  167. }
  168. /// <summary>
  169. /// Normalizes a vector. Returns zero vector if input length is zero.
  170. /// </summary>
  171. /// <param name="vector">The vector to normalize.</param>
  172. /// <returns>The normalized vector, or zero if input length is zero.</returns>
  173. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  174. internal static Vector2 SafeNormalize(Vector2 vector)
  175. {
  176. float lengthSquared = vector.LengthSquared();
  177. if(lengthSquared < MathExtended.Epsilon * MathExtended.Epsilon)
  178. {
  179. return Vector2.Zero;
  180. }
  181. return vector / MathF.Sqrt(lengthSquared);
  182. }
  183. #endregion
  184. }