aboutsummaryrefslogtreecommitdiffstats
path: root/packages/math/segment.ts
diff options
context:
space:
mode:
authorkj_sh6042026-03-15 16:19:35 -0400
committerkj_sh6042026-03-15 16:19:35 -0400
commit6ec259a0e71174651bae95d4628138bf6fd68742 (patch)
tree5e33c6a5ec091ecabfcb257fdc7b6a88ed8754ac /packages/math/segment.ts
parent16c8578b15c727f22921f8a80a56ee4d4e7f2272 (diff)
refactor: packages/
Diffstat (limited to 'packages/math/segment.ts')
-rw-r--r--packages/math/segment.ts174
1 files changed, 174 insertions, 0 deletions
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<P extends GlobalPoint | LocalPoint>(
+ a: P,
+ b: P,
+): LineSegment<P> {
+ return [a, b] as LineSegment<P>;
+}
+
+/**
+ *
+ * @param segment
+ * @returns
+ */
+export const isLineSegment = <Point extends GlobalPoint | LocalPoint>(
+ segment: unknown,
+): segment is LineSegment<Point> =>
+ 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 = <Point extends LocalPoint | GlobalPoint>(
+ l: LineSegment<Point>,
+ angle: Radians,
+ origin?: Point,
+): LineSegment<Point> => {
+ 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 = <Point extends GlobalPoint | LocalPoint>(
+ a: Readonly<LineSegment<Point>>,
+ b: Readonly<LineSegment<Point>>,
+): 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<Point>(p);
+ }
+
+ return null;
+};
+
+export const pointOnLineSegment = <Point extends LocalPoint | GlobalPoint>(
+ point: Point,
+ line: LineSegment<Point>,
+ threshold = PRECISION,
+) => {
+ const distance = distanceToLineSegment(point, line);
+
+ if (distance === 0) {
+ return true;
+ }
+
+ return distance < threshold;
+};
+
+export const distanceToLineSegment = <Point extends LocalPoint | GlobalPoint>(
+ point: Point,
+ line: LineSegment<Point>,
+) => {
+ 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<Point>, s: LineSegment<Point>): 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;
+}