You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

intersections.ts 5.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. import { Bounds } from "types"
  2. import * as vec from "utils/vec"
  3. interface Intersection {
  4. didIntersect: boolean
  5. message: string
  6. points: number[][]
  7. }
  8. function getIntersection(
  9. points: number[][],
  10. message = points.length ? "Intersection" : "No intersection"
  11. ) {
  12. return { didIntersect: points.length > 0, message, points }
  13. }
  14. export function intersectLineSegments(
  15. a1: number[],
  16. a2: number[],
  17. b1: number[],
  18. b2: number[]
  19. ) {
  20. const AB = vec.sub(a1, b1)
  21. const BV = vec.sub(b2, b1)
  22. const AV = vec.sub(a2, a1)
  23. const ua_t = BV[0] * AB[1] - BV[1] * AB[0]
  24. const ub_t = AV[0] * AB[1] - AV[1] * AB[0]
  25. const u_b = BV[1] * AV[0] - BV[0] * AV[1]
  26. if (ua_t === 0 || ub_t === 0) {
  27. return getIntersection([], "Coincident")
  28. }
  29. if (u_b === 0) {
  30. return getIntersection([], "Parallel")
  31. }
  32. if (u_b != 0) {
  33. const ua = ua_t / u_b
  34. const ub = ub_t / u_b
  35. if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
  36. return getIntersection([vec.add(a1, vec.mul(AV, ua))])
  37. }
  38. }
  39. return getIntersection([])
  40. }
  41. export function intersectCircleLineSegment(
  42. c: number[],
  43. r: number,
  44. a1: number[],
  45. a2: number[]
  46. ): Intersection {
  47. const a =
  48. (a2[0] - a1[0]) * (a2[0] - a1[0]) + (a2[1] - a1[1]) * (a2[1] - a1[1])
  49. const b =
  50. 2 * ((a2[0] - a1[0]) * (a1[0] - c[0]) + (a2[1] - a1[1]) * (a1[1] - c[1]))
  51. const cc =
  52. c[0] * c[0] +
  53. c[1] * c[1] +
  54. a1[0] * a1[0] +
  55. a1[1] * a1[1] -
  56. 2 * (c[0] * a1[0] + c[1] * a1[1]) -
  57. r * r
  58. const deter = b * b - 4 * a * cc
  59. if (deter < 0) {
  60. return { didIntersect: false, message: "outside", points: [] }
  61. }
  62. if (deter === 0) {
  63. return { didIntersect: false, message: "tangent", points: [] }
  64. }
  65. var e = Math.sqrt(deter)
  66. var u1 = (-b + e) / (2 * a)
  67. var u2 = (-b - e) / (2 * a)
  68. if ((u1 < 0 || u1 > 1) && (u2 < 0 || u2 > 1)) {
  69. if ((u1 < 0 && u2 < 0) || (u1 > 1 && u2 > 1)) {
  70. return { didIntersect: false, message: "outside", points: [] }
  71. } else {
  72. return { didIntersect: false, message: "inside", points: [] }
  73. }
  74. }
  75. const result = { didIntersect: true, message: "intersection", points: [] }
  76. if (0 <= u1 && u1 <= 1) result.points.push(vec.lrp(a1, a2, u1))
  77. if (0 <= u2 && u2 <= 1) result.points.push(vec.lrp(a1, a2, u2))
  78. return result
  79. }
  80. export function intersectCircleRectangle(
  81. c: number[],
  82. r: number,
  83. point: number[],
  84. size: number[]
  85. ): Intersection[] {
  86. const tl = point
  87. const tr = vec.add(point, [size[0], 0])
  88. const br = vec.add(point, size)
  89. const bl = vec.add(point, [0, size[1]])
  90. const intersections: Intersection[] = []
  91. const topIntersection = intersectCircleLineSegment(c, r, tl, tr)
  92. const rightIntersection = intersectCircleLineSegment(c, r, tr, br)
  93. const bottomIntersection = intersectCircleLineSegment(c, r, bl, br)
  94. const leftIntersection = intersectCircleLineSegment(c, r, tl, bl)
  95. if (topIntersection.didIntersect) {
  96. intersections.push({ ...topIntersection, message: "top" })
  97. }
  98. if (rightIntersection.didIntersect) {
  99. intersections.push({ ...rightIntersection, message: "right" })
  100. }
  101. if (bottomIntersection.didIntersect) {
  102. intersections.push({ ...bottomIntersection, message: "bottom" })
  103. }
  104. if (leftIntersection.didIntersect) {
  105. intersections.push({ ...leftIntersection, message: "left" })
  106. }
  107. return intersections
  108. }
  109. export function intersectRectangleLineSegment(
  110. point: number[],
  111. size: number[],
  112. a1: number[],
  113. a2: number[]
  114. ) {
  115. const tl = point
  116. const tr = vec.add(point, [size[0], 0])
  117. const br = vec.add(point, size)
  118. const bl = vec.add(point, [0, size[1]])
  119. const intersections: Intersection[] = []
  120. const topIntersection = intersectLineSegments(a1, a2, tl, tr)
  121. const rightIntersection = intersectLineSegments(a1, a2, tr, br)
  122. const bottomIntersection = intersectLineSegments(a1, a2, bl, br)
  123. const leftIntersection = intersectLineSegments(a1, a2, tl, bl)
  124. if (topIntersection.didIntersect) {
  125. intersections.push({ ...topIntersection, message: "top" })
  126. }
  127. if (rightIntersection.didIntersect) {
  128. intersections.push({ ...rightIntersection, message: "right" })
  129. }
  130. if (bottomIntersection.didIntersect) {
  131. intersections.push({ ...bottomIntersection, message: "bottom" })
  132. }
  133. if (leftIntersection.didIntersect) {
  134. intersections.push({ ...leftIntersection, message: "left" })
  135. }
  136. return intersections
  137. }
  138. /* -------------------------------------------------- */
  139. /* Shape vs. Bounds */
  140. /* -------------------------------------------------- */
  141. export function intersectCircleBounds(
  142. c: number[],
  143. r: number,
  144. bounds: Bounds
  145. ): Intersection[] {
  146. const { minX, minY, width, height } = bounds
  147. return intersectCircleRectangle(c, r, [minX, minY], [width, height])
  148. }
  149. export function intersectLineSegmentBounds(
  150. a1: number[],
  151. a2: number[],
  152. bounds: Bounds
  153. ) {
  154. const { minX, minY, width, height } = bounds
  155. return intersectRectangleLineSegment([minX, minY], [width, height], a1, a2)
  156. }
  157. export function intersectPolylineBounds(points: number[][], bounds: Bounds) {
  158. const { minX, minY, width, height } = bounds
  159. const intersections: Intersection[] = []
  160. for (let i = 1; i < points.length; i++) {
  161. intersections.push(
  162. ...intersectRectangleLineSegment(
  163. [minX, minY],
  164. [width, height],
  165. points[i - 1],
  166. points[i]
  167. )
  168. )
  169. }
  170. return intersections
  171. }