diff --git a/packages/math/line.test.ts b/packages/math/line.test.ts
new file mode 100644
index 0000000000..f72617d4bb
--- /dev/null
+++ b/packages/math/line.test.ts
@@ -0,0 +1,51 @@
+import { line, lineIntersectsLine, lineIntersectsSegment } from "./line";
+import { point } from "./point";
+import { segment } from "./segment";
+
+describe("line-line intersections", () => {
+ it("should correctly detect intersection at origin", () => {
+ expect(
+ lineIntersectsLine(
+ line(point(-5, -5), point(5, 5)),
+ line(point(5, -5), point(-5, 5)),
+ ),
+ ).toEqual(point(0, 0));
+ });
+
+ it("should correctly detect intersection at non-origin", () => {
+ expect(
+ lineIntersectsLine(
+ line(point(0, 0), point(10, 10)),
+ line(point(10, 0), point(0, 10)),
+ ),
+ ).toEqual(point(5, 5));
+ });
+
+ it("should correctly detect parallel lines", () => {
+ expect(
+ lineIntersectsLine(
+ line(point(0, 0), point(0, 10)),
+ line(point(10, 0), point(10, 10)),
+ ),
+ ).toBe(null);
+ });
+});
+
+describe("line-segment intersections", () => {
+ it("should correctly detect intersection", () => {
+ expect(
+ lineIntersectsSegment(
+ line(point(0, 0), point(5, 0)),
+ segment(point(2, -2), point(3, 2)),
+ ),
+ ).toEqual(point(2.5, -0));
+ });
+ it("should correctly detect non-intersection", () => {
+ expect(
+ lineIntersectsSegment(
+ line(point(0, 0), point(5, 0)),
+ segment(point(3, 1), point(4, 4)),
+ ),
+ ).toEqual(null);
+ });
+});
diff --git a/packages/math/line.ts b/packages/math/line.ts
index 6ff8a66eeb..16a0ec02f2 100644
--- a/packages/math/line.ts
+++ b/packages/math/line.ts
@@ -1,5 +1,6 @@
-import { pointCenter, pointRotateRads } from "./point";
-import type { GenericPoint, Line, Radians } from "./types";
+import { point, pointCenter, pointRotateRads } from "./point";
+import { segmentIncludesPoint } from "./segment";
+import type { GenericPoint, Line, Radians, Segment } from "./types";
/**
* Create a line from two points.
@@ -40,13 +41,55 @@ export function lineFromPointArray
(
// return the coordinates resulting from rotating the given line about an origin by an angle in degrees
// note that when the origin is not given, the midpoint of the given line is used as the origin
-export const lineRotate = (
+export function lineRotate(
l: Line,
angle: Radians,
origin?: Point,
-): Line => {
+): Line {
return line(
pointRotateRads(l[0], origin || pointCenter(l[0], l[1]), angle),
pointRotateRads(l[1], origin || pointCenter(l[0], l[1]), angle),
);
-};
+}
+
+/**
+ * Returns the intersection point of two infinite lines, if any
+ *
+ * @param a One of the line to intersect
+ * @param b Another line to intersect
+ * @returns The intersection point
+ */
+export function lineIntersectsLine(
+ [[x1, y1], [x2, y2]]: Line,
+ [[x3, y3], [x4, y4]]: Line,
+): Point | null {
+ const a = x1 * y2 - x2 * y1;
+ const c = x3 * y4 - x4 * y3;
+ const den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
+ if (den === 0) {
+ return null;
+ }
+ const xnum = a * (x3 - x4) - (x1 - x2) * c;
+ const ynum = a * (y3 - y4) - (y1 - y2) * c;
+
+ return point(xnum / den, ynum / den);
+}
+
+/**
+ * Returns the intersection point of a segment and a line
+ *
+ * @param l
+ * @param s
+ * @returns
+ */
+export function lineIntersectsSegment(
+ l: Line,
+ s: Segment,
+): Point | null {
+ const candidate = lineIntersectsLine(l, line(s[0], s[1]));
+ if (!candidate || !segmentIncludesPoint(candidate, s)) {
+ return null;
+ }
+
+ return candidate;
+}
diff --git a/packages/math/segment.ts b/packages/math/segment.ts
index baec9065ab..775402ab4f 100644
--- a/packages/math/segment.ts
+++ b/packages/math/segment.ts
@@ -1,3 +1,4 @@
+import { lineIntersectsSegment } from "./line";
import {
isPoint,
pointCenter,
@@ -5,7 +6,7 @@ import {
pointRotateRads,
pointsEqual,
} from "./point";
-import type { GenericPoint, Segment, Radians } from "./types";
+import type { GenericPoint, Segment, Radians, Line } from "./types";
import { PRECISION } from "./utils";
import {
vectorAdd,
@@ -38,17 +39,21 @@ export function segmentFromPointArray(
}
/**
+ * Determines if the provided value is a segment
*
- * @param segment
- * @returns
+ * @param value The candidate
+ * @returns Returns TRUE if the provided value is a segment
*/
-export const isSegment = (
- segment: unknown,
-): segment is Segment =>
- Array.isArray(segment) &&
- segment.length === 2 &&
- isPoint(segment[0]) &&
- isPoint(segment[0]);
+export function isSegment(
+ value: unknown,
+): value is Segment {
+ return (
+ Array.isArray(value) &&
+ segment.length === 2 &&
+ isPoint(value[0]) &&
+ isPoint(value[0])
+ );
+}
/**
* Return the coordinates resulting from rotating the given line about an origin by an angle in radians
@@ -59,25 +64,25 @@ export const isSegment = (
* @param origin
* @returns
*/
-export const segmentRotate = (
+export function segmentRotate(
l: Segment,
angle: Radians,
origin?: Point,
-): Segment => {
+): Segment {
return segment(
pointRotateRads(l[0], origin || pointCenter(l[0], l[1]), angle),
pointRotateRads(l[1], origin || pointCenter(l[0], l[1]), angle),
);
-};
+}
/**
* Calculates the point two line segments with a definite start and end point
* intersect at.
*/
-export const segmentsIntersectAt = (
+export function segmentsIntersectAt(
a: Readonly>,
b: Readonly>,
-): Point | null => {
+): Point | null {
const a0 = vectorFromPoint(a[0]);
const a1 = vectorFromPoint(a[1]);
const b0 = vectorFromPoint(b[0]);
@@ -105,26 +110,41 @@ export const segmentsIntersectAt = (
}
return null;
-};
+}
-export const segmentIncludesPoint = (
+/**
+ * Determnines if a point lies on a segment
+ *
+ * @param point
+ * @param s
+ * @param threshold
+ * @returns
+ */
+export function segmentIncludesPoint(
point: Point,
- line: Segment,
+ s: Segment,
threshold = PRECISION,
-) => {
- const distance = segmentDistanceToPoint(point, line);
+) {
+ const distance = segmentDistanceToPoint(point, s);
if (distance === 0) {
return true;
}
return distance < threshold;
-};
+}
-export const segmentDistanceToPoint = (
+/**
+ * Returns the shortest distance from a point to a segment.
+ *
+ * @param p
+ * @param s
+ * @returns
+ */
+export function segmentDistanceToPoint(
p: Point,
s: Segment,
-) => {
+): number {
const [x, y] = p;
const [[x1, y1], [x2, y2]] = s;
@@ -157,4 +177,18 @@ export const segmentDistanceToPoint = (
const dx = x - xx;
const dy = y - yy;
return Math.sqrt(dx * dx + dy * dy);
-};
+}
+
+/**
+ * Returns the intersection point between a segment and a line, if any
+ *
+ * @param s
+ * @param l
+ * @returns
+ */
+export function segmentIntersectsLine(
+ s: Segment,
+ l: Line,
+): Point | null {
+ return lineIntersectsSegment(l, s);
+}