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.

index.ts 29KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241
  1. import { Vec } from '@tldraw/vec'
  2. export type TLIntersection = {
  3. didIntersect: boolean
  4. message: string
  5. points: number[][]
  6. }
  7. export interface TLBounds {
  8. minX: number
  9. minY: number
  10. maxX: number
  11. maxY: number
  12. width: number
  13. height: number
  14. rotation?: number
  15. }
  16. /**
  17. * Get an intersection.
  18. * @param message
  19. * @param points
  20. * @internal
  21. */
  22. function createIntersection(message: string, ...points: number[][]): TLIntersection {
  23. const didIntersect = points.length > 0
  24. return { didIntersect, message, points }
  25. }
  26. /**
  27. *
  28. * @param point
  29. * @param size
  30. * @param rotation
  31. * @internal
  32. */
  33. function getRectangleSides(point: number[], size: number[], rotation = 0): [string, number[][]][] {
  34. const center = [point[0] + size[0] / 2, point[1] + size[1] / 2]
  35. const tl = Vec.rotWith(point, center, rotation)
  36. const tr = Vec.rotWith(Vec.add(point, [size[0], 0]), center, rotation)
  37. const br = Vec.rotWith(Vec.add(point, size), center, rotation)
  38. const bl = Vec.rotWith(Vec.add(point, [0, size[1]]), center, rotation)
  39. return [
  40. ['top', [tl, tr]],
  41. ['right', [tr, br]],
  42. ['bottom', [br, bl]],
  43. ['left', [bl, tl]],
  44. ]
  45. }
  46. /**
  47. * Get whether angle c lies between angles a and b.
  48. * @param a
  49. * @param b
  50. * @param c
  51. * @internal
  52. */
  53. function isAngleBetween(a: number, b: number, c: number): boolean {
  54. if (c === a || c === b) return true
  55. const PI2 = Math.PI * 2
  56. const AB = (b - a + PI2) % PI2
  57. const AC = (c - a + PI2) % PI2
  58. return AB <= Math.PI !== AC > AB
  59. }
  60. /* -------------------------------------------------- */
  61. /* Ray */
  62. /* -------------------------------------------------- */
  63. /**
  64. * Find the intersection between a ray and a ray.
  65. * @param p0 The first ray's point
  66. * @param n0 The first ray's direction vector.
  67. * @param p1 The second ray's point.
  68. * @param n1 The second ray's direction vector.
  69. */
  70. export function intersectRayRay(
  71. p0: number[],
  72. n0: number[],
  73. p1: number[],
  74. n1: number[]
  75. ): TLIntersection {
  76. const dx = p1[0] - p0[0]
  77. const dy = p1[1] - p0[1]
  78. const det = n1[0] * n0[1] - n1[1] * n0[0]
  79. const u = (dy * n1[0] - dx * n1[1]) / det
  80. const v = (dy * n0[0] - dx * n0[1]) / det
  81. if (u < 0 || v < 0) return createIntersection('miss')
  82. const m0 = n0[1] / n0[0]
  83. const m1 = n1[1] / n1[0]
  84. const b0 = p0[1] - m0 * p0[0]
  85. const b1 = p1[1] - m1 * p1[0]
  86. const x = (b1 - b0) / (m0 - m1)
  87. const y = m0 * x + b0
  88. return Number.isFinite(x)
  89. ? createIntersection('intersection', [x, y])
  90. : createIntersection('parallel')
  91. }
  92. /**
  93. * Find the intersections between a ray and a line segment.
  94. * @param origin
  95. * @param direction
  96. * @param a1
  97. * @param a2
  98. */
  99. export function intersectRayLineSegment(
  100. origin: number[],
  101. direction: number[],
  102. a1: number[],
  103. a2: number[]
  104. ): TLIntersection {
  105. const [x, y] = origin
  106. const [dx, dy] = direction
  107. const [x1, y1] = a1
  108. const [x2, y2] = a2
  109. if (dy / dx !== (y2 - y1) / (x2 - x1)) {
  110. const d = dx * (y2 - y1) - dy * (x2 - x1)
  111. if (d !== 0) {
  112. const r = ((y - y1) * (x2 - x1) - (x - x1) * (y2 - y1)) / d
  113. const s = ((y - y1) * dx - (x - x1) * dy) / d
  114. if (r >= 0 && s >= 0 && s <= 1) {
  115. return createIntersection('intersection', [x + r * dx, y + r * dy])
  116. }
  117. }
  118. }
  119. return createIntersection('no intersection')
  120. }
  121. /**
  122. * Find the intersections between a ray and a rectangle.
  123. * @param origin
  124. * @param direction
  125. * @param point
  126. * @param size
  127. * @param rotation
  128. */
  129. export function intersectRayRectangle(
  130. origin: number[],
  131. direction: number[],
  132. point: number[],
  133. size: number[],
  134. rotation = 0
  135. ): TLIntersection[] {
  136. return intersectRectangleRay(point, size, rotation, origin, direction)
  137. }
  138. /**
  139. * Find the intersections between a ray and an ellipse.
  140. * @param origin
  141. * @param direction
  142. * @param center
  143. * @param rx
  144. * @param ry
  145. * @param rotation
  146. */
  147. export function intersectRayEllipse(
  148. origin: number[],
  149. direction: number[],
  150. center: number[],
  151. rx: number,
  152. ry: number,
  153. rotation: number
  154. ): TLIntersection {
  155. const a1 = origin
  156. const a2 = Vec.mul(direction, 999999999)
  157. return intersectLineSegmentEllipse(a1, a2, center, rx, ry, rotation)
  158. }
  159. /**
  160. * Find the intersections between a ray and a bounding box.
  161. * @param origin
  162. * @param direction
  163. * @param bounds
  164. * @param rotation
  165. */
  166. export function intersectRayBounds(
  167. origin: number[],
  168. direction: number[],
  169. bounds: TLBounds,
  170. rotation = 0
  171. ): TLIntersection[] {
  172. const { minX, minY, width, height } = bounds
  173. return intersectRayRectangle(origin, direction, [minX, minY], [width, height], rotation)
  174. }
  175. /* -------------------------------------------------- */
  176. /* Line Segment */
  177. /* -------------------------------------------------- */
  178. /**
  179. * Find the intersection between a line segment and a ray.
  180. * @param a1
  181. * @param a2
  182. * @param origin
  183. * @param direction
  184. */
  185. export function intersectLineSegmentRay(
  186. a1: number[],
  187. a2: number[],
  188. origin: number[],
  189. direction: number[]
  190. ): TLIntersection {
  191. return intersectRayLineSegment(origin, direction, a1, a2)
  192. }
  193. /**
  194. * Find the intersection between a line segment and a line segment.
  195. * @param a1
  196. * @param a2
  197. * @param b1
  198. * @param b2
  199. */
  200. export function intersectLineSegmentLineSegment(
  201. a1: number[],
  202. a2: number[],
  203. b1: number[],
  204. b2: number[]
  205. ): TLIntersection {
  206. const AB = Vec.sub(a1, b1)
  207. const BV = Vec.sub(b2, b1)
  208. const AV = Vec.sub(a2, a1)
  209. const ua_t = BV[0] * AB[1] - BV[1] * AB[0]
  210. const ub_t = AV[0] * AB[1] - AV[1] * AB[0]
  211. const u_b = BV[1] * AV[0] - BV[0] * AV[1]
  212. if (ua_t === 0 || ub_t === 0) {
  213. return createIntersection('coincident')
  214. }
  215. if (u_b === 0) {
  216. return createIntersection('parallel')
  217. }
  218. if (u_b !== 0) {
  219. const ua = ua_t / u_b
  220. const ub = ub_t / u_b
  221. if (0 <= ua && ua <= 1 && 0 <= ub && ub <= 1) {
  222. return createIntersection('intersection', Vec.add(a1, Vec.mul(AV, ua)))
  223. }
  224. }
  225. return createIntersection('no intersection')
  226. }
  227. /**
  228. * Find the intersections between a line segment and a rectangle.
  229. * @param a1
  230. * @param a2
  231. * @param point
  232. * @param size
  233. */
  234. export function intersectLineSegmentRectangle(
  235. a1: number[],
  236. a2: number[],
  237. point: number[],
  238. size: number[]
  239. ): TLIntersection[] {
  240. return intersectRectangleLineSegment(point, size, a1, a2)
  241. }
  242. /**
  243. * Find the intersections between a line segment and an arc.
  244. * @param a1
  245. * @param a2
  246. * @param center
  247. * @param radius
  248. * @param start
  249. * @param end
  250. */
  251. export function intersectLineSegmentArc(
  252. a1: number[],
  253. a2: number[],
  254. center: number[],
  255. radius: number,
  256. start: number[],
  257. end: number[]
  258. ): TLIntersection {
  259. const sa = Vec.angle(center, start)
  260. const ea = Vec.angle(center, end)
  261. const ellipseTest = intersectEllipseLineSegment(center, radius, radius, 0, a1, a2)
  262. if (!ellipseTest.didIntersect) return createIntersection('no intersection')
  263. const points = ellipseTest.points.filter((point) =>
  264. isAngleBetween(sa, ea, Vec.angle(center, point))
  265. )
  266. if (points.length === 0) {
  267. return createIntersection('no intersection')
  268. }
  269. return createIntersection('intersection', ...points)
  270. }
  271. /**
  272. * Find the intersections between a line segment and a circle.
  273. * @param a1
  274. * @param a2
  275. * @param c
  276. * @param r
  277. */
  278. export function intersectLineSegmentCircle(
  279. a1: number[],
  280. a2: number[],
  281. c: number[],
  282. r: number
  283. ): TLIntersection {
  284. const a = (a2[0] - a1[0]) * (a2[0] - a1[0]) + (a2[1] - a1[1]) * (a2[1] - a1[1])
  285. const b = 2 * ((a2[0] - a1[0]) * (a1[0] - c[0]) + (a2[1] - a1[1]) * (a1[1] - c[1]))
  286. const cc =
  287. c[0] * c[0] +
  288. c[1] * c[1] +
  289. a1[0] * a1[0] +
  290. a1[1] * a1[1] -
  291. 2 * (c[0] * a1[0] + c[1] * a1[1]) -
  292. r * r
  293. const deter = b * b - 4 * a * cc
  294. if (deter < 0) {
  295. return createIntersection('outside')
  296. }
  297. if (deter === 0) {
  298. return createIntersection('tangent')
  299. }
  300. const e = Math.sqrt(deter)
  301. const u1 = (-b + e) / (2 * a)
  302. const u2 = (-b - e) / (2 * a)
  303. if ((u1 < 0 || u1 > 1) && (u2 < 0 || u2 > 1)) {
  304. if ((u1 < 0 && u2 < 0) || (u1 > 1 && u2 > 1)) {
  305. return createIntersection('outside')
  306. } else {
  307. return createIntersection('inside')
  308. }
  309. }
  310. const results: number[][] = []
  311. if (0 <= u1 && u1 <= 1) results.push(Vec.lrp(a1, a2, u1))
  312. if (0 <= u2 && u2 <= 1) results.push(Vec.lrp(a1, a2, u2))
  313. return createIntersection('intersection', ...results)
  314. }
  315. /**
  316. * Find the intersections between a line segment and an ellipse.
  317. * @param a1
  318. * @param a2
  319. * @param center
  320. * @param rx
  321. * @param ry
  322. * @param rotation
  323. */
  324. export function intersectLineSegmentEllipse(
  325. a1: number[],
  326. a2: number[],
  327. center: number[],
  328. rx: number,
  329. ry: number,
  330. rotation = 0
  331. ): TLIntersection {
  332. // If the ellipse or line segment are empty, return no tValues.
  333. if (rx === 0 || ry === 0 || Vec.isEqual(a1, a2)) {
  334. return createIntersection('no intersection')
  335. }
  336. // Get the semimajor and semiminor axes.
  337. rx = rx < 0 ? rx : -rx
  338. ry = ry < 0 ? ry : -ry
  339. // Rotate points and translate so the ellipse is centered at the origin.
  340. a1 = Vec.sub(Vec.rotWith(a1, center, -rotation), center)
  341. a2 = Vec.sub(Vec.rotWith(a2, center, -rotation), center)
  342. // Calculate the quadratic parameters.
  343. const diff = Vec.sub(a2, a1)
  344. const A = (diff[0] * diff[0]) / rx / rx + (diff[1] * diff[1]) / ry / ry
  345. const B = (2 * a1[0] * diff[0]) / rx / rx + (2 * a1[1] * diff[1]) / ry / ry
  346. const C = (a1[0] * a1[0]) / rx / rx + (a1[1] * a1[1]) / ry / ry - 1
  347. // Make a list of t values (normalized points on the line where intersections occur).
  348. const tValues: number[] = []
  349. // Calculate the discriminant.
  350. const discriminant = B * B - 4 * A * C
  351. if (discriminant === 0) {
  352. // One real solution.
  353. tValues.push(-B / 2 / A)
  354. } else if (discriminant > 0) {
  355. const root = Math.sqrt(discriminant)
  356. // Two real solutions.
  357. tValues.push((-B + root) / 2 / A)
  358. tValues.push((-B - root) / 2 / A)
  359. }
  360. // Filter to only points that are on the segment.
  361. // Solve for points, then counter-rotate points.
  362. const points = tValues
  363. .filter((t) => t >= 0 && t <= 1)
  364. .map((t) => Vec.add(center, Vec.add(a1, Vec.mul(Vec.sub(a2, a1), t))))
  365. .map((p) => Vec.rotWith(p, center, rotation))
  366. return createIntersection('intersection', ...points)
  367. }
  368. /**
  369. * Find the intersections between a line segment and a bounding box.
  370. * @param a1
  371. * @param a2
  372. * @param bounds
  373. */
  374. export function intersectLineSegmentBounds(
  375. a1: number[],
  376. a2: number[],
  377. bounds: TLBounds
  378. ): TLIntersection[] {
  379. return intersectBoundsLineSegment(bounds, a1, a2)
  380. }
  381. /**
  382. * Find the intersections between a line segment and a polyline.
  383. * @param a1
  384. * @param a2
  385. * @param points
  386. */
  387. export function intersectLineSegmentPolyline(
  388. a1: number[],
  389. a2: number[],
  390. points: number[][]
  391. ): TLIntersection {
  392. const pts: number[][] = []
  393. for (let i = 1; i < points.length; i++) {
  394. const int = intersectLineSegmentLineSegment(a1, a2, points[i - 1], points[i])
  395. if (int) {
  396. pts.push(...int.points)
  397. }
  398. }
  399. if (pts.length === 0) {
  400. return createIntersection('no intersection')
  401. }
  402. return createIntersection('intersection', ...points)
  403. }
  404. /**
  405. * Find the intersections between a line segment and a closed polygon.
  406. * @param a1
  407. * @param a2
  408. * @param points
  409. */
  410. export function intersectLineSegmentPolygon(
  411. a1: number[],
  412. a2: number[],
  413. points: number[][]
  414. ): TLIntersection {
  415. const pts: number[][] = []
  416. for (let i = 1; i < points.length + 1; i++) {
  417. const int = intersectLineSegmentLineSegment(a1, a2, points[i - 1], points[i % points.length])
  418. if (int) {
  419. pts.push(...int.points)
  420. }
  421. }
  422. if (pts.length === 0) {
  423. return createIntersection('no intersection')
  424. }
  425. return createIntersection('intersection', ...points)
  426. }
  427. /* -------------------------------------------------- */
  428. /* Rectangle */
  429. /* -------------------------------------------------- */
  430. /**
  431. * Find the intersections between a rectangle and a ray.
  432. * @param point
  433. * @param size
  434. * @param rotation
  435. * @param origin
  436. * @param direction
  437. */
  438. export function intersectRectangleRay(
  439. point: number[],
  440. size: number[],
  441. rotation: number,
  442. origin: number[],
  443. direction: number[]
  444. ): TLIntersection[] {
  445. const sideIntersections = getRectangleSides(point, size, rotation).reduce<TLIntersection[]>(
  446. (acc, [message, [a1, a2]]) => {
  447. const intersection = intersectRayLineSegment(origin, direction, a1, a2)
  448. if (intersection) {
  449. acc.push(createIntersection(message, ...intersection.points))
  450. }
  451. return acc
  452. },
  453. []
  454. )
  455. return sideIntersections.filter((int) => int.didIntersect)
  456. }
  457. /**
  458. * Find the intersections between a rectangle and a line segment.
  459. * @param point
  460. * @param size
  461. * @param a1
  462. * @param a2
  463. */
  464. export function intersectRectangleLineSegment(
  465. point: number[],
  466. size: number[],
  467. a1: number[],
  468. a2: number[]
  469. ): TLIntersection[] {
  470. const sideIntersections = getRectangleSides(point, size).reduce<TLIntersection[]>(
  471. (acc, [message, [b1, b2]]) => {
  472. const intersection = intersectLineSegmentLineSegment(a1, a2, b1, b2)
  473. if (intersection) {
  474. acc.push(createIntersection(message, ...intersection.points))
  475. }
  476. return acc
  477. },
  478. []
  479. )
  480. return sideIntersections.filter((int) => int.didIntersect)
  481. }
  482. /**
  483. * Find the intersections between a rectangle and a rectangle.
  484. * @param point1
  485. * @param size1
  486. * @param point2
  487. * @param size2
  488. */
  489. export function intersectRectangleRectangle(
  490. point1: number[],
  491. size1: number[],
  492. point2: number[],
  493. size2: number[]
  494. ): TLIntersection[] {
  495. const sideIntersections = getRectangleSides(point1, size1).reduce<TLIntersection[]>(
  496. (acc, [message, [a1, a2]]) => {
  497. const intersections = intersectRectangleLineSegment(point2, size2, a1, a2)
  498. acc.push(
  499. ...intersections.map((int) =>
  500. createIntersection(`${message} ${int.message}`, ...int.points)
  501. )
  502. )
  503. return acc
  504. },
  505. []
  506. )
  507. return sideIntersections.filter((int) => int.didIntersect)
  508. }
  509. /**
  510. * Find the intersections between a rectangle and an arc.
  511. * @param point
  512. * @param size
  513. * @param center
  514. * @param radius
  515. * @param start
  516. * @param end
  517. */
  518. export function intersectRectangleArc(
  519. point: number[],
  520. size: number[],
  521. center: number[],
  522. radius: number,
  523. start: number[],
  524. end: number[]
  525. ): TLIntersection[] {
  526. const sideIntersections = getRectangleSides(point, size).reduce<TLIntersection[]>(
  527. (acc, [message, [a1, a2]]) => {
  528. const intersection = intersectArcLineSegment(center, radius, start, end, a1, a2)
  529. if (intersection) {
  530. acc.push({ ...intersection, message })
  531. }
  532. return acc
  533. },
  534. []
  535. )
  536. return sideIntersections.filter((int) => int.didIntersect)
  537. }
  538. /**
  539. * Find the intersections between a rectangle and a circle.
  540. * @param point
  541. * @param size
  542. * @param c
  543. * @param r
  544. */
  545. export function intersectRectangleCircle(
  546. point: number[],
  547. size: number[],
  548. c: number[],
  549. r: number
  550. ): TLIntersection[] {
  551. const sideIntersections = getRectangleSides(point, size).reduce<TLIntersection[]>(
  552. (acc, [message, [a1, a2]]) => {
  553. const intersection = intersectLineSegmentCircle(a1, a2, c, r)
  554. if (intersection) {
  555. acc.push({ ...intersection, message })
  556. }
  557. return acc
  558. },
  559. []
  560. )
  561. return sideIntersections.filter((int) => int.didIntersect)
  562. }
  563. /**
  564. * Find the intersections between a rectangle and an ellipse.
  565. * @param point
  566. * @param size
  567. * @param c
  568. * @param rx
  569. * @param ry
  570. * @param rotation
  571. */
  572. export function intersectRectangleEllipse(
  573. point: number[],
  574. size: number[],
  575. c: number[],
  576. rx: number,
  577. ry: number,
  578. rotation = 0
  579. ): TLIntersection[] {
  580. const sideIntersections = getRectangleSides(point, size).reduce<TLIntersection[]>(
  581. (acc, [message, [a1, a2]]) => {
  582. const intersection = intersectLineSegmentEllipse(a1, a2, c, rx, ry, rotation)
  583. if (intersection) {
  584. acc.push({ ...intersection, message })
  585. }
  586. return acc
  587. },
  588. []
  589. )
  590. return sideIntersections.filter((int) => int.didIntersect)
  591. }
  592. /**
  593. * Find the intersections between a rectangle and a bounding box.
  594. * @param point
  595. * @param size
  596. * @param bounds
  597. */
  598. export function intersectRectangleBounds(
  599. point: number[],
  600. size: number[],
  601. bounds: TLBounds
  602. ): TLIntersection[] {
  603. const { minX, minY, width, height } = bounds
  604. return intersectRectangleRectangle(point, size, [minX, minY], [width, height])
  605. }
  606. /**
  607. * Find the intersections between a rectangle and a polyline.
  608. * @param point
  609. * @param size
  610. * @param points
  611. */
  612. export function intersectRectanglePolyline(
  613. point: number[],
  614. size: number[],
  615. points: number[][]
  616. ): TLIntersection[] {
  617. const sideIntersections = getRectangleSides(point, size).reduce<TLIntersection[]>(
  618. (acc, [message, [a1, a2]]) => {
  619. const intersection = intersectLineSegmentPolyline(a1, a2, points)
  620. if (intersection.didIntersect) {
  621. acc.push(createIntersection(message, ...intersection.points))
  622. }
  623. return acc
  624. },
  625. []
  626. )
  627. return sideIntersections.filter((int) => int.didIntersect)
  628. }
  629. /**
  630. * Find the intersections between a rectangle and a polygon.
  631. * @param point
  632. * @param size
  633. * @param points
  634. */
  635. export function intersectRectanglePolygon(
  636. point: number[],
  637. size: number[],
  638. points: number[][]
  639. ): TLIntersection[] {
  640. const sideIntersections = getRectangleSides(point, size).reduce<TLIntersection[]>(
  641. (acc, [message, [a1, a2]]) => {
  642. const intersection = intersectLineSegmentPolygon(a1, a2, points)
  643. if (intersection.didIntersect) {
  644. acc.push(createIntersection(message, ...intersection.points))
  645. }
  646. return acc
  647. },
  648. []
  649. )
  650. return sideIntersections.filter((int) => int.didIntersect)
  651. }
  652. /* -------------------------------------------------- */
  653. /* Arc */
  654. /* -------------------------------------------------- */
  655. /**
  656. * Find the intersections between a arc and a line segment.
  657. * @param center
  658. * @param radius
  659. * @param start
  660. * @param end
  661. * @param a1
  662. * @param a2
  663. */
  664. export function intersectArcLineSegment(
  665. center: number[],
  666. radius: number,
  667. start: number[],
  668. end: number[],
  669. a1: number[],
  670. a2: number[]
  671. ): TLIntersection {
  672. return intersectLineSegmentArc(a1, a2, center, radius, start, end)
  673. }
  674. /**
  675. * Find the intersections between a arc and a rectangle.
  676. * @param center
  677. * @param radius
  678. * @param start
  679. * @param end
  680. * @param point
  681. * @param size
  682. */
  683. export function intersectArcRectangle(
  684. center: number[],
  685. radius: number,
  686. start: number[],
  687. end: number[],
  688. point: number[],
  689. size: number[]
  690. ): TLIntersection[] {
  691. return intersectRectangleArc(point, size, center, radius, start, end)
  692. }
  693. /**
  694. * Find the intersections between a arc and a bounding box.
  695. * @param center
  696. * @param radius
  697. * @param start
  698. * @param end
  699. * @param bounds
  700. */
  701. export function intersectArcBounds(
  702. center: number[],
  703. radius: number,
  704. start: number[],
  705. end: number[],
  706. bounds: TLBounds
  707. ): TLIntersection[] {
  708. const { minX, minY, width, height } = bounds
  709. return intersectArcRectangle(center, radius, start, end, [minX, minY], [width, height])
  710. }
  711. /* -------------------------------------------------- */
  712. /* Circle */
  713. /* -------------------------------------------------- */
  714. /**
  715. * Find the intersections between a circle and a line segment.
  716. * @param c
  717. * @param r
  718. * @param a1
  719. * @param a2
  720. */
  721. export function intersectCircleLineSegment(
  722. c: number[],
  723. r: number,
  724. a1: number[],
  725. a2: number[]
  726. ): TLIntersection {
  727. return intersectLineSegmentCircle(a1, a2, c, r)
  728. }
  729. /**
  730. * Find the intersections between a circle and a circle.
  731. * @param c1
  732. * @param r1
  733. * @param c2
  734. * @param r2
  735. */
  736. export function intersectCircleCircle(
  737. c1: number[],
  738. r1: number,
  739. c2: number[],
  740. r2: number
  741. ): TLIntersection {
  742. let dx = c2[0] - c1[0],
  743. dy = c2[1] - c1[1]
  744. const d = Math.sqrt(dx * dx + dy * dy),
  745. x = (d * d - r2 * r2 + r1 * r1) / (2 * d),
  746. y = Math.sqrt(r1 * r1 - x * x)
  747. dx /= d
  748. dy /= d
  749. return createIntersection(
  750. 'intersection',
  751. [c1[0] + dx * x - dy * y, c1[1] + dy * x + dx * y],
  752. [c1[0] + dx * x + dy * y, c1[1] + dy * x - dx * y]
  753. )
  754. }
  755. /**
  756. * Find the intersections between a circle and a rectangle.
  757. * @param c
  758. * @param r
  759. * @param point
  760. * @param size
  761. */
  762. export function intersectCircleRectangle(
  763. c: number[],
  764. r: number,
  765. point: number[],
  766. size: number[]
  767. ): TLIntersection[] {
  768. return intersectRectangleCircle(point, size, c, r)
  769. }
  770. /**
  771. * Find the intersections between a circle and a bounding box.
  772. * @param c
  773. * @param r
  774. * @param bounds
  775. */
  776. export function intersectCircleBounds(c: number[], r: number, bounds: TLBounds): TLIntersection[] {
  777. const { minX, minY, width, height } = bounds
  778. return intersectCircleRectangle(c, r, [minX, minY], [width, height])
  779. }
  780. /* -------------------------------------------------- */
  781. /* Ellipse */
  782. /* -------------------------------------------------- */
  783. /**
  784. * Find the intersections between an ellipse and a ray.
  785. * @param center
  786. * @param rx
  787. * @param ry
  788. * @param rotation
  789. * @param point
  790. * @param direction
  791. */
  792. export function intersectEllipseRay(
  793. center: number[],
  794. rx: number,
  795. ry: number,
  796. rotation: number,
  797. point: number[],
  798. direction: number[]
  799. ): TLIntersection {
  800. return intersectRayEllipse(point, direction, center, rx, ry, rotation)
  801. }
  802. /**
  803. * Find the intersections between an ellipse and a line segment.
  804. * @param center
  805. * @param rx
  806. * @param ry
  807. * @param rotation
  808. * @param a1
  809. * @param a2
  810. */
  811. export function intersectEllipseLineSegment(
  812. center: number[],
  813. rx: number,
  814. ry: number,
  815. rotation = 0,
  816. a1: number[],
  817. a2: number[]
  818. ): TLIntersection {
  819. if (rx === ry) {
  820. return intersectLineSegmentCircle(a1, a2, center, rx)
  821. }
  822. return intersectLineSegmentEllipse(a1, a2, center, rx, ry, rotation)
  823. }
  824. /**
  825. * Find the intersections between an ellipse and a rectangle.
  826. * @param center
  827. * @param rx
  828. * @param ry
  829. * @param rotation
  830. * @param point
  831. * @param size
  832. */
  833. export function intersectEllipseRectangle(
  834. center: number[],
  835. rx: number,
  836. ry: number,
  837. rotation = 0,
  838. point: number[],
  839. size: number[]
  840. ): TLIntersection[] {
  841. if (rx === ry) {
  842. return intersectRectangleCircle(point, size, center, rx)
  843. }
  844. return intersectRectangleEllipse(point, size, center, rx, ry, rotation)
  845. }
  846. /**
  847. * Find the intersections between an ellipse and an ellipse.
  848. * Adapted from https://gist.github.com/drawable/92792f59b6ff8869d8b1
  849. * @param _c1
  850. * @param _rx1
  851. * @param _ry1
  852. * @param _r1
  853. * @param _c2
  854. * @param _rx2
  855. * @param _ry2
  856. * @param _r2
  857. */
  858. export function intersectEllipseEllipse(
  859. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  860. _c1: number[],
  861. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  862. _rx1: number,
  863. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  864. _ry1: number,
  865. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  866. _r1: number,
  867. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  868. _c2: number[],
  869. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  870. _rx2: number,
  871. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  872. _ry2: number,
  873. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  874. _r2: number
  875. ): TLIntersection {
  876. // TODO
  877. return createIntersection('no intersection')
  878. }
  879. /**
  880. * Find the intersections between an ellipse and a circle.
  881. * @param c
  882. * @param rx
  883. * @param ry
  884. * @param rotation
  885. * @param c2
  886. * @param r2
  887. */
  888. export function intersectEllipseCircle(
  889. c: number[],
  890. rx: number,
  891. ry: number,
  892. rotation: number,
  893. c2: number[],
  894. r2: number
  895. ): TLIntersection {
  896. return intersectEllipseEllipse(c, rx, ry, rotation, c2, r2, r2, 0)
  897. }
  898. /**
  899. * Find the intersections between an ellipse and a bounding box.
  900. * @param c
  901. * @param rx
  902. * @param ry
  903. * @param rotation
  904. * @param bounds
  905. */
  906. export function intersectEllipseBounds(
  907. c: number[],
  908. rx: number,
  909. ry: number,
  910. rotation: number,
  911. bounds: TLBounds
  912. ): TLIntersection[] {
  913. const { minX, minY, width, height } = bounds
  914. return intersectEllipseRectangle(c, rx, ry, rotation, [minX, minY], [width, height])
  915. }
  916. /**
  917. * Find the intersections between a bounding box and a ray.
  918. * @param bounds
  919. * @param origin
  920. * @param direction
  921. */
  922. export function intersectBoundsRay(
  923. bounds: TLBounds,
  924. origin: number[],
  925. direction: number[]
  926. ): TLIntersection[] {
  927. const { minX, minY, width, height } = bounds
  928. return intersectRayRectangle(origin, direction, [minX, minY], [width, height])
  929. }
  930. /**
  931. * Find the intersections between a bounding box and a line segment.
  932. * @param bounds
  933. * @param a1
  934. * @param a2
  935. */
  936. export function intersectBoundsLineSegment(
  937. bounds: TLBounds,
  938. a1: number[],
  939. a2: number[]
  940. ): TLIntersection[] {
  941. const { minX, minY, width, height } = bounds
  942. return intersectLineSegmentRectangle(a1, a2, [minX, minY], [width, height])
  943. }
  944. /**
  945. * Find the intersections between a bounding box and a rectangle.
  946. * @param bounds
  947. * @param point
  948. * @param size
  949. */
  950. export function intersectBoundsRectangle(
  951. bounds: TLBounds,
  952. point: number[],
  953. size: number[]
  954. ): TLIntersection[] {
  955. const { minX, minY, width, height } = bounds
  956. return intersectRectangleRectangle(point, size, [minX, minY], [width, height])
  957. }
  958. /**
  959. * Find the intersections between a bounding box and a bounding box.
  960. * @param bounds1
  961. * @param bounds2
  962. */
  963. export function intersectBoundsBounds(bounds1: TLBounds, bounds2: TLBounds): TLIntersection[] {
  964. return intersectRectangleRectangle(
  965. [bounds1.minX, bounds1.minY],
  966. [bounds1.width, bounds1.height],
  967. [bounds2.minX, bounds2.minY],
  968. [bounds2.width, bounds2.height]
  969. )
  970. }
  971. /**
  972. * Find the intersections between a bounding box and an arc.
  973. * @param bounds
  974. * @param center
  975. * @param radius
  976. * @param start
  977. * @param end
  978. */
  979. export function intersectBoundsArc(
  980. bounds: TLBounds,
  981. center: number[],
  982. radius: number,
  983. start: number[],
  984. end: number[]
  985. ): TLIntersection[] {
  986. const { minX, minY, width, height } = bounds
  987. return intersectArcRectangle(center, radius, start, end, [minX, minY], [width, height])
  988. }
  989. /**
  990. * Find the intersections between a bounding box and a circle.
  991. * @param bounds
  992. * @param c
  993. * @param r
  994. */
  995. export function intersectBoundsCircle(bounds: TLBounds, c: number[], r: number): TLIntersection[] {
  996. const { minX, minY, width, height } = bounds
  997. return intersectCircleRectangle(c, r, [minX, minY], [width, height])
  998. }
  999. /**
  1000. * Find the intersections between a bounding box and an ellipse.
  1001. * @param bounds
  1002. * @param c
  1003. * @param rx
  1004. * @param ry
  1005. * @param rotation
  1006. */
  1007. export function intersectBoundsEllipse(
  1008. bounds: TLBounds,
  1009. c: number[],
  1010. rx: number,
  1011. ry: number,
  1012. rotation = 0
  1013. ): TLIntersection[] {
  1014. const { minX, minY, width, height } = bounds
  1015. return intersectEllipseRectangle(c, rx, ry, rotation, [minX, minY], [width, height])
  1016. }
  1017. /**
  1018. * Find the intersections between a bounding box and a polyline.
  1019. * @param bounds
  1020. * @param points
  1021. */
  1022. export function intersectBoundsPolyline(bounds: TLBounds, points: number[][]): TLIntersection[] {
  1023. return intersectPolylineBounds(points, bounds)
  1024. }
  1025. /**
  1026. * Find the intersections between a bounding box and a polygon.
  1027. * @param bounds
  1028. * @param points
  1029. */
  1030. export function intersectBoundsPolygon(bounds: TLBounds, points: number[][]): TLIntersection[] {
  1031. return intersectPolygonBounds(points, bounds)
  1032. }
  1033. /* -------------------------------------------------- */
  1034. /* Polyline */
  1035. /* -------------------------------------------------- */
  1036. /**
  1037. * Find the intersections between a polyline and a line segment.
  1038. * @param points
  1039. * @param a1
  1040. * @param a2
  1041. */
  1042. export function intersectPolylineLineSegment(
  1043. points: number[][],
  1044. a1: number[],
  1045. a2: number[]
  1046. ): TLIntersection {
  1047. return intersectLineSegmentPolyline(a1, a2, points)
  1048. }
  1049. /**
  1050. * Find the intersections between a polyline and a rectangle.
  1051. * @param points
  1052. * @param point
  1053. * @param size
  1054. */
  1055. export function intersectPolylineRectangle(
  1056. points: number[][],
  1057. point: number[],
  1058. size: number[]
  1059. ): TLIntersection[] {
  1060. return intersectRectanglePolyline(point, size, points)
  1061. }
  1062. /**
  1063. * Find the intersections between a polyline and a bounding box.
  1064. * @param points
  1065. * @param bounds
  1066. */
  1067. export function intersectPolylineBounds(points: number[][], bounds: TLBounds): TLIntersection[] {
  1068. return intersectRectanglePolyline(
  1069. [bounds.minX, bounds.minY],
  1070. [bounds.width, bounds.height],
  1071. points
  1072. )
  1073. }
  1074. /* -------------------------------------------------- */
  1075. /* Polygon */
  1076. /* -------------------------------------------------- */
  1077. /**
  1078. * Find the intersections between a polygon nd a line segment.
  1079. * @param points
  1080. * @param a1
  1081. * @param a2
  1082. */
  1083. export function intersectPolygonLineSegment(
  1084. points: number[][],
  1085. a1: number[],
  1086. a2: number[]
  1087. ): TLIntersection {
  1088. return intersectLineSegmentPolyline(a1, a2, points)
  1089. }
  1090. /**
  1091. * Find the intersections between a polygon and a rectangle.
  1092. * @param points
  1093. * @param point
  1094. * @param size
  1095. */
  1096. export function intersectPolygonRectangle(
  1097. points: number[][],
  1098. point: number[],
  1099. size: number[]
  1100. ): TLIntersection[] {
  1101. return intersectRectanglePolyline(point, size, points)
  1102. }
  1103. /**
  1104. * Find the intersections between a polygon and a bounding box.
  1105. * @param points
  1106. * @param bounds
  1107. */
  1108. export function intersectPolygonBounds(points: number[][], bounds: TLBounds): TLIntersection[] {
  1109. return intersectRectanglePolygon(
  1110. [bounds.minX, bounds.minY],
  1111. [bounds.width, bounds.height],
  1112. points
  1113. )
  1114. }