import { EPSILON } from "../excalidraw/constants"; import { pointCenter, pointDistanceSq, pointFrom, pointRotateRads, pointsEqual, } from "./point"; import { pointOnLineSegment } from "./segment"; import type { GlobalPoint, Line, LineSegment, LocalPoint, Radians, } from "./types"; import { vectorCross, vectorFromPoint } from "./vector"; /** * Create a line from two points. * * @param points The two points lying on the line * @returns The line on which the points lie */ export function line

(a: P, b: P): Line

{ return [a, b] as Line

; } /** * Convenient point creation from an array of two points. * * @param param0 The array with the two points to convert to a line * @returns The created line */ export function lineFromPointPair

([a, b]: [ P, P, ]): Line

{ return line(a, b); } /** * TODO * * @param pointArray * @returns */ export function lineFromPointArray

( pointArray: P[], ): Line

| undefined { return pointArray.length === 2 ? line

(pointArray[0], pointArray[1]) : undefined; } /** * 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 * * @param l * @param angle * @param origin * @returns */ export function lineRotate( l: Line, angle: Radians, origin?: Point, ): Line { return line( pointRotateRads(l[0], origin || pointCenter(l[0], l[1]), angle), pointRotateRads(l[1], origin || pointCenter(l[0], l[1]), angle), ); } /** * Determines the intersection point (unless the lines are parallel) of two * lines * * @param a * @param b * @returns */ export function linesIntersectAt( a: Line, b: Line, ): Point | null { const A1 = a[1][1] - a[0][1]; const B1 = a[0][0] - a[1][0]; const A2 = b[1][1] - b[0][1]; const B2 = b[0][0] - b[1][0]; const D = A1 * B2 - A2 * B1; if (D !== 0) { const C1 = A1 * a[0][0] + B1 * a[0][1]; const C2 = A2 * b[0][0] + B2 * b[0][1]; return pointFrom((C1 * B2 - C2 * B1) / D, (A1 * C2 - A2 * C1) / D); } return null; } /** * 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; } export function isPointOnLine

( l: Line

, p: P, epsilon: number = EPSILON, ) { const p1 = vectorFromPoint(l[1], l[0]); const p2 = vectorFromPoint(p, l[0]); const r = vectorCross(p1, p2); return Math.abs(r) < epsilon; } export function lineClosestPoint

( l: Line

, p: P, ): P { if (pointsEqual(l[0], l[1])) { return l[0]; } const t = ((p[0] - l[0][0]) * (l[1][0] - l[0][0]) + (p[1] - l[0][1]) * (l[1][1] - l[0][1])) / pointDistanceSq(l[0], l[1]); return pointFrom( l[0][0] + t * (l[1][0] - l[0][0]), l[0][1] + t * (l[1][1] - l[0][1]), ); }