Ray2D.cs 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716
  1. // Copyright (c) Craftwork Games. All rights reserved.
  2. // Licensed under the MIT license.
  3. // See LICENSE file in the project root for full license information.
  4. using System;
  5. using System.Diagnostics;
  6. using System.Runtime.Serialization;
  7. using Microsoft.Xna.Framework;
  8. namespace MonoGame.Extended
  9. {
  10. /// <summary>
  11. /// Represents a semi-infinite ray in 2D space starting at an origin point and extending in a direction.
  12. /// </summary>
  13. [DataContract]
  14. [DebuggerDisplay("{DebugDisplayString,nq}")]
  15. public struct Ray2D : IEquatable<Ray2D>
  16. {
  17. #region Public Fields
  18. /// <summary>
  19. /// The direction vector defining which way this ray extends from its origin.
  20. /// Should be normalized for accurate distance calculations.
  21. /// </summary>
  22. [DataMember]
  23. public Vector2 Direction;
  24. /// <summary>
  25. /// The starting point of this ray in 2D space.
  26. /// </summary>
  27. [DataMember]
  28. public Vector2 Origin;
  29. #endregion
  30. #region Internal Properties
  31. internal readonly string DebugDisplayString
  32. {
  33. get
  34. {
  35. return string.Concat(
  36. "Origin( ", Origin.ToString(), " ) \r\n",
  37. "Direction( ", Direction.ToString(), " )"
  38. );
  39. }
  40. }
  41. #endregion
  42. #region Public Constructors
  43. /// <summary>
  44. /// Creates a new <see cref="Ray2D"/> with the specified origin and direction.
  45. /// </summary>
  46. /// <param name="origin">The starting point of the ray in 2D space.</param>
  47. /// <param name="direction">
  48. /// The direction vector defining which way the ray extends. Should be normalized for accurate distance calculations.
  49. /// </param>
  50. public Ray2D(Vector2 origin, Vector2 direction)
  51. {
  52. Origin = origin;
  53. Direction = direction;
  54. }
  55. #endregion
  56. #region Public Methods
  57. #endregion
  58. /// <summary>
  59. /// Creates a <see cref="Ray2D"/> from a starting point toward a target point.
  60. /// </summary>
  61. /// <param name="start">The starting point of the ray in 2D space.</param>
  62. /// <param name="through">A target point the ray should pass through.</param>
  63. /// <returns>
  64. /// A new <see cref="Ray2D"/> starting at the specified point with a unit direction vector
  65. /// pointing toward the target point.
  66. /// </returns>
  67. /// <exception cref="ArgumentException">
  68. /// Thrown when the points are too close to define a valid direction.
  69. /// </exception>
  70. public static Ray2D CreateFromPoints(Vector2 start, Vector2 through)
  71. {
  72. Vector2 direction = through - start;
  73. float lengthSq = direction.LengthSquared();
  74. if (lengthSq < Collision2D.Epsilon * Collision2D.Epsilon)
  75. {
  76. throw new ArgumentException("Points must be distinct to define a ray direction");
  77. }
  78. return new Ray2D(start, Vector2.Normalize(direction));
  79. }
  80. /// <summary>
  81. /// Computes a point along this ray at the specified parametric distance.
  82. /// </summary>
  83. /// <param name="distanceAlongRay">The parametric distance along the ray from the origin.</param>
  84. /// <returns>
  85. /// The point at position <c>Origin + distanceAlongRay * Direction</c>.
  86. /// </returns>
  87. /// <remarks>
  88. /// Negative values of <paramref name="distanceAlongRay"/> return points behind the ray's origin.
  89. /// </remarks>
  90. public readonly Vector2 GetPoint(float distanceAlongRay)
  91. {
  92. return Origin + distanceAlongRay * Direction;
  93. }
  94. /// <summary>
  95. /// Computes the closest point on this ray to a specified point.
  96. /// </summary>
  97. /// <param name="point">The point in 2D space to find the closest point to.</param>
  98. /// <param name="distanceAlongRay">
  99. /// When this method returns, contains the parametric distance along the ray to the closest point.
  100. /// Will be clamped to zero or greater, as rays only extend forward from the origin.
  101. /// </param>
  102. /// <returns>
  103. /// The closest point on the ray. If the point projects behind the ray's origin, returns the origin itself.
  104. /// </returns>
  105. public readonly Vector2 ClosestPoint(Vector2 point, out float distanceAlongRay)
  106. {
  107. Vector2 ab = Direction;
  108. float denom = Vector2.Dot(ab, ab);
  109. if (denom <= Collision2D.Epsilon)
  110. {
  111. // Direction of ray is effectively zero, so it's just a point.
  112. distanceAlongRay = 0.0f;
  113. return Origin;
  114. }
  115. // Project point ab, but deferring divide by Dot(ab, ab)
  116. distanceAlongRay = Vector2.Dot(point - Origin, ab);
  117. if (distanceAlongRay <= 0.0f)
  118. {
  119. // point projects before the ray origin, clamp to origin
  120. distanceAlongRay = 0.0f;
  121. return Origin;
  122. }
  123. // Point projects after the ray origin, must do deferred divide
  124. distanceAlongRay /= denom;
  125. return Origin + distanceAlongRay * ab;
  126. }
  127. /// <summary>
  128. /// Computes the squared distance from a point to the closest point on this ray.
  129. /// </summary>
  130. /// <param name="point">The point in 2D space to measure from.</param>
  131. /// <returns>
  132. /// The squared distance. Returns the distance to the origin if the point
  133. /// projects behind the ray's starting point, or the perpendicular distance if the point
  134. /// projects onto the ray.
  135. /// </returns>
  136. /// <remarks>
  137. /// This method returns squared distance to avoid the expensive square root operation,
  138. /// making it efficient for distance comparisons.
  139. /// </remarks>
  140. public readonly float DistanceSquaredToPoint(Vector2 point)
  141. {
  142. Vector2 closestPoint = ClosestPoint(point, out _);
  143. return Vector2.DistanceSquared(point, closestPoint);
  144. }
  145. /// <summary>
  146. /// Computes the distance from a point to the closest point on this ray.
  147. /// </summary>
  148. /// <param name="point">The point in 2D space to measure from.</param>
  149. /// <returns>
  150. /// The distance. Returns the distance to the origin if the point
  151. /// projects behind the ray's starting point, or the perpendicular distance if the point
  152. /// projects onto the ray.
  153. /// </returns>
  154. /// <remarks>
  155. /// This method performs a square root operation. For distance comparisons,
  156. /// use <see cref="DistanceSquaredToPoint"/> instead to avoid the computational cost.
  157. /// </remarks>
  158. public readonly float DistanceToPoint(Vector2 point)
  159. {
  160. float distSq = DistanceSquaredToPoint(point);
  161. return MathF.Sqrt(distSq);
  162. }
  163. /// <summary>
  164. /// Returns a normalized representation of the specified ray with a unit direction vector.
  165. /// </summary>
  166. /// <param name="value">The ray to normalize.</param>
  167. /// <param name="result">
  168. /// When this method returns, contains a <see cref="Ray2D"/> with the same origin but a unit direction vector.
  169. /// </param>
  170. public static void Normalize(ref Ray2D value, out Ray2D result)
  171. {
  172. result = new Ray2D(value.Origin, Vector2.Normalize(value.Direction));
  173. }
  174. /// <summary>
  175. /// Returns a normalized representation of the specified ray with a unit direction vector.
  176. /// </summary>
  177. /// <param name="value">The ray to normalize.</param>
  178. /// <returns>
  179. /// A new <see cref="Ray2D"/> with the same origin but a unit direction vector.
  180. /// </returns>
  181. public static Ray2D Normalize(Ray2D value)
  182. {
  183. Ray2D result;
  184. Normalize(ref value, out result);
  185. return result;
  186. }
  187. /// <summary>
  188. /// Normalizes this ray's direction vector to unit length.
  189. /// </summary>
  190. /// <remarks>
  191. /// After normalization, the <see cref="Direction"/> will be a unit vector while
  192. /// <see cref="Origin"/> remains unchanged.
  193. /// </remarks>
  194. public void Normalize()
  195. {
  196. Direction.Normalize();
  197. }
  198. /// <summary>
  199. /// Tests if this ray intersects with a line.
  200. /// </summary>
  201. /// <param name="line">The line to test against.</param>
  202. /// <param name="distanceAlongRay">
  203. /// When this method returns <see langword="true"/>, contains the parametric distance along this ray
  204. /// to the intersection point.
  205. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  206. /// </param>
  207. /// <param name="point">
  208. /// When this method returns <see langword="true"/>, contains the point where the ray and line intersect.
  209. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  210. /// </param>
  211. /// <returns>
  212. /// <see langword="true"/> if the ray and line intersect in the ray's forward direction;
  213. /// otherwise, <see langword="false"/> if they are parallel or the intersection point is behind the ray's origin.
  214. /// </returns>
  215. public readonly bool Intersects(Line2D line, out float? distanceAlongRay, out Vector2? point)
  216. {
  217. return line.Intersects(this, out distanceAlongRay, out point);
  218. }
  219. /// <summary>
  220. /// Tests if this ray intersects with a line.
  221. /// </summary>
  222. /// <param name="line">The line to test against.</param>
  223. /// <returns>
  224. /// <see langword="true"/> if the ray and line intersect in the ray's forward direction;
  225. /// otherwise, <see langword="false"/> if they are parallel or the intersection point is behind the ray's origin.
  226. /// </returns>
  227. public readonly bool Intersects(Line2D line)
  228. {
  229. return line.Intersects(this);
  230. }
  231. /// <summary>
  232. /// Tests if this ray intersects with another ray.
  233. /// </summary>
  234. /// <param name="other">The other ray to test against.</param>
  235. /// <param name="distanceAlongRay1">
  236. /// When this method returns <see langword="true"/>, contains the parametric distance along this ray
  237. /// to the intersection point.
  238. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  239. /// </param>
  240. /// <param name="distanceAlongRay2">
  241. /// When this method returns <see langword="true"/>, contains the parametric distance along the other ray
  242. /// to the intersection point.
  243. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  244. /// </param>
  245. /// <param name="point">
  246. /// When this method returns <see langword="true"/>, contains the point where the rays intersect.
  247. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  248. /// </param>
  249. /// <returns>
  250. /// <see langword="true"/> if both rays intersect in their forward directions;
  251. /// otherwise, <see langword="false"/> if they are parallel or if the intersection point is behind either ray's origin.
  252. /// </returns>
  253. public readonly bool Intersects(Ray2D other, out float? distanceAlongRay1, out float? distanceAlongRay2, out Vector2? point)
  254. {
  255. // C. Ericson, Real-Time Collision Detection, Morgan Kaufmann, 2005
  256. // Parametric intersection of two rays using 2D cross products
  257. // Derived from Section 5.1.9.1 "2D Segment Intersection" (line–line formulation)
  258. if (!Collision2D.SolveParametricIntersection2D(Origin, Direction, other.Origin, other.Direction, out float t1, out float t2))
  259. {
  260. distanceAlongRay1 = distanceAlongRay2 = null;
  261. point = null;
  262. return false;
  263. }
  264. // Only intersections in forward direction. Negative direction indicates
  265. // intersection would have happened behind origin.
  266. if (t1 < 0.0f || t2 < 0.0f)
  267. {
  268. distanceAlongRay1 = distanceAlongRay2 = null;
  269. point = null;
  270. return false;
  271. }
  272. distanceAlongRay1 = t1;
  273. distanceAlongRay2 = t2;
  274. point = Origin + t1 * Direction;
  275. return true;
  276. }
  277. /// <summary>
  278. /// Tests if this ray intersects with another ray.
  279. /// </summary>
  280. /// <param name="other">The other ray to test against.</param>
  281. /// <returns>
  282. /// <see langword="true"/> if both rays intersect in their forward directions;
  283. /// otherwise, <see langword="false"/> if they are parallel or if the intersection point is behind either ray's origin.
  284. /// </returns>
  285. public readonly bool Intersects(Ray2D other)
  286. {
  287. return Intersects(other, out _, out _, out _);
  288. }
  289. /// <summary>
  290. /// Tests if this ray intersects with a line segment.
  291. /// </summary>
  292. /// <param name="segment">The line segment to test against.</param>
  293. /// <param name="distanceAlongRay">
  294. /// When this method returns <see langword="true"/>, contains the parametric distance along this ray
  295. /// to the intersection point.
  296. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  297. /// </param>
  298. /// <param name="distanceAlongSegment">
  299. /// When this method returns <see langword="true"/>, contains the parametric distance along the segment
  300. /// to the intersection point, in the range [0, 1] where 0 represents the start and 1 represents the end.
  301. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  302. /// </param>
  303. /// <param name="point">
  304. /// When this method returns <see langword="true"/>, contains the point where the ray and segment intersect.
  305. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  306. /// </param>
  307. /// <returns>
  308. /// <see langword="true"/> if the ray intersects the segment in its forward direction and within the segment's bounds;
  309. /// otherwise, <see langword="false"/> if they are parallel, the intersection is outside the segment, or behind the ray's origin.
  310. /// </returns>
  311. public readonly bool Intersects(LineSegment2D segment, out float? distanceAlongRay, out float? distanceAlongSegment, out Vector2? point)
  312. {
  313. // C. Ericson, Real-Time Collision Detection, Morgan Kaufmann, 2005
  314. // Parametric intersection of a ray with a line segment using 2D cross products
  315. // Derived from Section 5.1.9.1 "2D Segment Intersection" (line–line formulation)
  316. Vector2 segmentDir = segment.End - segment.Start;
  317. if (!Collision2D.SolveParametricIntersection2D(Origin, Direction, segment.Start, segmentDir, out float tRay, out float tSeg))
  318. {
  319. distanceAlongRay = distanceAlongSegment = null;
  320. point = null;
  321. return false;
  322. }
  323. // Intersection only if within segments bounds [0,1] and
  324. // only in forward direction of ray. Negative direction of ray indicates
  325. // intersection would have happened behind ray origin
  326. if (tSeg < 0.0f || tSeg > 1.0f || tRay < 0.0f)
  327. {
  328. distanceAlongRay = distanceAlongSegment = null;
  329. point = null;
  330. return false;
  331. }
  332. distanceAlongRay = tRay;
  333. distanceAlongSegment = tSeg;
  334. point = Origin + tRay * Direction;
  335. return true;
  336. }
  337. /// <summary>Tests if this <see cref="Ray2D"/> intersects a <see cref="LineSegment2D"/>.</summary>
  338. /// <param name="segment">The <see cref="LineSegment2D"/> to test against.</param>
  339. /// <returns>
  340. /// <see langword="true"/> if this ray and <paramref name="segment"/> intersect; otherwise, <see langword="false"/>.
  341. /// </returns>
  342. public readonly bool Intersects(LineSegment2D segment)
  343. {
  344. return Intersects(segment, out _, out _, out _);
  345. }
  346. /// <summary>
  347. /// Tests if this ray intersects with an axis-aligned bounding box and computes the parametric distances to the intersection points.
  348. /// </summary>
  349. /// <param name="box">The bounding box to test against.</param>
  350. /// <param name="tRayMin">
  351. /// When this method returns <see langword="true"/>, contains the parametric distance along this ray
  352. /// to the entry intersection point, where the intersection point equals <c>Origin + tRayMin * Direction</c>.
  353. /// If the ray origin is inside the bounding box, this will be <c>0</c>.
  354. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  355. /// </param>
  356. /// <param name="tRayMax">
  357. /// When this method returns <see langword="true"/>, contains the parametric distance along this ray
  358. /// to the exit intersection point, where the intersection point equals <c>Origin + tRayMax * Direction</c>.
  359. /// This is always greater than or equal to <paramref name="tRayMin"/>.
  360. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  361. /// </param>
  362. /// <returns>
  363. /// <see langword="true"/> if the ray intersects the bounding box in its forward direction;
  364. /// otherwise, <see langword="false"/>.
  365. /// </returns>
  366. public readonly bool Intersects(BoundingBox2D box, out float? tRayMin, out float? tRayMax)
  367. {
  368. if (!Collision2D.ClipLineToAabb(Origin, Direction, box.Min, box.Max, 0.0f, float.MaxValue, out float tEnter, out float tExit))
  369. {
  370. tRayMin = tRayMax = null;
  371. return false;
  372. }
  373. tRayMin = tEnter;
  374. tRayMax = tExit;
  375. return true;
  376. }
  377. /// <summary>
  378. /// Tests if this ray intersects with an axis-aligned bounding box.
  379. /// </summary>
  380. /// <param name="box">The bounding box to test against.</param>
  381. /// <returns>
  382. /// <see langword="true"/> if the ray intersects the bounding box in its forward direction;
  383. /// otherwise, <see langword="false"/>.
  384. /// </returns>
  385. public readonly bool Intersects(BoundingBox2D box)
  386. {
  387. return Intersects(box, out _, out _);
  388. }
  389. /// <summary>
  390. /// Tests if this ray intersects with a circle and computes the parametric distances to the intersection points.
  391. /// </summary>
  392. /// <param name="circle">The circle to test against.</param>
  393. /// <param name="tRayMin">
  394. /// When this method returns <see langword="true"/>, contains the parametric distance along this ray
  395. /// to the first intersection point, where the intersection point equals <c>Origin + tRayMin * Direction</c>.
  396. /// If the ray origin is inside the circle, this will be <c>0</c>.
  397. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  398. /// </param>
  399. /// <param name="tRayMax">
  400. /// When this method returns <see langword="true"/>, contains the parametric distance along this ray
  401. /// to the second intersection point, where the intersection point equals <c>Origin + tRayMax * Direction</c>.
  402. /// This is always greater than or equal to <paramref name="tRayMin"/>.
  403. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  404. /// </param>
  405. /// <returns>
  406. /// <see langword="true"/> if the ray intersects the circle in its forward direction;
  407. /// otherwise, <see langword="false"/>.
  408. /// </returns>
  409. public readonly bool Intersects(BoundingCircle2D circle, out float? tRayMin, out float? tRayMax)
  410. {
  411. if (!Collision2D.RayCircleIntersectionInterval(Origin, Direction, circle.Center, circle.Radius, out float tMin, out float tMax))
  412. {
  413. tRayMin = tRayMax = null;
  414. return false;
  415. }
  416. if (!Collision2D.ClipInterval(tMin, tMax, 0.0f, float.MaxValue, out float entry, out float exit))
  417. {
  418. tRayMin = tRayMax = null;
  419. return false;
  420. }
  421. tRayMin = entry;
  422. tRayMax = exit;
  423. return true;
  424. }
  425. /// <summary>
  426. /// Tests if this ray intersects with a circle.
  427. /// </summary>
  428. /// <param name="circle">The circle to test against.</param>
  429. /// <returns>
  430. /// <see langword="true"/> if the ray intersects the circle in its forward direction;
  431. /// otherwise, <see langword="false"/>.
  432. /// </returns>
  433. public readonly bool Intersects(BoundingCircle2D circle)
  434. {
  435. return Intersects(circle, out _, out _);
  436. }
  437. /// <summary>
  438. /// Tests if this ray intersects with a capsule and computes the parametric distances to the intersection points.
  439. /// </summary>
  440. /// <param name="capsule">The capsule to test against.</param>
  441. /// <param name="tRayMin">
  442. /// When this method returns <see langword="true"/>, contains the parametric distance along this ray
  443. /// to the entry intersection point, where the intersection point equals Origin + tRayMin * Direction.
  444. /// If the ray origin is inside the capsule, this will be 0.
  445. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  446. /// </param>
  447. /// <param name="tRayMax">
  448. /// When this method returns <see langword="true"/>, contains the parametric distance along this ray
  449. /// to the exit intersection point, where the intersection point equals Origin + tRayMax * Direction.
  450. /// This is always greater than or equal to <paramref name="tRayMin"/>.
  451. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  452. /// </param>
  453. /// <returns>
  454. /// <see langword="true"/> if the ray intersects the capsule in its forward direction;
  455. /// otherwise, <see langword="false"/>.
  456. /// </returns>
  457. public readonly bool Intersects(BoundingCapsule2D capsule, out float? tRayMin, out float? tRayMax)
  458. {
  459. if (!Collision2D.RayCapsuleIntersectionInterval(Origin, Direction, capsule.PointA, capsule.PointB, capsule.Radius, out float tMin, out float tMax))
  460. {
  461. tRayMin = tRayMax = null;
  462. return false;
  463. }
  464. if (!Collision2D.ClipInterval(tMin, tMax, 0.0f, float.MaxValue, out float entry, out float exit))
  465. {
  466. tRayMin = tRayMax = null;
  467. return false;
  468. }
  469. tRayMin = entry;
  470. tRayMax = exit;
  471. return true;
  472. }
  473. /// <summary>
  474. /// Tests if this ray intersects with a capsule.
  475. /// </summary>
  476. /// <param name="capsule">The capsule to test against.</param>
  477. /// <returns>
  478. /// <see langword="true"/> if the ray intersects the capsule in its forward direction;
  479. /// otherwise, <see langword="false"/>.
  480. /// </returns>
  481. public readonly bool Intersects(BoundingCapsule2D capsule)
  482. {
  483. return Intersects(capsule, out _, out _);
  484. }
  485. /// <summary>
  486. /// Tests if this ray intersects with an oriented bounding box and computes the parametric distances to the intersection points.
  487. /// </summary>
  488. /// <param name="obb">The oriented bounding box to test against.</param>
  489. /// <param name="tRayMin">
  490. /// When this method returns <see langword="true"/>, contains the parametric distance along this ray
  491. /// to the entry intersection point, where the intersection point equals Origin + tRayMin * Direction.
  492. /// If the ray origin is inside the box, this will be 0.
  493. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  494. /// </param>
  495. /// <param name="tRayMax">
  496. /// When this method returns <see langword="true"/>, contains the parametric distance along this ray
  497. /// to the exit intersection point, where the intersection point equals Origin + tRayMax * Direction.
  498. /// This is always greater than or equal to <paramref name="tRayMin"/>.
  499. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  500. /// </param>
  501. /// <returns>
  502. /// <see langword="true"/> if the ray intersects the box in its forward direction;
  503. /// otherwise, <see langword="false"/>.
  504. /// </returns>
  505. public readonly bool Intersects(OrientedBoundingBox2D obb, out float? tRayMin, out float? tRayMax)
  506. {
  507. // C. Ericson, Real-Time Collision Detection, Morgan Kaufmann, 2005
  508. // Parametric intersection of a ray with an oriented bounding rectangle (2D reduction)
  509. // Derived from Section 5.3.3 "Intersecting Ray or Segment Against Box"
  510. // Project the ray origin and direction onto the OBBs local axes
  511. // to transform the ray from world space to OBB local space.
  512. Vector2 diff = Origin - obb.Center;
  513. Vector2 localOrigin = new Vector2(
  514. Vector2.Dot(diff, obb.AxisX),
  515. Vector2.Dot(diff, obb.AxisY)
  516. );
  517. Vector2 localDirection = new Vector2(
  518. Vector2.Dot(Direction, obb.AxisX),
  519. Vector2.Dot(Direction, obb.AxisY)
  520. );
  521. if (!Collision2D.ClipLineToAabb(localOrigin, localDirection, -obb.HalfExtents, obb.HalfExtents, 0.0f, float.MaxValue, out float tEnter, out float tExit))
  522. {
  523. tRayMin = tRayMax = null;
  524. return false;
  525. }
  526. tRayMin = tEnter;
  527. tRayMax = tExit;
  528. return true;
  529. }
  530. /// <summary>
  531. /// Tests if this ray intersects with an oriented bounding box.
  532. /// </summary>
  533. /// <param name="obb">The oriented bounding box to test against.</param>
  534. /// <returns>
  535. /// <see langword="true"/> if the ray intersects the box in its forward direction;
  536. /// otherwise, <see langword="false"/>.
  537. /// </returns>
  538. public readonly bool Intersects(OrientedBoundingBox2D obb)
  539. {
  540. return Intersects(obb, out _, out _);
  541. }
  542. /// <summary>
  543. /// Tests if this ray intersects with a polygon and computes the parametric distances to the intersection points.
  544. /// </summary>
  545. /// <param name="polygon">The polygon to test against.</param>
  546. /// <param name="tRayMin">
  547. /// When this method returns <see langword="true"/>, contains the parametric distance along this ray
  548. /// to the entry intersection point, where the intersection point equals Origin + tRayMin * Direction.
  549. /// If the ray origin is inside the polygon, this will be 0.
  550. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  551. /// </param>
  552. /// <param name="tRayMax">
  553. /// When this method returns <see langword="true"/>, contains the parametric distance along this ray
  554. /// to the exit intersection point, where the intersection point equals Origin + tRayMax * Direction.
  555. /// This is always greater than or equal to <paramref name="tRayMin"/>.
  556. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  557. /// </param>
  558. /// <param name="point">
  559. /// When this method returns <see langword="true"/>, contains the position of the entry intersection point
  560. /// corresponding to <paramref name="tRayMin"/>.
  561. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  562. /// </param>
  563. /// <returns>
  564. /// <see langword="true"/> if the ray intersects the polygon in its forward direction;
  565. /// otherwise, <see langword="false"/>.
  566. /// </returns>
  567. public readonly bool Intersects(BoundingPolygon2D polygon, out float? tRayMin, out float? tRayMax, out Vector2? point)
  568. {
  569. // Clip ray's parametric line against the polygon
  570. if (!Collision2D.ClipLineToConvexPolygon(Origin, Direction, polygon.Vertices, polygon.Normals, 0.0f, float.MaxValue, out float t0, out float t1))
  571. {
  572. tRayMin = tRayMax = null;
  573. point = null;
  574. return false;
  575. }
  576. if (!Collision2D.ClipInterval(t0, t1, 0.0f, float.MaxValue, out float entry, out float exit))
  577. {
  578. tRayMin = tRayMax = null;
  579. point = null;
  580. return false;
  581. }
  582. tRayMin = entry;
  583. tRayMax = exit;
  584. point = Origin + Direction * entry;
  585. return true;
  586. }
  587. /// <summary>
  588. /// Tests if this ray intersects with a polygon.
  589. /// </summary>
  590. /// <param name="polygon">The polygon to test against.</param>
  591. /// <returns>
  592. /// <see langword="true"/> if the ray intersects the polygon in its forward direction;
  593. /// otherwise, <see langword="false"/>.
  594. /// </returns>
  595. public readonly bool Intersects(BoundingPolygon2D polygon)
  596. {
  597. return Intersects(polygon, out _, out _, out _);
  598. }
  599. /// <summary>
  600. /// Deconstructs this ray into its component values.
  601. /// </summary>
  602. /// <param name="origin">
  603. /// When this method returns, contains the starting point of this ray in 2D space.
  604. /// </param>
  605. /// <param name="direction">
  606. /// When this method returns, contains the direction vector defining which way this ray extends.
  607. /// </param>
  608. public readonly void Deconstruct(out Vector2 origin, out Vector2 direction)
  609. {
  610. origin = Origin;
  611. direction = Direction;
  612. }
  613. /// <inheritdoc/>
  614. public readonly bool Equals(Ray2D other)
  615. {
  616. return Origin.Equals(other.Origin) && Direction.Equals(other.Direction);
  617. }
  618. /// <inheritdoc/>
  619. public override readonly bool Equals(object obj)
  620. {
  621. return (obj is Ray2D other) && Equals(other);
  622. }
  623. /// <inheritdoc/>
  624. public override readonly int GetHashCode()
  625. {
  626. return Origin.GetHashCode() ^ Direction.GetHashCode();
  627. }
  628. /// <inheritdoc/>
  629. public override readonly string ToString()
  630. {
  631. return "{{Origin:" + Origin.ToString() + " Direction:" + Direction.ToString() + "}}";
  632. }
  633. /// <summary/>
  634. public static bool operator ==(Ray2D left, Ray2D right)
  635. {
  636. return left.Equals(right);
  637. }
  638. /// <summary/>
  639. public static bool operator !=(Ray2D left, Ray2D right)
  640. {
  641. return !left.Equals(right);
  642. }
  643. }
  644. }