Spaces:
Sleeping
Sleeping
| import { line, linesIntersectAt } from "./line"; | |
| import { | |
| isPoint, | |
| pointCenter, | |
| pointFromVector, | |
| pointRotateRads, | |
| } from "./point"; | |
| import { PRECISION } from "./utils"; | |
| import { | |
| vectorAdd, | |
| vectorCross, | |
| vectorFromPoint, | |
| vectorScale, | |
| vectorSubtract, | |
| } from "./vector"; | |
| import type { GlobalPoint, LineSegment, LocalPoint, Radians } from "./types"; | |
| /** | |
| * 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[1]); | |
| /** | |
| * 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>, | |
| threshold?: number, | |
| ): Point | null { | |
| const candidate = linesIntersectAt(line(l[0], l[1]), line(s[0], s[1])); | |
| if ( | |
| !candidate || | |
| !pointOnLineSegment(candidate, s, threshold) || | |
| !pointOnLineSegment(candidate, l, threshold) | |
| ) { | |
| return null; | |
| } | |
| return candidate; | |
| } | |
| export function lineSegmentsDistance<Point extends GlobalPoint | LocalPoint>( | |
| s1: LineSegment<Point>, | |
| s2: LineSegment<Point>, | |
| ): number { | |
| if (lineSegmentIntersectionPoints(s1, s2)) { | |
| return 0; | |
| } | |
| return Math.min( | |
| distanceToLineSegment(s1[0], s2), | |
| distanceToLineSegment(s1[1], s2), | |
| distanceToLineSegment(s2[0], s1), | |
| distanceToLineSegment(s2[1], s1), | |
| ); | |
| } | |