Ray.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. using System;
  2. using System.Diagnostics;
  3. using System.Diagnostics.CodeAnalysis;
  4. using System.Runtime.Serialization;
  5. using Microsoft.Xna.Framework;
  6. namespace MonoGame.Extended;
  7. /// <summary>
  8. /// Represents a ray defined by an origin point and a direction vector.
  9. /// </summary>
  10. /// <remarks>
  11. /// A ray extends infinitely in one direction from its origin point. Unlike line segments, rays have no endpoint and
  12. /// continue indefinitely along their direction vector.
  13. /// </remarks>
  14. [DataContract]
  15. [DebuggerDisplay("(DebugDisplayString,nq)")]
  16. public struct Ray : IEquatable<Ray>
  17. {
  18. #region Fields
  19. private static readonly Ray s_empty = new Ray(Vector2.Zero, Vector2.Zero);
  20. /// <summary>
  21. /// The starting point of the ray.
  22. /// </summary>
  23. [DataMember]
  24. public Vector2 Origin;
  25. /// <summary>
  26. /// The direction vector that defines which way the ray extends from its origin.
  27. /// </summary>
  28. [DataMember]
  29. public Vector2 Direction;
  30. #endregion
  31. #region Properties
  32. /// <summary>
  33. /// Gets an empty ray representing a degenerate case with zero direction.
  34. /// </summary>
  35. public static Ray Empty => s_empty;
  36. /// <summary>
  37. /// Gets the direction vector normalized to unit length.
  38. /// </summary>
  39. public readonly Vector2 DirectionNormalized => Vector2.Normalize(Direction);
  40. /// <summary>
  41. /// Gets a value indicating whether this ray represents a degenerate case with no direction, having
  42. /// zero-length direction vector.
  43. /// </summary>
  44. public readonly bool IsEmpty => Direction.LengthSquared() <= 0.0f;
  45. #endregion
  46. #region Constructors
  47. /// <summary>
  48. /// Initializes a new ray with the specified origin and direction vector.
  49. /// </summary>
  50. /// <param name="origin">The starting point of the ray.</param>
  51. /// <param name="direction">The direction vector defining the ray's extension.</param>
  52. public Ray(Vector2 origin, Vector2 direction)
  53. {
  54. Origin = origin;
  55. Direction = direction;
  56. }
  57. #endregion
  58. #region Create Methods
  59. /// <summary>
  60. /// Creates a new ray with the specified origin and a normalized direction vector.
  61. /// </summary>
  62. /// <param name="origin">The starting point of the ray.</param>
  63. /// <param name="direction">The direction vector to normalize and use for the ray.</param>
  64. /// <returns>A new ray with the normalized direction vector.</returns>
  65. /// <remarks>
  66. /// This method ensures the direction vector has unit length for consistent
  67. /// distance calculations along the ray.
  68. /// </remarks>
  69. public static Ray CreateNormalized(Vector2 origin, Vector2 direction)
  70. {
  71. direction.Normalize();
  72. return new Ray(origin, direction);
  73. }
  74. #endregion
  75. #region Intersection Methods
  76. /// <summary>
  77. /// Determines whether this ray intersects with another ray.
  78. /// </summary>
  79. /// <param name="other">The other ray to test for intersection.</param>
  80. /// <returns>
  81. /// <see langword="true"/> if this ray intersects the other; otherwise, <see langword="false"/>.
  82. /// </returns>
  83. public readonly bool Intersects(Ray other) => IntersectionTests.RayRay(in this, in other);
  84. /// <summary>
  85. /// Determines whether this ray intersects with a circle.
  86. /// </summary>
  87. /// <param name="circle">The circle to test for intersection.</param>
  88. /// <returns>
  89. /// <see langword="true"/> if this ray intersects the circle; otherwise, <see langword="false"/>.
  90. /// </returns>
  91. public readonly bool Intersects(Circle circle) => IntersectionTests.CircleRay(in circle, in this);
  92. /// <summary>
  93. /// Determines whether this ray intersects with an ellipse.
  94. /// </summary>
  95. /// <param name="ellipse">The ellipse to test for intersection.</param>
  96. /// <returns>
  97. /// <see langword="true"/> if this ray intersects the ellipse; otherwise, <see langword="false"/>.
  98. /// </returns>
  99. public readonly bool Intersects(Ellipse ellipse) => IntersectionTests.EllipseRay(in ellipse, in this);
  100. /// <summary>
  101. /// Determines whether this ray intersects with a rectangle.
  102. /// </summary>
  103. /// <param name="rectangle">The rectangle to test for intersection.</param>
  104. /// <returns>
  105. /// <see langword="true"/> if this ray intersects the rectangle; otherwise, <see langword="false"/>.
  106. /// </returns>
  107. public readonly bool Intersects(RectangleF rectangle) => IntersectionTests.RectangleFRay(in rectangle, in this);
  108. /// <summary>
  109. /// Determines whether this ray intersects with a line segment.
  110. /// </summary>
  111. /// <param name="lineSegment">The line segment to test for intersection.</param>
  112. /// <returns>
  113. /// <see langword="true"/> if this ray intersects the line segment; otherwise, <see langword="false"/>.
  114. /// </returns>
  115. public readonly bool Intersects(LineSegment lineSegment) => IntersectionTests.RayLineSegment(in this, in lineSegment);
  116. #endregion
  117. #region Closest Point To Methods
  118. /// <summary>
  119. /// Computes the point on the ray at the specified distance from the origin.
  120. /// </summary>
  121. /// <param name="distance">The distance along the ray from the origin point.</param>
  122. /// <returns>The point on the ray at the specified distance.</returns>
  123. /// <remarks>
  124. /// Negative distance values will return points in the opposite direction of the ray, effectively extending
  125. /// the ray backwards from its origin.
  126. /// </remarks>
  127. public readonly Vector2 GetPointAt(float distance)
  128. {
  129. return Origin + Direction * distance;
  130. }
  131. /// <summary>
  132. /// Computes the closest point on the ray to the specified point.
  133. /// </summary>
  134. /// <param name="point">The query point for closest-point computation.</param>
  135. /// <returns>The point on the ray closest to the query point.</returns>
  136. /// <remarks>
  137. /// If the closest point would be behind the ray's origin (negative distance), the origin point is returned
  138. /// instead, as rays only extend forward.
  139. /// </remarks>
  140. public readonly Vector2 ClosestPointTo(Vector2 point)
  141. {
  142. float t = Vector2.Dot(point - Origin, Direction);
  143. // Clamp to ray (t >= 0)
  144. if (t < 0.0f)
  145. {
  146. return Origin;
  147. }
  148. return Origin + Direction * t;
  149. }
  150. #endregion
  151. #region Offset Methods
  152. /// <summary>
  153. /// Translates the ray by moving its origin position, preserving direction.
  154. /// </summary>
  155. /// <param name="offset">The translation vector to apply to the origin.</param>
  156. public void Offset(Vector2 offset)
  157. {
  158. Origin += offset;
  159. }
  160. /// <summary>
  161. /// Creates a new ray translated by the specified offset vector.
  162. /// </summary>
  163. /// <param name="ray">The ray to translate.</param>
  164. /// <param name="offset">The translation vector to apply.</param>
  165. /// <returns>A new ray with the translated origin position.</returns>
  166. public static Ray Offset(Ray ray, Vector2 offset)
  167. {
  168. return ray with { Origin = ray.Origin + offset };
  169. }
  170. #endregion
  171. #region Normalize Methods
  172. /// <summary>
  173. /// Normalizes the direction vector to unit length.
  174. /// </summary>
  175. public void Normalize()
  176. {
  177. Direction.Normalize();
  178. }
  179. /// <summary>
  180. /// Creates a new ray with the direction vector normalized to unit length.
  181. /// </summary>
  182. /// <param name="ray">The ray to normalize.</param>
  183. /// <returns>A new ray with normalized direction vector.</returns>
  184. public static Ray Normalize(Ray ray)
  185. {
  186. return ray with { Direction = Vector2.Normalize(ray.Direction) };
  187. }
  188. #endregion
  189. #region Transform Methods
  190. /// <summary>
  191. /// Applies a 2D transformation matrix to the ray, transforming both origin and direction.
  192. /// </summary>
  193. /// <param name="matrix">The transformation matrix to apply.</param>
  194. public void Transform(Matrix3x2 matrix)
  195. {
  196. Transform(ref matrix);
  197. }
  198. /// <summary>
  199. /// Applies a 2D transformation matrix to the ray using reference parameters.
  200. /// </summary>
  201. /// <param name="matrix">The transformation matrix to apply.</param>
  202. /// <remarks>
  203. /// The direction vector is transformed as a vector (ignoring translation components)
  204. /// to preserve its directional properties.
  205. /// </remarks>
  206. public void Transform(ref Matrix3x2 matrix)
  207. {
  208. Origin = matrix.Transform(Origin);
  209. // Transform direction (as a vector, not a point (ignore translation))
  210. float directionX = Direction.X * matrix.M11 + Direction.Y * matrix.M21;
  211. float directionY = Direction.X * matrix.M12 + Direction.Y * matrix.M22;
  212. Direction = new Vector2(directionX, directionY);
  213. }
  214. /// <summary>
  215. /// Creates a new ray by applying a 2D transformation matrix.
  216. /// </summary>
  217. /// <param name="ray">The ray to transform.</param>
  218. /// <param name="matrix">The transformation matrix to apply.</param>
  219. /// <returns>A new transformed ray.</returns>
  220. public static Ray Transform(Ray ray, Matrix3x2 matrix)
  221. {
  222. return Transform(ray, ref matrix);
  223. }
  224. /// <summary>
  225. /// Computes a transformed ray using reference parameters for performance.
  226. /// </summary>
  227. /// <param name="ray">The ray to transform.</param>
  228. /// <param name="matrix">The transformation matrix to apply.</param>
  229. public static Ray Transform(Ray ray, ref Matrix3x2 matrix)
  230. {
  231. Vector2 origin = matrix.Transform(ray.Origin);
  232. // Transform direction (as a vector, not a point (ignore translation))
  233. float directionX = ray.Direction.X * matrix.M11 + ray.Direction.Y * matrix.M21;
  234. float directionY = ray.Direction.X * matrix.M12 + ray.Direction.Y * matrix.M22;
  235. Vector2 direction = new Vector2(directionX, directionY);
  236. return new Ray(origin, direction);
  237. }
  238. #endregion
  239. #region Equality Methods
  240. /// <summary>
  241. /// Determines whether this ray is equal to the specified object.
  242. /// </summary>
  243. /// <param name="obj">The object to compare.</param>
  244. /// <returns>
  245. /// <c>true</c> if the object is a <see cref="Ray"/> with the same origin and direction; otherwise, <c>false</c>.
  246. /// </returns>
  247. public override readonly bool Equals([NotNullWhen(true)] object obj)
  248. {
  249. return obj is Ray other && Equals(other);
  250. }
  251. /// <summary>
  252. /// Determines whether this ray is equal to another ray.
  253. /// </summary>
  254. /// <param name="other">The ray to compare with this ray.</param>
  255. /// <returns><c>true</c> if the rays have the same origin and direction; otherwise, <c>false</c>.</returns>
  256. public readonly bool Equals(Ray other)
  257. {
  258. return Equals(other);
  259. }
  260. #endregion
  261. /// <summary>
  262. /// Computes the hash code for this ray.
  263. /// </summary>
  264. /// <returns>A hash code derived from the origin and direction.</returns>
  265. public override readonly int GetHashCode()
  266. {
  267. return HashCode.Combine(Origin, Direction);
  268. }
  269. /// <summary>
  270. /// Creates a string representation of this ray.
  271. /// </summary>
  272. /// <returns>A formatted string containing the origin and direction values.</returns>
  273. public override readonly string ToString()
  274. {
  275. return $"Ray(Origin: {Origin}, Direction: {Direction})";
  276. }
  277. internal readonly string DebugDisplayString => ToString();
  278. #region Operators
  279. /// <summary>
  280. /// Tests whether two rays are equal.
  281. /// </summary>
  282. /// <param name="left">The first ray.</param>
  283. /// <param name="right">The second ray.</param>
  284. /// <returns><c>true</c> if both rays have the same origin and direction; otherwise, <c>false</c>.</returns>
  285. public static bool operator ==(Ray left, Ray right)
  286. {
  287. return left.Equals(right);
  288. }
  289. /// <summary>
  290. /// Tests whether two rays are not equal.
  291. /// </summary>
  292. /// <param name="left">The first ray.</param>
  293. /// <param name="right">The second ray.</param>
  294. /// <returns><c>true</c> if the rays have different origins or directions; otherwise, <c>false</c>.</returns>
  295. public static bool operator !=(Ray left, Ray right)
  296. {
  297. return !left.Equals(right);
  298. }
  299. #endregion
  300. }