IntersectionTests.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626
  1. using System;
  2. using Microsoft.Xna.Framework;
  3. namespace MonoGame.Extended;
  4. /// <summary>
  5. /// Provides methods for testing geometric intersections between shapes.
  6. /// </summary>
  7. internal static class IntersectionTests
  8. {
  9. /// <summary>
  10. /// Determines whether two circles intersect.
  11. /// </summary>
  12. /// <param name="a">The first circle.</param>
  13. /// <param name="b">The second circle.</param>
  14. /// <returns>
  15. /// <c>true</c> if the circles intersect; otherwise, <c>false</c>.
  16. /// </returns>
  17. /// <remarks>
  18. /// Two circles intersect when the distance between their centers is less than or equal to the sum of their radii.
  19. /// <para>
  20. /// Reference: Real-Time Collision Detection by Christer Ericson (2005)<br/>
  21. /// Chapter 4.3.1: Bounding Spheres - Sphere-Sphere Intersection
  22. /// </para>
  23. /// </remarks>
  24. public static bool CircleCircle(in Circle a, in Circle b)
  25. {
  26. float distSq = Vector2.DistanceSquared(a.Center, b.Center);
  27. float radiiSum = a.Radius + b.Radius;
  28. return distSq <= radiiSum * radiiSum;
  29. }
  30. /// <summary>
  31. /// Determines whether a circle and an ellipse intersect.
  32. /// </summary>
  33. /// <param name="circle">The circle to test.</param>
  34. /// <param name="ellipse">The ellipse to test.</param>
  35. /// <returns>
  36. /// <c>true</c> if the circle and ellipse intersect; otherwise, <c>false</c>.
  37. /// </returns>
  38. /// <remarks>
  39. /// This method uses an approximation that provides reasonable accuracy for most collision detection scenarios.
  40. /// </remarks>
  41. public static bool CircleEllipse(in Circle circle, in Ellipse ellipse)
  42. {
  43. Vector2 closestPoint = ellipse.ClosestPointTo(circle.Center);
  44. float distSq = Vector2.DistanceSquared(closestPoint, circle.Center);
  45. return distSq <= circle.Radius * circle.Radius;
  46. }
  47. /// <summary>
  48. /// Determines whether a circle and an axis-aligned rectangle intersect.
  49. /// </summary>
  50. /// <param name="circle">The circle to test.</param>
  51. /// <param name="rectangle">The axis-aligned rectangle to test.</param>
  52. /// <returns>
  53. /// <c>true</c> if the circle and rectangle intersect; otherwise, <c>false</c>.
  54. /// </returns>
  55. /// <remarks>
  56. /// Reference: Real-Time Collision Detection by Christer Ericson (2005)<br/>
  57. /// Chapter 5.2.5: Testing Sphere against AABB
  58. /// </remarks>
  59. public static bool CircleRectangleF(in Circle circle, in RectangleF rectangle)
  60. {
  61. float distSq = rectangle.DistanceToSquared(circle.Center);
  62. return distSq <= circle.Radius * circle.Radius;
  63. }
  64. /// <summary>
  65. /// Determines whether a circle and a ray intersect.
  66. /// </summary>
  67. /// <param name="circle">The circle to test.</param>
  68. /// <param name="ray">The ray to test.</param>
  69. /// <returns>
  70. /// <c>true</c> if the ray intersects the circle; otherwise, <c>false</c>.
  71. /// </returns>
  72. /// <remarks>
  73. /// Reference: Real-Time Collision Detection by Christer Ericson (2005)<br/>
  74. /// Chapter 5.3.2: Intersecting Ray or Segment Against Sphere
  75. /// </remarks>
  76. public static bool CircleRay(in Circle circle, in Ray ray)
  77. {
  78. Vector2 m = ray.Origin - circle.Center;
  79. float c = Vector2.Dot(m, m) - circle.Radius * circle.Radius;
  80. // If ray origin is inside circle, there is definitely an intersection
  81. if (c <= 0.0f)
  82. {
  83. return true;
  84. }
  85. float b = Vector2.Dot(m, ray.Direction);
  86. // Exit early if ray origin is outside circle and ray is pointing away
  87. // from circle
  88. if (b > 0.0f)
  89. {
  90. return false;
  91. }
  92. float discriminant = b * b - c;
  93. // A negative discriminant corresponds to ray missing circle
  94. if (discriminant < 0.0f)
  95. {
  96. return false;
  97. }
  98. return true;
  99. }
  100. /// <summary>
  101. /// Determines whether a circle and a line segment intersect.
  102. /// </summary>
  103. /// <param name="circle">The circle to test.</param>
  104. /// <param name="lineSegment">The line segment to test.</param>
  105. /// <returns>
  106. /// <c>true</c> if the circle and line segment intersect; otherwise, <c>false</c>.
  107. /// </returns>
  108. /// <remarks>
  109. /// Reference: Real-Time Collision Detection by Christer Ericson (2005)<br/>
  110. /// Chapter 5.1.2: Closest Point on Line Segment to Point<br/>
  111. /// Chapter 5.1.2.1: Distance of Point to Segment
  112. /// </remarks>
  113. public static bool CirceLineSegment(in Circle circle, in LineSegment lineSegment)
  114. {
  115. float distSq = lineSegment.DistanceSquaredTo(circle.Center);
  116. return distSq <= circle.Radius * circle.Radius;
  117. }
  118. /// <summary>
  119. /// Determines whether two ellipses intersect.
  120. /// </summary>
  121. /// <param name="a">The first ellipse.</param>
  122. /// <param name="b">The second ellipse.</param>
  123. /// <returns>
  124. /// <c>true</c> if the ellipses intersect; otherwise, <c>false</c>.
  125. /// </returns>
  126. /// <remarks>
  127. /// This method uses an approximation that provides reasonable accuracy for most collision detection scenarios.
  128. /// </remarks>
  129. public static bool EllipseEllipse(in Ellipse a, in Ellipse b)
  130. {
  131. Vector2 closestOnB = b.ClosestPointTo(a.Center);
  132. if (a.Contains(closestOnB))
  133. {
  134. return true;
  135. }
  136. Vector2 closestOnA = a.ClosestPointTo(b.Center);
  137. return b.Contains(closestOnA);
  138. }
  139. /// <summary>
  140. /// Determines whether an ellipse and an axis-aligned rectangle intersect.
  141. /// </summary>
  142. /// <param name="ellipse">The ellipse to test.</param>
  143. /// <param name="rectangle">The axis-aligned rectangle to test.</param>
  144. /// <returns>
  145. /// <c>true</c> if the ellipse and rectangle intersect; otherwise, <c>false</c>.
  146. /// </returns>
  147. /// <remarks>
  148. /// Reference: Real-Time Collision Detection by Christer Ericson (2005)<br/>
  149. /// Chapter 5.2.5: Testing Sphere Against AABB<br/>
  150. /// (adapted for ellipses)
  151. /// </remarks>
  152. public static bool EllipseRectangleF(in Ellipse ellipse, in RectangleF rectangle)
  153. {
  154. // Find the closest point on the rectangle to the ellipse center
  155. // Clamp ellipse center to the rectangle bounds (analogous to sphere-AABB test)
  156. float closestX = Math.Clamp(ellipse.Center.X, rectangle.Left, rectangle.Right);
  157. float closestY = Math.Clamp(ellipse.Center.Y, rectangle.Top, rectangle.Bottom);
  158. Vector2 closestPoint = new Vector2(closestX, closestY);
  159. return ellipse.Contains(closestPoint);
  160. }
  161. /// <summary>
  162. /// Determines whether an ellipse and a ray intersect.
  163. /// </summary>
  164. /// <param name="ellipse">The ellipse to test.</param>
  165. /// <param name="ray">The ray to test.</param>
  166. /// <returns>
  167. /// <c>true</c> if the ray intersects the ellipse; otherwise, <c>false</c>.
  168. /// </returns>
  169. /// <remarks>
  170. /// Reference: Real-Time Collision Detection by Christer Ericson (2005)<br/>
  171. /// Chapter 5.3.2: Intersecting Ray or Segment Against Sphere
  172. /// </remarks>
  173. public static bool EllipseRay(in Ellipse ellipse, in Ray ray)
  174. {
  175. // Transform the ray into ellipse local space where it's a unit circle
  176. // This makes the intersection test much simpler
  177. // 1. Translate ray origin relative to ellipse center
  178. Vector2 localOrigin = ray.Origin - ellipse.Center;
  179. // 2. Rotate ray by -ellipse.Rotation to align with axes
  180. float cos = MathF.Cos(-ellipse.Rotation);
  181. float sin = MathF.Sin(-ellipse.Rotation);
  182. Vector2 rotatedOrigin = new Vector2(
  183. localOrigin.X * cos - localOrigin.Y * sin,
  184. localOrigin.X * sin + localOrigin.Y * cos
  185. );
  186. Vector2 rotatedDirection = new Vector2(
  187. ray.Direction.X * cos - ray.Direction.Y * sin,
  188. ray.Direction.X * sin + ray.Direction.Y * cos
  189. );
  190. // 3. Scale to transform ellipse into unit circle
  191. Vector2 scaledOrigin = new Vector2(
  192. rotatedOrigin.X / ellipse.RadiusX,
  193. rotatedOrigin.Y / ellipse.RadiusY
  194. );
  195. Vector2 scaledDirection = new Vector2(
  196. rotatedDirection.X / ellipse.RadiusX,
  197. rotatedDirection.Y / ellipse.RadiusY
  198. );
  199. // 4. Normalize the scaled directions
  200. float dirLength = scaledDirection.Length();
  201. if (dirLength < 0.0001f)
  202. {
  203. // Degenerate ray
  204. return false;
  205. }
  206. scaledDirection /= dirLength;
  207. // 5. Perform ray-circle intersection in unit circle space
  208. // Ray equation: P(t) = origin + t * direction
  209. // Circle equation: x^2 + y^2 = 1
  210. // Substitute and solve quadratic: (o + td)·(o + td) = 1
  211. float a = Vector2.Dot(scaledDirection, scaledDirection);
  212. float b = 2.0f * Vector2.Dot(scaledOrigin, scaledDirection);
  213. float c = Vector2.Dot(scaledOrigin, scaledOrigin) - 1.0f;
  214. float discriminant = b * b - 4 * a * c;
  215. // No intersection if discriminant is negative
  216. return discriminant >= 0.0f;
  217. }
  218. /// <summary>
  219. /// Determines whether an ellipse and a line segment intersect.
  220. /// </summary>
  221. /// <param name="ellipse">The ellipse to test.</param>
  222. /// <param name="lineSegment">The line segment to test.</param>
  223. /// <returns>
  224. /// <c>true</c> if the ellipse and line segment intersect; otherwise, <c>false</c>.
  225. /// </returns>
  226. /// <remarks>
  227. /// Reference: Real-Time Collision Detection by Christer Ericson (2005)<br/>
  228. /// Chapter 5.3.2: Intersecting Ray or Segment Against Sphere
  229. /// </remarks>
  230. public static bool EllipseLineSegment(in Ellipse ellipse, in LineSegment lineSegment)
  231. {
  232. // Transform the line segment into ellipse local space
  233. Vector2 p1 = lineSegment.Start;
  234. Vector2 p2 = lineSegment.End;
  235. Vector2 direction = p2 - p1;
  236. float segmentLength = direction.Length();
  237. if (segmentLength < 0.0001f)
  238. {
  239. // Degenerate segment
  240. // just test point containment
  241. return ellipse.Contains(p1);
  242. }
  243. direction /= segmentLength;
  244. // 1. Translate segment relative to ellipse center
  245. Vector2 localP1 = p1 - ellipse.Center;
  246. // 2. Rotate segment by -ellipse.Rotation
  247. float cos = MathF.Cos(-ellipse.Rotation);
  248. float sin = MathF.Sin(-ellipse.Rotation);
  249. Vector2 rotatedP1 = new Vector2(
  250. localP1.X * cos - localP1.Y * sin,
  251. localP1.X * sin + localP1.Y * cos
  252. );
  253. Vector2 rotatedDir = new Vector2(
  254. direction.X * cos - direction.Y * sin,
  255. direction.X * sin + direction.Y * cos
  256. );
  257. // 3. Scale to unit circle space
  258. Vector2 scaledOrigin = new Vector2(
  259. rotatedP1.X / ellipse.RadiusX,
  260. rotatedP1.Y / ellipse.RadiusY
  261. );
  262. Vector2 scaledDirection = new Vector2(
  263. rotatedDir.X / ellipse.RadiusX,
  264. rotatedDir.Y / ellipse.RadiusY
  265. );
  266. float scaledDirLength = scaledDirection.Length();
  267. if (scaledDirLength < 0.0001f)
  268. {
  269. return ellipse.Contains(p1);
  270. }
  271. scaledDirection /= scaledDirLength;
  272. // 4. Solve quadratic for ray-circle intersection
  273. float a = Vector2.Dot(scaledDirection, scaledDirection);
  274. float b = 2.0f * Vector2.Dot(scaledOrigin, scaledDirection);
  275. float c = Vector2.Dot(scaledOrigin, scaledOrigin) - 1.0f;
  276. float discriminant = b * b - 4 * a * c;
  277. if (discriminant < 0)
  278. {
  279. // no intersection
  280. return false;
  281. }
  282. // 5. Check if intersection points lie within segment bounds
  283. float sqrtDiscriminant = MathF.Sqrt(discriminant);
  284. float t1 = (-b - sqrtDiscriminant) / (2 * a);
  285. float t2 = (-b + sqrtDiscriminant) / (2 * a);
  286. // Scale t values back to original segment parameterization
  287. // Account for the scaling transformation
  288. float scaleRatio = segmentLength / scaledDirLength;
  289. t1 *= scaleRatio;
  290. t2 *= scaleRatio;
  291. // Check if either intersection point is within [0, segmentLength]
  292. return (t1 >= 0 && t1 <= segmentLength)
  293. || (t2 >= 0 && t2 <= segmentLength)
  294. || (t1 < 0 && t2 > segmentLength);
  295. }
  296. /// <summary>
  297. /// Determines whether two axis-aligned rectangles intersect.
  298. /// </summary>
  299. /// <param name="a">The first rectangle.</param>
  300. /// <param name="b">The second rectangle.</param>
  301. /// <returns>
  302. /// <c>true</c> if the rectangles intersect; otherwise, <c>false</c>.
  303. /// </returns>
  304. /// <remarks>
  305. /// Reference: Real-Time Collision Detection by Christer Ericson (2005)<br/>
  306. /// Chapter 4.2.1: AABB-AABB Intersection
  307. /// </remarks>
  308. public static bool RectangleFRectangleF(in RectangleF a, in RectangleF b)
  309. {
  310. return a.Left < b.Right
  311. && b.Left < a.Right
  312. && a.Top < b.Bottom
  313. && b.Bottom < a.Top;
  314. }
  315. /// <summary>
  316. /// Determines whether an axis-aligned rectangle and a ray intersect.
  317. /// </summary>
  318. /// <param name="rectangle">The axis-aligned rectangle to test.</param>
  319. /// <param name="ray">The ray to test.</param>
  320. /// <returns>
  321. /// <c>true</c> if the ray intersects the rectangle; otherwise, <c>false</c>.
  322. /// </returns>
  323. /// <remarks>
  324. /// Reference: Real-Time Collision Detection by Christer Ericson (2005)<br/>
  325. /// Chapter 5.3.3: Intersecting Ray or Segment Against Box
  326. /// </remarks>
  327. public static bool RectangleFRay(in RectangleF rectangle, in Ray ray)
  328. {
  329. float tMin = 0.0f;
  330. float tMax = float.MaxValue;
  331. // Test intersection with X slab
  332. if (MathF.Abs(ray.Direction.X) < MathExtended.Epsilon)
  333. {
  334. // Ray is parallel to slap.
  335. // No hit if origin not within slab
  336. if (ray.Origin.X < rectangle.Left || ray.Origin.X > rectangle.Right)
  337. {
  338. return false;
  339. }
  340. }
  341. else
  342. {
  343. // Compute intersection t value of ray with near and far plane of slab
  344. float ood = 1.0f / ray.Direction.X;
  345. float t1 = (rectangle.Left - ray.Origin.X) * ood;
  346. float t2 = (rectangle.Right - ray.Origin.X) * ood;
  347. // Make t1 be intersection with near plan, t2 with far plane
  348. if (t1 > t2)
  349. {
  350. (t1, t2) = (t2, t1);
  351. }
  352. // Compute the intersection of slab intersection intervals
  353. tMin = MathF.Max(t1, tMin);
  354. tMax = MathF.Min(t2, tMax);
  355. // Exit with no collision as soon as slab intersection becomes empty
  356. if (tMin > tMax)
  357. {
  358. return false;
  359. }
  360. }
  361. // Test intersection with Y slab
  362. if (MathF.Abs(ray.Direction.Y) < MathExtended.Epsilon)
  363. {
  364. // Ray is parallel to slab.
  365. // No hit if origin not within slab
  366. if (ray.Origin.Y < rectangle.Top || ray.Origin.Y > rectangle.Bottom)
  367. {
  368. return false;
  369. }
  370. }
  371. else
  372. {
  373. // Compute the intersection t value of ray with near and far plane of slab
  374. float ood = 1.0f / ray.Direction.Y;
  375. float t1 = (rectangle.Top - ray.Origin.Y) * ood;
  376. float t2 = (rectangle.Bottom - ray.Origin.Y) * ood;
  377. // Make t1 be intersection with near plan, t2 with far plan
  378. if (t1 > t2)
  379. {
  380. (t1, t2) = (t2, t1);
  381. }
  382. // Compute the intersection of slab intersection intervals
  383. tMin = MathF.Max(t1, tMin);
  384. tMax = MathF.Min(t2, tMax);
  385. // Exit with no collision as soon as slab intersection becomes empty
  386. if (tMin > tMax)
  387. {
  388. return false;
  389. }
  390. }
  391. // Ray intersects all slabs
  392. return true;
  393. }
  394. /// <summary>
  395. /// Determines whether an axis-aligned rectangle and a line segment intersect.
  396. /// </summary>
  397. /// <param name="rectangle">The axis-aligned rectangle to test.</param>
  398. /// <param name="lineSegment">The line segment to test.</param>
  399. /// <returns>
  400. /// <c>true</c> if the rectangle and line segment intersect; otherwise, <c>false</c>.
  401. /// </returns>
  402. /// <remarks>
  403. /// Reference: Real-Time Collision Detection by Christer Ericson (2005)<br/>
  404. /// Chapter 5.3.3: Testing Segment Against AABB
  405. /// </remarks>
  406. public static bool RectangleFLineSegment(in RectangleF rectangle, in LineSegment lineSegment)
  407. {
  408. // Box center point
  409. Vector2 c = rectangle.Center;
  410. // Box half length extents
  411. Vector2 e = new Vector2(rectangle.Width, rectangle.Height) * 0.5f;
  412. // Segment midpoint
  413. Vector2 m = lineSegment.Center;
  414. // Segment half length vector
  415. Vector2 d = lineSegment.End - m;
  416. // Translate box and segment to origin
  417. m -= c;
  418. // Try world coordinate axes as separating axes
  419. float adx = MathF.Abs(d.X);
  420. if (MathF.Abs(m.X) > e.X + adx) return false;
  421. float ady = MathF.Abs(d.Y);
  422. if (MathF.Abs(m.Y) > e.Y + ady) return false;
  423. // Add in an epsilon term to counteract arithmetic errors when segment
  424. // is (near) parallel to coordinate axis
  425. adx += MathExtended.Epsilon;
  426. ady += MathExtended.Epsilon;
  427. // Try cross products of segment direction vector with coordinate axes
  428. // For 2D, we only have one cross product to test (the Z component of the 3D cross product)
  429. if (Math.Abs(m.X * d.Y - m.Y * d.X) > e.X * ady + e.Y * adx) return false;
  430. // No separating axis found, segment must be overlapping AABB
  431. return true;
  432. }
  433. /// <summary>
  434. /// Determines whether two rays in 2D intersect.
  435. /// </summary>
  436. /// <param name="a">The first ray.</param>
  437. /// <param name="b">The second ray.</param>
  438. /// <returns>
  439. /// <c>true</c> if the rays intersect; otherwise, <c>false</c>.
  440. /// </returns>
  441. /// <remarks>
  442. /// Returns <c>false</c> for parallel or coincident rays.
  443. /// <para>
  444. /// Reference: Real-Time Collision Detection by Christer Ericson (2005)<br/>
  445. /// Chapter 5.1.9.1: 2D Segment Intersection
  446. /// </para>
  447. /// </remarks>
  448. public static bool RayRay(in Ray a, in Ray b)
  449. {
  450. Vector2 r = b.Origin - a.Origin;
  451. // Using 2D cross product: s = (r × b.Direction) / (a.Direction × b.Direction)
  452. // For 2D vectors: u × v = u.X * v.Y - u.Y * v.X
  453. float denom = a.Direction.X * b.Direction.Y - a.Direction.Y * b.Direction.X;
  454. // Parallel or coincident rays
  455. if (MathF.Abs(denom) < MathExtended.Epsilon)
  456. {
  457. return false;
  458. }
  459. float s = (r.X * b.Direction.Y - r.Y * b.Direction.X) / denom;
  460. float t = (r.X * a.Direction.Y - r.Y * a.Direction.X) / denom;
  461. // Rays intersect if both parameters are non-negative
  462. return s >= 0.0f & t >= 0.0f;
  463. }
  464. /// <summary>
  465. /// Determines whether a ray and a line segment in 2D intersect.
  466. /// </summary>
  467. /// <param name="ray">The ray to test.</param>
  468. /// <param name="lineSegment">The line segment to test.</param>
  469. /// <returns>
  470. /// <c>true</c> if the ray and line segment intersect; otherwise, <c>false</c>.
  471. /// </returns>
  472. /// <remarks>
  473. /// Returns <c>false</c> for parallel or coincident cases.
  474. /// <para>
  475. /// Reference: Real-Time Collision Detection by Christer Ericson (2005)<br/>
  476. /// Chapter 5.3.1: Intersecting Segment Against Plane
  477. /// </para>
  478. /// </remarks>
  479. public static bool RayLineSegment(in Ray ray, in LineSegment lineSegment)
  480. {
  481. Vector2 segmentDir = lineSegment.Direction;
  482. Vector2 r = lineSegment.Start - ray.Origin;
  483. // Using 2D cross produce to solve the system
  484. float denom = ray.Direction.X * segmentDir.Y - ray.Direction.Y * segmentDir.X;
  485. // Parallel or coincident
  486. if (MathF.Abs(denom) < MathExtended.Epsilon)
  487. {
  488. return false;
  489. }
  490. float s = (r.X * segmentDir.Y - r.Y * segmentDir.X) / denom;
  491. float t = (r.X * ray.Direction.Y - r.Y * ray.Direction.X) / denom;
  492. // Ray intersects segment if s >= 0 (ray constraint) and 0 <= t <= 1 (segment constraint)
  493. return s >= 0.0f && t >= 0.0f && t <= 1.0f;
  494. }
  495. /// <summary>
  496. /// Determines whether two line segments in 2D intersect.
  497. /// </summary>
  498. /// <param name="a">The first line segment.</param>
  499. /// <param name="b">The second line segment.</param>
  500. /// <returns>
  501. /// <c>true</c> if the line segments intersect; otherwise, <c>false</c>.
  502. /// </returns>
  503. /// <remarks>
  504. /// Reference: Real-Time Collision Detection by Christer Ericson (2005)<br/>
  505. /// Chapter 5.1.9.1: 2D Segment Intersection
  506. /// </remarks>
  507. public static bool LineSegmentLineSegment(in LineSegment a, in LineSegment b)
  508. {
  509. // Compute signed areas for triangles formed by segment a and endpoints of segment b
  510. float a1 = SignedTriangleArea(a.Start, a.End, b.End);
  511. float a2 = SignedTriangleArea(a.Start, a.End, b.Start);
  512. // If both endpoints of b are on the same side of a, no intersection
  513. if (a1 * a2 > 0.0f) return false;
  514. // Compute signed areas for triangles formed by segment b and endpoints of segment a
  515. float a3 = SignedTriangleArea(b.Start, b.End, a.Start);
  516. float a4 = SignedTriangleArea(b.Start, b.End, a.End);
  517. // If both endpoints of a are on the same side of b, no intersection
  518. if (a3 * a4 > 0.0f) return false;
  519. // Segments intersect
  520. return true;
  521. }
  522. /// <summary>
  523. /// Computes twice the signed area of a triangle defined by three points.
  524. /// </summary>
  525. /// <param name="a">The first vertex of the triangle.</param>
  526. /// <param name="b">The second vertex of the triangle.</param>
  527. /// <param name="c">The third vertex of the triangle.</param>
  528. /// <returns>
  529. /// A positive value if the triangle vertices are ordered counterclockwise,
  530. /// a negative value if clockwise, or zero if the points are collinear.
  531. /// </returns>
  532. /// <remarks>
  533. /// Reference: Real-Time Collision Detection by Christer Ericson (2005)<br/>
  534. /// Chapter 5.1.9.1: 2D Segment Intersection
  535. /// </remarks>
  536. private static float SignedTriangleArea(Vector2 a, Vector2 b, Vector2 c)
  537. {
  538. return (a.X - c.X) * (b.Y - c.Y) - (a.Y - c.Y) * (b.X - c.X);
  539. }
  540. }