LineSegment2D.cs 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847
  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.Diagnostics.CodeAnalysis;
  7. using System.Runtime.Serialization;
  8. using Microsoft.Xna.Framework;
  9. namespace MonoGame.Extended
  10. {
  11. /// <summary>
  12. /// Represents a finite line segment connecting two endpoints in 2D space.
  13. /// </summary>
  14. [DataContract]
  15. [DebuggerDisplay("{DebugDisplayString,nq}")]
  16. public struct LineSegment2D : IEquatable<LineSegment2D>
  17. {
  18. #region Public Fields
  19. /// <summary>
  20. /// The starting point of this line segment in 2D space.
  21. /// </summary>
  22. [DataMember]
  23. public Vector2 Start;
  24. /// <summary>
  25. /// The ending point of this line segment in 2D space.
  26. /// </summary>
  27. [DataMember]
  28. public Vector2 End;
  29. #endregion
  30. #region Public Properties
  31. /// <summary>
  32. /// Gets the unnormalized direction vector from start to end.
  33. /// The length of this vector equals the length of the segment.
  34. /// </summary>
  35. public readonly Vector2 Direction
  36. {
  37. get
  38. {
  39. return End - Start;
  40. }
  41. }
  42. /// <summary>
  43. /// Gets the midpoint of this line segment, located halfway between the start and end points.
  44. /// </summary>
  45. public readonly Vector2 Midpoint
  46. {
  47. get
  48. {
  49. return (Start + End) * 0.5f;
  50. }
  51. }
  52. /// <summary>
  53. /// Gets the length of this line segment.
  54. /// For length comparisons, use <see cref="LengthSquared"/> to avoid the square root calculation.
  55. /// </summary>
  56. public readonly float Length
  57. {
  58. get
  59. {
  60. return Vector2.Distance(Start, End);
  61. }
  62. }
  63. /// <summary>
  64. /// Gets the squared length of this line segment.
  65. /// </summary>
  66. /// <remarks>
  67. /// This property avoids the expensive square root operation, making it efficient for length comparisons.
  68. /// </remarks>
  69. public readonly float LengthSquared
  70. {
  71. get
  72. {
  73. return Vector2.DistanceSquared(Start, End);
  74. }
  75. }
  76. #endregion
  77. #region Internal Properties
  78. internal readonly string DebugDisplayString
  79. {
  80. get
  81. {
  82. return string.Concat(
  83. "Start( ", Start.ToString(), " ) \r\n",
  84. "End( ", End.ToString(), " )"
  85. );
  86. }
  87. }
  88. #endregion
  89. #region Public Constructors
  90. /// <summary>
  91. /// Creates a new <see cref="LineSegment2D"/> connecting two specified points.
  92. /// </summary>
  93. /// <param name="start">The starting point of the line segment in 2D space.</param>
  94. /// <param name="end">The ending point of the line segment in 2D space.</param>
  95. public LineSegment2D(Vector2 start, Vector2 end)
  96. {
  97. Start = start;
  98. End = end;
  99. }
  100. #endregion
  101. #region Public Methods
  102. /// <summary>
  103. /// Computes the smallest axis-aligned bounding box that contains this line segment.
  104. /// </summary>
  105. /// <returns>
  106. /// A <see cref="BoundingBox2D"/> that tightly encloses both endpoints of this segment.
  107. /// </returns>
  108. /// <remarks>
  109. /// The bounding box is computed using the component-wise minimum and maximum of the start and end points.
  110. /// </remarks>
  111. public readonly BoundingBox2D GetBounds()
  112. {
  113. Vector2 min = Vector2.Min(Start, End);
  114. Vector2 max = Vector2.Max(Start, End);
  115. return new BoundingBox2D(min, max);
  116. }
  117. /// <summary>
  118. /// Computes a point along this line segment at the specified parametric distance.
  119. /// </summary>
  120. /// <param name="distanceAlongSegment">
  121. /// The parametric distance along the segment from the start point, where 0 represents the start,
  122. /// 1 represents the end, and values between 0 and 1 lie on the segment.
  123. /// </param>
  124. /// <returns>
  125. /// The point at position <c>Start + distanceAlongSegment * (End - Start)</c>.
  126. /// Values outside [0, 1] return points beyond the segment's endpoints along the line defined by the segment.
  127. /// </returns>
  128. public readonly Vector2 GetPoint(float distanceAlongSegment)
  129. {
  130. return Start + distanceAlongSegment * (End - Start);
  131. }
  132. /// <summary>
  133. /// Computes the closest point on this line segment to a specified point.
  134. /// </summary>
  135. /// <param name="point">The point in 2D space to find the closest point to.</param>
  136. /// <param name="distanceAlongSegment">
  137. /// When this method returns, contains the parametric distance along the segment to the closest point,
  138. /// clamped to the range [0, 1] where 0 represents the start and 1 represents the end.
  139. /// </param>
  140. /// <returns>
  141. /// The closest point on the segment. Returns the start if the point projects before the segment,
  142. /// the end if the point projects beyond the segment, or the perpendicular projection if the point
  143. /// projects onto the segment.
  144. /// </returns>
  145. public readonly Vector2 ClosestPoint(Vector2 point, out float distanceAlongSegment)
  146. {
  147. // C. Ericson, Real-Time Collision Detection, Morgan Kaufmann, 2005
  148. // Section 5.1.2 "Closest Point on Line Segment to Point"
  149. Vector2 ab = End - Start;
  150. // Project point onto ab, but deferring divide by Dot(ab, ab)
  151. distanceAlongSegment = Vector2.Dot(point - Start, ab);
  152. if (distanceAlongSegment <= 0.0f)
  153. {
  154. // point projects outside the [a,b] interval, on the a side; clamp to a
  155. distanceAlongSegment = 0.0f;
  156. return Start;
  157. }
  158. float denom = Vector2.Dot(ab, ab);
  159. if (distanceAlongSegment >= denom)
  160. {
  161. // point projects outside the [a,b] interval, on the b side; clamp to b
  162. distanceAlongSegment = 1.0f;
  163. return End;
  164. }
  165. // Point projects inside the [a,b] interval; must do deferred divide now
  166. distanceAlongSegment /= denom;
  167. return Start + distanceAlongSegment * ab;
  168. }
  169. /// <summary>
  170. /// Computes the squared distance from a point to the closest point on this line segment.
  171. /// </summary>
  172. /// <param name="point">The point in 2D space to measure from.</param>
  173. /// <returns>
  174. /// The squared distance. Returns the distance to <see cref="Start"/> if the point
  175. /// projects before the segment, distance to <see cref="End"/> if it projects beyond, or the
  176. /// perpendicular distance if the point projects onto the segment.
  177. /// </returns>
  178. /// <remarks>
  179. /// This method returns squared distance to avoid the expensive square root operation,
  180. /// making it efficient for distance comparisons.
  181. /// </remarks>
  182. public readonly float DistanceSquaredToPoint(Vector2 point)
  183. {
  184. return Collision2D.DistanceSquaredPointSegment(point, Start, End, out _, out _);
  185. }
  186. /// <summary>
  187. /// Computes the distance from a point to the closest point on this line segment.
  188. /// </summary>
  189. /// <param name="point">The point in 2D space to measure from.</param>
  190. /// <returns>
  191. /// The distance. Returns the distance to <see cref="Start"/> if the point
  192. /// projects before the segment, distance to <see cref="End"/> if it projects beyond, or the
  193. /// perpendicular distance if the point projects onto the segment.
  194. /// </returns>
  195. /// <remarks>
  196. /// This method performs a square root operation. For distance comparisons,
  197. /// use <see cref="DistanceSquaredToPoint"/> instead to avoid the computational cost.
  198. /// </remarks>
  199. public readonly float DistanceToPoint(Vector2 point)
  200. {
  201. return MathF.Sqrt(DistanceSquaredToPoint(point));
  202. }
  203. /// <summary>
  204. /// Computes the squared distance between this line segment and another line segment.
  205. /// </summary>
  206. /// <param name="other">The other line segment to measure distance to.</param>
  207. /// <param name="distanceAlongSegment1">
  208. /// When this method returns, contains the parametric distance along this segment to the closest point,
  209. /// in the range [0, 1] where 0 represents the start and 1 represents the end.
  210. /// </param>
  211. /// <param name="distanceAlongSegment2">
  212. /// When this method returns, contains the parametric distance along the other segment to the closest point,
  213. /// in the range [0, 1] where 0 represents the start and 1 represents the end.
  214. /// </param>
  215. /// <param name="closestPoint1">
  216. /// When this method returns, contains the closest point on this segment to the other segment.
  217. /// </param>
  218. /// <param name="closestPoint2">
  219. /// When this method returns, contains the closest point on the other segment to this segment.
  220. /// </param>
  221. /// <returns>
  222. /// The squared distance in between the two closest points on the segments.
  223. /// </returns>
  224. /// <remarks>
  225. /// This method returns squared distance to avoid the expensive square root operation,
  226. /// making it efficient for distance comparisons.
  227. /// </remarks>
  228. public readonly float DistanceSquaredToSegment(LineSegment2D other, out float distanceAlongSegment1, out float distanceAlongSegment2, out Vector2 closestPoint1, out Vector2 closestPoint2)
  229. {
  230. return Collision2D.DistanceSquaredSegmentSegment(Start, End, other.Start, other.End,
  231. out distanceAlongSegment1, out distanceAlongSegment2,
  232. out closestPoint1, out closestPoint2);
  233. }
  234. /// <summary>
  235. /// Computes the distance between this line segment and another line segment.
  236. /// </summary>
  237. /// <param name="other">The other line segment to measure distance to.</param>
  238. /// <returns>
  239. /// The shortest distance between any two points on the segments.
  240. /// </returns>
  241. /// <remarks>
  242. /// This method performs a square root operation. For distance comparisons,
  243. /// use <see cref="DistanceSquaredToSegment"/> instead to avoid the computational cost.
  244. /// </remarks>
  245. public readonly float DistanceToSegment(LineSegment2D other)
  246. {
  247. return MathF.Sqrt(DistanceSquaredToSegment(other, out _, out _, out _, out _));
  248. }
  249. /// <summary>
  250. /// Tests if this line segment intersects with a line.
  251. /// </summary>
  252. /// <param name="line">The line to test against.</param>
  253. /// <param name="distanceAlongSegment">
  254. /// When this method returns <see langword="true"/>, contains the parametric distance along this segment
  255. /// to the intersection point, in the range [0, 1] where 0 represents the start and 1 represents the end.
  256. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  257. /// </param>
  258. /// <param name="point">
  259. /// When this method returns <see langword="true"/>, contains the point where the segment and line intersect.
  260. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  261. /// </param>
  262. /// <returns>
  263. /// <see langword="true"/> if the segment intersects the line within its bounds;
  264. /// otherwise, <see langword="false"/> if they are parallel or the intersection point lies outside the segment.
  265. /// </returns>
  266. public readonly bool Intersects(Line2D line, out float? distanceAlongSegment, out Vector2? point)
  267. {
  268. return line.Intersects(this, out distanceAlongSegment, out point);
  269. }
  270. /// <summary>
  271. /// Tests if this line segment intersects with a line.
  272. /// </summary>
  273. /// <param name="line">The line to test against.</param>
  274. /// <returns>
  275. /// <see langword="true"/> if the segment intersects the line within its bounds;
  276. /// otherwise, <see langword="false"/> if they are parallel or the intersection point lies outside the segment.
  277. /// </returns>
  278. public readonly bool Intersects(Line2D line)
  279. {
  280. return line.Intersects(this);
  281. }
  282. /// <summary>
  283. /// Tests if this line segment intersects with a ray.
  284. /// </summary>
  285. /// <param name="ray">The ray to test against.</param>
  286. /// <param name="distanceAlongSegment">
  287. /// When this method returns <see langword="true"/>, contains the parametric distance along this segment
  288. /// to the intersection point, in the range [0, 1] where 0 represents the start and 1 represents the end.
  289. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  290. /// </param>
  291. /// <param name="distanceAlongRay">
  292. /// When this method returns <see langword="true"/>, contains the parametric distance along the ray
  293. /// to the intersection point.
  294. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  295. /// </param>
  296. /// <param name="point">
  297. /// When this method returns <see langword="true"/>, contains the point where the segment and ray intersect.
  298. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  299. /// </param>
  300. /// <returns>
  301. /// <see langword="true"/> if the segment intersects the ray within the segment's bounds and the ray's forward
  302. /// direction; otherwise, <see langword="false"/> if they are parallel, the intersection is outside the segment,
  303. /// or behind the ray's origin.
  304. /// </returns>
  305. public readonly bool Intersects(Ray2D ray, out float? distanceAlongSegment, out float? distanceAlongRay, out Vector2? point)
  306. {
  307. return ray.Intersects(this, out distanceAlongRay, out distanceAlongSegment, out point);
  308. }
  309. /// <summary>
  310. /// Tests if this line segment intersects with a ray.
  311. /// </summary>
  312. /// <param name="ray">The ray to test against.</param>
  313. /// <returns>
  314. /// <see langword="true"/> if the segment intersects the ray within the segment's bounds and the ray's forward
  315. /// direction; otherwise, <see langword="false"/> if they are parallel, the intersection is outside the segment,
  316. /// or behind the ray's origin.
  317. /// </returns>
  318. public readonly bool Intersects(Ray2D ray)
  319. {
  320. return ray.Intersects(this);
  321. }
  322. /// <summary>
  323. /// Tests if this line segment intersects with another line segment.
  324. /// </summary>
  325. /// <param name="other">The other line segment to test against.</param>
  326. /// <param name="distanceAlongSegment1">
  327. /// When this method returns <see langword="true"/>, contains the parametric distance along this segment
  328. /// to the intersection point, in the range [0, 1] where 0 represents the start and 1 represents the end.
  329. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  330. /// </param>
  331. /// <param name="distanceAlongSegment2">
  332. /// When this method returns <see langword="true"/>, contains the parametric distance along the other segment
  333. /// to the intersection point, in the range [0, 1] where 0 represents the start and 1 represents the end.
  334. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  335. /// </param>
  336. /// <param name="point">
  337. /// When this method returns <see langword="true"/>, contains the point where the segments intersect.
  338. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  339. /// </param>
  340. /// <returns>
  341. /// <see langword="true"/> if both segments intersect within their bounds;
  342. /// otherwise, <see langword="false"/> if they are parallel or if the intersection point lies outside either segment.
  343. /// </returns>
  344. public readonly bool Intersects(LineSegment2D other, out float? distanceAlongSegment1, out float? distanceAlongSegment2, out Vector2? point)
  345. {
  346. if (!Collision2D.SolveParametricIntersection2D(Start, Direction, other.Start, other.Direction, out float t1, out float t2))
  347. {
  348. distanceAlongSegment1 = distanceAlongSegment2 = null;
  349. point = null;
  350. return false;
  351. }
  352. // Clamp to segment bounds [0,1]
  353. if (t1 < 0.0f || t1 > 1.0f || t2 < 0.0f || t2 > 1.0f)
  354. {
  355. distanceAlongSegment1 = distanceAlongSegment2 = null;
  356. point = null;
  357. return false;
  358. }
  359. distanceAlongSegment1 = t1;
  360. distanceAlongSegment2 = t2;
  361. point = Start + t1 * Direction;
  362. return true;
  363. }
  364. /// <summary>
  365. /// Tests if this line segment intersects with another line segment.
  366. /// </summary>
  367. /// <param name="other">The other line segment to test against.</param>
  368. /// <returns>
  369. /// <see langword="true"/> if both segments intersect within their bounds;
  370. /// otherwise, <see langword="false"/> if they are parallel or if the intersection point lies outside either segment.
  371. /// </returns>
  372. public readonly bool Intersects(LineSegment2D other)
  373. {
  374. return Intersects(other, out _, out _, out _);
  375. }
  376. /// <summary>
  377. /// Tests if this line segment intersects with an axis-aligned bounding box and computes the parametric distances to the intersection points.
  378. /// </summary>
  379. /// <param name="box">The bounding box to test against.</param>
  380. /// <param name="tMin">
  381. /// When this method returns <see langword="true"/>, contains the parametric distance along this segment
  382. /// to the entry intersection point, in the range [0, 1] where 0 represents the start and 1 represents the end.
  383. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  384. /// </param>
  385. /// <param name="tMax">
  386. /// When this method returns <see langword="true"/>, contains the parametric distance along this segment
  387. /// to the exit intersection point, in the range [0, 1] where 0 represents the start and 1 represents the end.
  388. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  389. /// </param>
  390. /// <returns>
  391. /// <see langword="true"/> if the segment intersects the bounding box; otherwise, <see langword="false"/>.
  392. /// </returns>
  393. /// <remarks>
  394. /// For degenerate segments (zero length), returns <see langword="true"/> with tMin = tMax = 0
  395. /// if the start point is inside the bounding box.
  396. /// </remarks>
  397. public readonly bool Intersects(BoundingBox2D box, out float? tMin, out float? tMax)
  398. {
  399. Vector2 d = Direction;
  400. float dd = Vector2.Dot(d, d);
  401. // Handle degenerate segment (zero length)
  402. if (dd < Collision2D.Epsilon * Collision2D.Epsilon)
  403. {
  404. bool inside = Start.X >= box.Min.X && Start.X <= box.Max.X &&
  405. Start.Y >= box.Min.Y && Start.Y <= box.Max.Y;
  406. if (inside)
  407. {
  408. tMin = tMax = 0.0f;
  409. return true;
  410. }
  411. tMin = tMax = null;
  412. return false;
  413. }
  414. if (!Collision2D.ClipLineToAabb(Start, d, box.Min, box.Max, 0.0f, 1.0f, out float tEnter, out float tExit))
  415. {
  416. tMin = tMax = null;
  417. return false;
  418. }
  419. tMin = tEnter;
  420. tMax = tExit;
  421. return true;
  422. }
  423. /// <summary>
  424. /// Tests if this line segment intersects with an axis-aligned bounding box.
  425. /// </summary>
  426. /// <param name="box">The bounding box to test against.</param>
  427. /// <returns>
  428. /// <see langword="true"/> if the segment intersects the bounding box; otherwise, <see langword="false"/>.
  429. /// </returns>
  430. public readonly bool Intersects(BoundingBox2D box)
  431. {
  432. return Intersects(box, out _, out _);
  433. }
  434. /// <summary>
  435. /// Tests if this line segment intersects with a circle and computes the parametric distances to the intersection points.
  436. /// </summary>
  437. /// <param name="circle">The circle to test against.</param>
  438. /// <param name="tSegmentMin">
  439. /// When this method returns <see langword="true"/>, contains the parametric distance along this segment
  440. /// to the entry intersection point, in the range [0, 1] where 0 represents the start and 1 represents the end.
  441. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  442. /// </param>
  443. /// <param name="tSegmentMax">
  444. /// When this method returns <see langword="true"/>, contains the parametric distance along this segment
  445. /// to the exit intersection point, in the range [0, 1] where 0 represents the start and 1 represents the end.
  446. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  447. /// </param>
  448. /// <returns>
  449. /// <see langword="true"/> if the segment intersects the circle; otherwise, <see langword="false"/>.
  450. /// </returns>
  451. /// <remarks>
  452. /// For degenerate segments (zero length), returns <see langword="true"/> with tSegmentMin = tSegmentMax = 0
  453. /// if the start point is inside the circle.
  454. /// </remarks>
  455. public readonly bool Intersects(BoundingCircle2D circle, out float? tSegmentMin, out float? tSegmentMax)
  456. {
  457. // C. Ericson, Real-Time Collision Detection, Morgan Kaufmann, 2005
  458. // Parametric intersection of a segment with a circle (2D reduction)
  459. // Derived from Section 5.3.2 "Intersecting Ray or Segment Against Sphere"
  460. Vector2 d = Direction;
  461. float dd = Vector2.Dot(d, d);
  462. // Handle degenerate segment (zero length)
  463. if (dd <= Collision2D.Epsilon * Collision2D.Epsilon)
  464. {
  465. float distSq = Vector2.DistanceSquared(Start, circle.Center);
  466. if (distSq <= circle.Radius * circle.Radius)
  467. {
  468. tSegmentMin = tSegmentMax = 0.0f;
  469. return true;
  470. }
  471. tSegmentMin = tSegmentMax = null;
  472. return false;
  473. }
  474. if (!Collision2D.RayCircleIntersectionInterval(Start, d, circle.Center, circle.Radius, out float tMin, out float tMax))
  475. {
  476. tSegmentMin = tSegmentMax = null;
  477. return false;
  478. }
  479. // Clip ray interval to segment interval [0,1]
  480. if (!Collision2D.ClipInterval(tMin, tMax, 0.0f, 1.0f, out float enter, out float exit))
  481. {
  482. tSegmentMin = tSegmentMax = null;
  483. return false;
  484. }
  485. tSegmentMin = enter;
  486. tSegmentMax = exit;
  487. return true;
  488. }
  489. /// <summary>
  490. /// Tests if this line segment intersects with a circle.
  491. /// </summary>
  492. /// <param name="circle">The circle to test against.</param>
  493. /// <returns>
  494. /// <see langword="true"/> if the segment intersects the circle; otherwise, <see langword="false"/>.
  495. /// </returns>
  496. public readonly bool Intersects(BoundingCircle2D circle)
  497. {
  498. return Intersects(circle, out _, out _);
  499. }
  500. /// <summary>
  501. /// Deconstructs this line segment into its component values.
  502. /// </summary>
  503. /// <param name="start">
  504. /// When this method returns, contains the starting point of this line segment in 2D space.
  505. /// </param>
  506. /// <param name="end">
  507. /// When this method returns, contains the ending point of this line segment in 2D space.
  508. /// </param>
  509. public readonly void Deconstruct(out Vector2 start, out Vector2 end)
  510. {
  511. start = Start;
  512. end = End;
  513. }
  514. /// <summary>
  515. /// Tests if this line segment intersects with a capsule and computes the parametric distances to the intersection points.
  516. /// </summary>
  517. /// <param name="capsule">The capsule to test against.</param>
  518. /// <param name="tMin">
  519. /// When this method returns <see langword="true"/>, contains the parametric distance along this segment
  520. /// to the entry intersection point, in the range [0, 1] where 0 represents the start and 1 represents the end.
  521. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  522. /// </param>
  523. /// <param name="tMax">
  524. /// When this method returns <see langword="true"/>, contains the parametric distance along this segment
  525. /// to the exit intersection point, in the range [0, 1] where 0 represents the start and 1 represents the end.
  526. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  527. /// </param>
  528. /// <returns>
  529. /// <see langword="true"/> if the segment intersects the capsule; otherwise, <see langword="false"/>.
  530. /// </returns>
  531. /// <remarks>
  532. /// For degenerate segments (zero length), returns <see langword="true"/> with tMin = tMax = 0
  533. /// if the start point is inside the capsule.
  534. /// </remarks>
  535. public readonly bool Intersects(BoundingCapsule2D capsule, out float? tMin, out float? tMax)
  536. {
  537. // C. Ericson, Real-Time Collision Detection, Morgan Kaufmann, 2005
  538. // Parametric intersection of a segment with a capsule
  539. // Derived from Section 5.1.9 "Closest Points of Two Line Segments"
  540. // and Section 5.3.7 "Intersecting Ray or Segment Against Cylinder"
  541. Vector2 d = Direction;
  542. float dd = Vector2.Dot(d, d);
  543. float radiusSq = capsule.Radius * capsule.Radius;
  544. // Check for degenerate segment
  545. if (dd <= Collision2D.Epsilon * Collision2D.Epsilon)
  546. {
  547. // Segment is degenerate, treat as point
  548. float distSq = Collision2D.DistanceSquaredPointSegment(Start, capsule.PointA, capsule.PointB, out _, out _);
  549. if (distSq <= radiusSq)
  550. {
  551. tMin = tMax = 0.0f;
  552. return true;
  553. }
  554. tMin = tMax = null;
  555. return false;
  556. }
  557. // Get intersection interval against the infinite parametric line P(t)=Start + t * d
  558. if (!Collision2D.RayCapsuleIntersectionInterval(Start, d, capsule.PointA, capsule.PointB, capsule.Radius, out float t0, out float t1))
  559. {
  560. tMin = tMax = null;
  561. return false;
  562. }
  563. if (!Collision2D.ClipInterval(t0, t1, 0.0f, 1.0f, out float enter, out float exit))
  564. {
  565. tMin = tMax = null;
  566. return false;
  567. }
  568. tMin = enter;
  569. tMax = exit;
  570. return true;
  571. }
  572. /// <summary>
  573. /// Tests if this line segment intersects with a capsule.
  574. /// </summary>
  575. /// <param name="capsule">The capsule to test against.</param>
  576. /// <returns>
  577. /// <see langword="true"/> if the segment intersects the capsule; otherwise, <see langword="false"/>.
  578. /// </returns>
  579. public readonly bool Intersects(BoundingCapsule2D capsule)
  580. {
  581. return Intersects(capsule, out _, out _);
  582. }
  583. /// <summary>
  584. /// Tests if this line segment intersects with an oriented bounding box and computes the parametric distances to the intersection points.
  585. /// </summary>
  586. /// <param name="obb">The oriented bounding box to test against.</param>
  587. /// <param name="tMin">
  588. /// When this method returns <see langword="true"/>, contains the parametric distance along this segment
  589. /// to the entry intersection point, in the range [0, 1] where 0 represents the start and 1 represents the end.
  590. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  591. /// </param>
  592. /// <param name="tMax">
  593. /// When this method returns <see langword="true"/>, contains the parametric distance along this segment
  594. /// to the exit intersection point, in the range [0, 1] where 0 represents the start and 1 represents the end.
  595. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  596. /// </param>
  597. /// <returns>
  598. /// <see langword="true"/> if the segment intersects the box; otherwise, <see langword="false"/>.
  599. /// </returns>
  600. /// <remarks>
  601. /// For degenerate segments (zero length), returns <see langword="true"/> with tMin = tMax = 0
  602. /// if the start point is inside the box.
  603. /// </remarks>
  604. public readonly bool Intersects(OrientedBoundingBox2D obb, out float? tMin, out float? tMax)
  605. {
  606. // C. Ericson, Real-Time Collision Detection, Morgan Kaufmann, 2005
  607. // Parametric intersection of a segment with an oriented box
  608. // Derived from Section 5.3.3 "Intersecting Ray or Segment Against Box"
  609. Vector2 d = Direction;
  610. float dd = Vector2.Dot(d, d);
  611. Vector2 diff = Start - obb.Center;
  612. // Check for degenerate segment (zero length)
  613. if (dd <= Collision2D.Epsilon * Collision2D.Epsilon)
  614. {
  615. // Segment is degenerate, treat it as a point
  616. float x = Vector2.Dot(diff, obb.AxisX);
  617. float y = Vector2.Dot(diff, obb.AxisY);
  618. bool inside = MathF.Abs(x) <= obb.HalfExtents.X &&
  619. MathF.Abs(y) <= obb.HalfExtents.Y;
  620. if (inside)
  621. {
  622. tMin = tMax = 0.0f;
  623. return true;
  624. }
  625. tMin = tMax = null;
  626. return false;
  627. }
  628. // Transform segment start and direction into OBB local space
  629. Vector2 localOrigin = new Vector2(
  630. Vector2.Dot(diff, obb.AxisX),
  631. Vector2.Dot(diff, obb.AxisY)
  632. );
  633. Vector2 localDirection = new Vector2(
  634. Vector2.Dot(d, obb.AxisX),
  635. Vector2.Dot(d, obb.AxisY)
  636. );
  637. // Intersect the parametric line:
  638. // P(T) = localOrigin + t * localDirection
  639. // with local AABB [-halfExtent, +halfExtent]
  640. if (!Collision2D.ClipLineToAabb(localOrigin, localDirection, -obb.HalfExtents, obb.HalfExtents, 0.0f, 1.0f, out float enter, out float exit))
  641. {
  642. tMin = tMax = null;
  643. return false;
  644. }
  645. tMin = enter;
  646. tMax = exit;
  647. return true;
  648. }
  649. /// <summary>
  650. /// Tests if this line segment intersects with an oriented bounding box.
  651. /// </summary>
  652. /// <param name="obb">The oriented bounding box to test against.</param>
  653. /// <returns>
  654. /// <see langword="true"/> if the segment intersects the box; otherwise, <see langword="false"/>.
  655. /// </returns>
  656. public readonly bool Intersects(OrientedBoundingBox2D obb)
  657. {
  658. return Intersects(obb, out _, out _);
  659. }
  660. /// <summary>
  661. /// Tests if this line segment intersects with a polygon and computes the parametric distances to the intersection points.
  662. /// </summary>
  663. /// <param name="polygon">The polygon to test against.</param>
  664. /// <param name="tMin">
  665. /// When this method returns <see langword="true"/>, contains the parametric distance along this segment
  666. /// to the entry intersection point, in the range [0, 1] where 0 represents the start and 1 represents the end.
  667. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  668. /// </param>
  669. /// <param name="tMax">
  670. /// When this method returns <see langword="true"/>, contains the parametric distance along this segment
  671. /// to the exit intersection point, in the range [0, 1] where 0 represents the start and 1 represents the end.
  672. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  673. /// </param>
  674. /// <param name="point">
  675. /// When this method returns <see langword="true"/>, contains the intersection point corresponding to <paramref name="tMin"/>.
  676. /// When this method returns <see langword="false"/>, contains <see langword="null"/>.
  677. /// </param>
  678. /// <returns>
  679. /// <see langword="true"/> if the segment intersects the polygon; otherwise, <see langword="false"/>.
  680. /// </returns>
  681. /// <remarks>
  682. /// For degenerate segments (zero length), returns <see langword="true"/> with tMin = tMax = 0
  683. /// if the start point is inside the polygon.
  684. /// </remarks>
  685. public readonly bool Intersects(BoundingPolygon2D polygon, out float? tMin, out float? tMax, out Vector2? point)
  686. {
  687. Vector2 d = Direction;
  688. float dd = Vector2.Dot(d, d);
  689. // Check for degenerate segment
  690. if (dd <= Collision2D.Epsilon * Collision2D.Epsilon)
  691. {
  692. // Segment is degenerate, treat as point
  693. if (polygon.Contains(Start) == ContainmentType.Contains)
  694. {
  695. tMin = tMax = 0.0f;
  696. point = Start;
  697. return true;
  698. }
  699. tMin = tMax = null;
  700. point = null;
  701. return false;
  702. }
  703. // Clip the segment's parametric line against the polygon
  704. if (!Collision2D.ClipLineToConvexPolygon(Start, d, polygon.Vertices, polygon.Normals, 0.0f, 1.0f, out float t0, out float t1))
  705. {
  706. tMin = tMax = null;
  707. point = null;
  708. return false;
  709. }
  710. tMin = t0;
  711. tMax = t1;
  712. point = Start + d * t0;
  713. return true;
  714. }
  715. /// <summary>
  716. /// Tests if this line segment intersects with a polygon.
  717. /// </summary>
  718. /// <param name="polygon">The polygon to test against.</param>
  719. /// <returns>
  720. /// <see langword="true"/> if the segment intersects the polygon; otherwise, <see langword="false"/>.
  721. /// </returns>
  722. public readonly bool Intersects(BoundingPolygon2D polygon)
  723. {
  724. return Intersects(polygon, out _, out _, out _);
  725. }
  726. /// <inheritdoc/>
  727. public override readonly bool Equals([NotNullWhen(true)] object obj)
  728. {
  729. return obj is LineSegment2D other && Equals(other);
  730. }
  731. /// <inheritdoc/>
  732. public readonly bool Equals(LineSegment2D other)
  733. {
  734. return Start.Equals(other.Start)
  735. && End.Equals(other.End);
  736. }
  737. /// <inheritdoc/>
  738. public override readonly int GetHashCode()
  739. {
  740. return HashCode.Combine(Start, End);
  741. }
  742. /// <inheritdoc/>
  743. public override readonly string ToString()
  744. {
  745. return $"LineSegment2D {{ Start: {Start}, End: {End}, Length: {Length:F3} }}";
  746. }
  747. /// <summary/>
  748. public static bool operator ==(LineSegment2D left, LineSegment2D right)
  749. {
  750. return left.Equals(right);
  751. }
  752. /// <summary/>
  753. public static bool operator !=(LineSegment2D left, LineSegment2D right)
  754. {
  755. return !left.Equals(right);
  756. }
  757. #endregion
  758. }
  759. }