math.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import { Point } from "./types";
  2. // https://stackoverflow.com/a/6853926/232122
  3. export function distanceBetweenPointAndSegment(
  4. x: number,
  5. y: number,
  6. x1: number,
  7. y1: number,
  8. x2: number,
  9. y2: number,
  10. ) {
  11. const A = x - x1;
  12. const B = y - y1;
  13. const C = x2 - x1;
  14. const D = y2 - y1;
  15. const dot = A * C + B * D;
  16. const lenSquare = C * C + D * D;
  17. let param = -1;
  18. if (lenSquare !== 0) {
  19. // in case of 0 length line
  20. param = dot / lenSquare;
  21. }
  22. let xx, yy;
  23. if (param < 0) {
  24. xx = x1;
  25. yy = y1;
  26. } else if (param > 1) {
  27. xx = x2;
  28. yy = y2;
  29. } else {
  30. xx = x1 + param * C;
  31. yy = y1 + param * D;
  32. }
  33. const dx = x - xx;
  34. const dy = y - yy;
  35. return Math.hypot(dx, dy);
  36. }
  37. export function rotate(
  38. x1: number,
  39. y1: number,
  40. x2: number,
  41. y2: number,
  42. angle: number,
  43. ) {
  44. // 𝑎′𝑥=(𝑎𝑥−𝑐𝑥)cos𝜃−(𝑎𝑦−𝑐𝑦)sin𝜃+𝑐𝑥
  45. // 𝑎′𝑦=(𝑎𝑥−𝑐𝑥)sin𝜃+(𝑎𝑦−𝑐𝑦)cos𝜃+𝑐𝑦.
  46. // https://math.stackexchange.com/questions/2204520/how-do-i-rotate-a-line-segment-in-a-specific-point-on-the-line
  47. return [
  48. (x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle) + x2,
  49. (x1 - x2) * Math.sin(angle) + (y1 - y2) * Math.cos(angle) + y2,
  50. ];
  51. }
  52. export function adjustXYWithRotation(
  53. side: "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se",
  54. position: { x: number; y: number },
  55. deltaX: number,
  56. deltaY: number,
  57. angle: number,
  58. ) {
  59. let { x, y } = position;
  60. if (side === "e" || side === "ne" || side === "se") {
  61. x -= (deltaX / 2) * (1 - Math.cos(angle));
  62. y -= (deltaX / 2) * -Math.sin(angle);
  63. }
  64. if (side === "s" || side === "sw" || side === "se") {
  65. x -= (deltaY / 2) * Math.sin(angle);
  66. y -= (deltaY / 2) * (1 - Math.cos(angle));
  67. }
  68. if (side === "w" || side === "nw" || side === "sw") {
  69. x += (deltaX / 2) * (1 + Math.cos(angle));
  70. y += (deltaX / 2) * Math.sin(angle);
  71. }
  72. if (side === "n" || side === "nw" || side === "ne") {
  73. x += (deltaY / 2) * -Math.sin(angle);
  74. y += (deltaY / 2) * (1 + Math.cos(angle));
  75. }
  76. return { x, y };
  77. }
  78. export const getPointOnAPath = (point: Point, path: Point[]) => {
  79. const [px, py] = point;
  80. const [start, ...other] = path;
  81. let [lastX, lastY] = start;
  82. let kLine: number = 0;
  83. let idx: number = 0;
  84. // if any item in the array is true, it means that a point is
  85. // on some segment of a line based path
  86. const retVal = other.some(([x2, y2], i) => {
  87. // we always take a line when dealing with line segments
  88. const x1 = lastX;
  89. const y1 = lastY;
  90. lastX = x2;
  91. lastY = y2;
  92. // if a point is not within the domain of the line segment
  93. // it is not on the line segment
  94. if (px < x1 || px > x2) {
  95. return false;
  96. }
  97. // check if all points lie on the same line
  98. // y1 = kx1 + b, y2 = kx2 + b
  99. // y2 - y1 = k(x2 - x2) -> k = (y2 - y1) / (x2 - x1)
  100. // coefficient for the line (p0, p1)
  101. const kL = (y2 - y1) / (x2 - x1);
  102. // coefficient for the line segment (p0, point)
  103. const kP1 = (py - y1) / (px - x1);
  104. // coefficient for the line segment (point, p1)
  105. const kP2 = (py - y2) / (px - x2);
  106. // because we are basing both lines from the same starting point
  107. // the only option for collinearity is having same coefficients
  108. // using it for floating point comparisons
  109. const epsilon = 0.3;
  110. // if coefficient is more than an arbitrary epsilon,
  111. // these lines are nor collinear
  112. if (Math.abs(kP1 - kL) > epsilon && Math.abs(kP2 - kL) > epsilon) {
  113. return false;
  114. }
  115. // store the coefficient because we are goint to need it
  116. kLine = kL;
  117. idx = i;
  118. return true;
  119. });
  120. // Return a coordinate that is always on the line segment
  121. if (retVal === true) {
  122. return { x: point[0], y: kLine * point[0], segment: idx };
  123. }
  124. return null;
  125. };