collision.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. import type { Polycurve, Polyline } from "./geometry/shape";
  2. import {
  3. pointInEllipse,
  4. pointOnEllipse,
  5. type GeometricShape,
  6. } from "./geometry/shape";
  7. import type { Curve } from "../math";
  8. import {
  9. lineSegment,
  10. pointFrom,
  11. polygonIncludesPoint,
  12. pointOnLineSegment,
  13. pointOnPolygon,
  14. polygonFromPoints,
  15. type GlobalPoint,
  16. type LocalPoint,
  17. type Polygon,
  18. } from "../math";
  19. // check if the given point is considered on the given shape's border
  20. export const isPointOnShape = <Point extends GlobalPoint | LocalPoint>(
  21. point: Point,
  22. shape: GeometricShape<Point>,
  23. tolerance = 0,
  24. ) => {
  25. // get the distance from the given point to the given element
  26. // check if the distance is within the given epsilon range
  27. switch (shape.type) {
  28. case "polygon":
  29. return pointOnPolygon(point, shape.data, tolerance);
  30. case "ellipse":
  31. return pointOnEllipse(point, shape.data, tolerance);
  32. case "line":
  33. return pointOnLineSegment(point, shape.data, tolerance);
  34. case "polyline":
  35. return pointOnPolyline(point, shape.data, tolerance);
  36. case "curve":
  37. return pointOnCurve(point, shape.data, tolerance);
  38. case "polycurve":
  39. return pointOnPolycurve(point, shape.data, tolerance);
  40. default:
  41. throw Error(`shape ${shape} is not implemented`);
  42. }
  43. };
  44. // check if the given point is considered inside the element's border
  45. export const isPointInShape = <Point extends GlobalPoint | LocalPoint>(
  46. point: Point,
  47. shape: GeometricShape<Point>,
  48. ) => {
  49. switch (shape.type) {
  50. case "polygon":
  51. return polygonIncludesPoint(point, shape.data);
  52. case "line":
  53. return false;
  54. case "curve":
  55. return false;
  56. case "ellipse":
  57. return pointInEllipse(point, shape.data);
  58. case "polyline": {
  59. const polygon = polygonFromPoints(shape.data.flat());
  60. return polygonIncludesPoint(point, polygon);
  61. }
  62. case "polycurve": {
  63. return false;
  64. }
  65. default:
  66. throw Error(`shape ${shape} is not implemented`);
  67. }
  68. };
  69. // check if the given element is in the given bounds
  70. export const isPointInBounds = <Point extends GlobalPoint | LocalPoint>(
  71. point: Point,
  72. bounds: Polygon<Point>,
  73. ) => {
  74. return polygonIncludesPoint(point, bounds);
  75. };
  76. const pointOnPolycurve = <Point extends LocalPoint | GlobalPoint>(
  77. point: Point,
  78. polycurve: Polycurve<Point>,
  79. tolerance: number,
  80. ) => {
  81. return polycurve.some((curve) => pointOnCurve(point, curve, tolerance));
  82. };
  83. const cubicBezierEquation = <Point extends LocalPoint | GlobalPoint>(
  84. curve: Curve<Point>,
  85. ) => {
  86. const [p0, p1, p2, p3] = curve;
  87. // B(t) = p0 * (1-t)^3 + 3p1 * t * (1-t)^2 + 3p2 * t^2 * (1-t) + p3 * t^3
  88. return (t: number, idx: number) =>
  89. Math.pow(1 - t, 3) * p3[idx] +
  90. 3 * t * Math.pow(1 - t, 2) * p2[idx] +
  91. 3 * Math.pow(t, 2) * (1 - t) * p1[idx] +
  92. p0[idx] * Math.pow(t, 3);
  93. };
  94. const polyLineFromCurve = <Point extends LocalPoint | GlobalPoint>(
  95. curve: Curve<Point>,
  96. segments = 10,
  97. ): Polyline<Point> => {
  98. const equation = cubicBezierEquation(curve);
  99. let startingPoint = [equation(0, 0), equation(0, 1)] as Point;
  100. const lineSegments: Polyline<Point> = [];
  101. let t = 0;
  102. const increment = 1 / segments;
  103. for (let i = 0; i < segments; i++) {
  104. t += increment;
  105. if (t <= 1) {
  106. const nextPoint: Point = pointFrom(equation(t, 0), equation(t, 1));
  107. lineSegments.push(lineSegment(startingPoint, nextPoint));
  108. startingPoint = nextPoint;
  109. }
  110. }
  111. return lineSegments;
  112. };
  113. export const pointOnCurve = <Point extends LocalPoint | GlobalPoint>(
  114. point: Point,
  115. curve: Curve<Point>,
  116. threshold: number,
  117. ) => {
  118. return pointOnPolyline(point, polyLineFromCurve(curve), threshold);
  119. };
  120. export const pointOnPolyline = <Point extends LocalPoint | GlobalPoint>(
  121. point: Point,
  122. polyline: Polyline<Point>,
  123. threshold = 10e-5,
  124. ) => {
  125. return polyline.some((line) => pointOnLineSegment(point, line, threshold));
  126. };