From 6ec259a0e71174651bae95d4628138bf6fd68742 Mon Sep 17 00:00:00 2001
From: kj_sh604
Date: Sun, 15 Mar 2026 16:19:35 -0400
Subject: refactor: packages/
---
packages/math/segment.ts | 174 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 174 insertions(+)
create mode 100644 packages/math/segment.ts
(limited to 'packages/math/segment.ts')
diff --git a/packages/math/segment.ts b/packages/math/segment.ts
new file mode 100644
index 0000000..60943aa
--- /dev/null
+++ b/packages/math/segment.ts
@@ -0,0 +1,174 @@
+import { line, linesIntersectAt } from "./line";
+import {
+ isPoint,
+ pointCenter,
+ pointFromVector,
+ pointRotateRads,
+} from "./point";
+import type { GlobalPoint, LineSegment, LocalPoint, Radians } from "./types";
+import { PRECISION } from "./utils";
+import {
+ vectorAdd,
+ vectorCross,
+ vectorFromPoint,
+ vectorScale,
+ vectorSubtract,
+} from "./vector";
+
+/**
+ * Create a line segment from two points.
+ *
+ * @param points The two points delimiting the line segment on each end
+ * @returns The line segment delineated by the points
+ */
+export function lineSegment
(
+ a: P,
+ b: P,
+): LineSegment
{
+ return [a, b] as LineSegment
;
+}
+
+/**
+ *
+ * @param segment
+ * @returns
+ */
+export const isLineSegment = (
+ segment: unknown,
+): segment is LineSegment =>
+ Array.isArray(segment) &&
+ segment.length === 2 &&
+ isPoint(segment[0]) &&
+ isPoint(segment[0]);
+
+/**
+ * Return the coordinates resulting from rotating the given line about an origin by an angle in radians
+ * note that when the origin is not given, the midpoint of the given line is used as the origin.
+ *
+ * @param l
+ * @param angle
+ * @param origin
+ * @returns
+ */
+export const lineSegmentRotate = (
+ l: LineSegment,
+ angle: Radians,
+ origin?: Point,
+): LineSegment => {
+ return lineSegment(
+ 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 = (
+ a: Readonly>,
+ b: Readonly>,
+): Point | null => {
+ const a0 = vectorFromPoint(a[0]);
+ const a1 = vectorFromPoint(a[1]);
+ const b0 = vectorFromPoint(b[0]);
+ const b1 = vectorFromPoint(b[1]);
+ const r = vectorSubtract(a1, a0);
+ const s = vectorSubtract(b1, b0);
+ const denominator = vectorCross(r, s);
+
+ if (denominator === 0) {
+ return null;
+ }
+
+ const i = vectorSubtract(vectorFromPoint(b[0]), vectorFromPoint(a[0]));
+ const u = vectorCross(i, r) / denominator;
+ const t = vectorCross(i, s) / denominator;
+
+ if (u === 0) {
+ return null;
+ }
+
+ const p = vectorAdd(a0, vectorScale(r, t));
+
+ if (t >= 0 && t < 1 && u >= 0 && u < 1) {
+ return pointFromVector(p);
+ }
+
+ return null;
+};
+
+export const pointOnLineSegment = (
+ point: Point,
+ line: LineSegment,
+ threshold = PRECISION,
+) => {
+ const distance = distanceToLineSegment(point, line);
+
+ if (distance === 0) {
+ return true;
+ }
+
+ return distance < threshold;
+};
+
+export const distanceToLineSegment = (
+ point: Point,
+ line: LineSegment,
+) => {
+ const [x, y] = point;
+ const [[x1, y1], [x2, y2]] = line;
+
+ const A = x - x1;
+ const B = y - y1;
+ const C = x2 - x1;
+ const D = y2 - y1;
+
+ const dot = A * C + B * D;
+ const len_sq = C * C + D * D;
+ let param = -1;
+ if (len_sq !== 0) {
+ param = dot / len_sq;
+ }
+
+ let xx;
+ let yy;
+
+ if (param < 0) {
+ xx = x1;
+ yy = y1;
+ } else if (param > 1) {
+ xx = x2;
+ yy = y2;
+ } else {
+ xx = x1 + param * C;
+ yy = y1 + param * D;
+ }
+
+ const dx = x - xx;
+ const dy = y - yy;
+ return Math.sqrt(dx * dx + dy * dy);
+};
+
+/**
+ * Returns the intersection point of a segment and a line
+ *
+ * @param l
+ * @param s
+ * @returns
+ */
+export function lineSegmentIntersectionPoints<
+ Point extends GlobalPoint | LocalPoint,
+>(l: LineSegment, s: LineSegment): Point | null {
+ const candidate = linesIntersectAt(line(l[0], l[1]), line(s[0], s[1]));
+
+ if (
+ !candidate ||
+ !pointOnLineSegment(candidate, s) ||
+ !pointOnLineSegment(candidate, l)
+ ) {
+ return null;
+ }
+
+ return candidate;
+}
--
cgit v1.2.3