segment.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. import {
  2. isPoint,
  3. pointCenter,
  4. pointFromVector,
  5. pointRotateRads,
  6. } from "./point";
  7. import type { GlobalPoint, LineSegment, LocalPoint, Radians } from "./types";
  8. import { PRECISION } from "./utils";
  9. import {
  10. vectorAdd,
  11. vectorCross,
  12. vectorFromPoint,
  13. vectorScale,
  14. vectorSubtract,
  15. } from "./vector";
  16. /**
  17. * Create a line segment from two points.
  18. *
  19. * @param points The two points delimiting the line segment on each end
  20. * @returns The line segment delineated by the points
  21. */
  22. export function lineSegment<P extends GlobalPoint | LocalPoint>(
  23. a: P,
  24. b: P,
  25. ): LineSegment<P> {
  26. return [a, b] as LineSegment<P>;
  27. }
  28. export function lineSegmentFromPointArray<P extends GlobalPoint | LocalPoint>(
  29. pointArray: P[],
  30. ): LineSegment<P> | undefined {
  31. return pointArray.length === 2
  32. ? lineSegment<P>(pointArray[0], pointArray[1])
  33. : undefined;
  34. }
  35. /**
  36. *
  37. * @param segment
  38. * @returns
  39. */
  40. export const isLineSegment = <Point extends GlobalPoint | LocalPoint>(
  41. segment: unknown,
  42. ): segment is LineSegment<Point> =>
  43. Array.isArray(segment) &&
  44. segment.length === 2 &&
  45. isPoint(segment[0]) &&
  46. isPoint(segment[0]);
  47. /**
  48. * Return the coordinates resulting from rotating the given line about an origin by an angle in radians
  49. * note that when the origin is not given, the midpoint of the given line is used as the origin.
  50. *
  51. * @param l
  52. * @param angle
  53. * @param origin
  54. * @returns
  55. */
  56. export const lineSegmentRotate = <Point extends LocalPoint | GlobalPoint>(
  57. l: LineSegment<Point>,
  58. angle: Radians,
  59. origin?: Point,
  60. ): LineSegment<Point> => {
  61. return lineSegment(
  62. pointRotateRads(l[0], origin || pointCenter(l[0], l[1]), angle),
  63. pointRotateRads(l[1], origin || pointCenter(l[0], l[1]), angle),
  64. );
  65. };
  66. /**
  67. * Calculates the point two line segments with a definite start and end point
  68. * intersect at.
  69. */
  70. export const segmentsIntersectAt = <Point extends GlobalPoint | LocalPoint>(
  71. a: Readonly<LineSegment<Point>>,
  72. b: Readonly<LineSegment<Point>>,
  73. ): Point | null => {
  74. const a0 = vectorFromPoint(a[0]);
  75. const a1 = vectorFromPoint(a[1]);
  76. const b0 = vectorFromPoint(b[0]);
  77. const b1 = vectorFromPoint(b[1]);
  78. const r = vectorSubtract(a1, a0);
  79. const s = vectorSubtract(b1, b0);
  80. const denominator = vectorCross(r, s);
  81. if (denominator === 0) {
  82. return null;
  83. }
  84. const i = vectorSubtract(vectorFromPoint(b[0]), vectorFromPoint(a[0]));
  85. const u = vectorCross(i, r) / denominator;
  86. const t = vectorCross(i, s) / denominator;
  87. if (u === 0) {
  88. return null;
  89. }
  90. const p = vectorAdd(a0, vectorScale(r, t));
  91. if (t >= 0 && t < 1 && u >= 0 && u < 1) {
  92. return pointFromVector<Point>(p);
  93. }
  94. return null;
  95. };
  96. export const pointOnLineSegment = <Point extends LocalPoint | GlobalPoint>(
  97. point: Point,
  98. line: LineSegment<Point>,
  99. threshold = PRECISION,
  100. ) => {
  101. const distance = distanceToLineSegment(point, line);
  102. if (distance === 0) {
  103. return true;
  104. }
  105. return distance < threshold;
  106. };
  107. export const distanceToLineSegment = <Point extends LocalPoint | GlobalPoint>(
  108. point: Point,
  109. line: LineSegment<Point>,
  110. ) => {
  111. const [x, y] = point;
  112. const [[x1, y1], [x2, y2]] = line;
  113. const A = x - x1;
  114. const B = y - y1;
  115. const C = x2 - x1;
  116. const D = y2 - y1;
  117. const dot = A * C + B * D;
  118. const len_sq = C * C + D * D;
  119. let param = -1;
  120. if (len_sq !== 0) {
  121. param = dot / len_sq;
  122. }
  123. let xx;
  124. let yy;
  125. if (param < 0) {
  126. xx = x1;
  127. yy = y1;
  128. } else if (param > 1) {
  129. xx = x2;
  130. yy = y2;
  131. } else {
  132. xx = x1 + param * C;
  133. yy = y1 + param * D;
  134. }
  135. const dx = x - xx;
  136. const dy = y - yy;
  137. return Math.sqrt(dx * dx + dy * dy);
  138. };