diff options
| author | kj_sh604 | 2026-03-15 16:19:35 -0400 |
|---|---|---|
| committer | kj_sh604 | 2026-03-15 16:19:35 -0400 |
| commit | 6ec259a0e71174651bae95d4628138bf6fd68742 (patch) | |
| tree | 5e33c6a5ec091ecabfcb257fdc7b6a88ed8754ac /packages/math/segment.ts | |
| parent | 16c8578b15c727f22921f8a80a56ee4d4e7f2272 (diff) | |
refactor: packages/
Diffstat (limited to 'packages/math/segment.ts')
| -rw-r--r-- | packages/math/segment.ts | 174 |
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; +} |
