export type Point = [number, number]; export type Vector = Point; export class Line { p: Point; // Normalized vector v: Vector; l: number; constructor(p0: Point, p1: Point) { this.set(p0, p1); } set(p0: Point, p1: Point) { this.p = p0; this.v = sub([] as unknown as Vector, p1, p0); this.l = len(this.v); scale(this.v, this.v, 1 / this.l); } toString() { return `Line<(${this.p.join(', ')}) => (${scaleAndAdd( create(), this.p, this.v, this.l ).join(', ')}>)`; } } export class Ray extends Line { toString() { return `Ray<${this.p.join(', ')}) + (${this.v.join(', ')})`; } } export function create(x?: number, y?: number): Point { return [x || 0, y || 0]; } export function clone(p: Point): Point { return create(p[0], p[1]); } export function copy(out: Point, a: Point): Point { out[0] = a[0]; out[1] = a[1]; return out; } export function set(out: Point, x: number, y: number): Point { out[0] = x; out[1] = y; return out; } export function min(out: Point, a: Point, b: Point): Point { out[0] = Math.min(a[0], b[0]); out[1] = Math.min(a[1], b[1]); return out; } export function max(out: Point, a: Point, b: Point): Point { out[0] = Math.max(a[0], b[0]); out[1] = Math.max(a[1], b[1]); return out; } export function negate(out: Point, p: Point) { out[0] = -p[0]; out[1] = -p[1]; return out; } export function dot(v1: Point, v2: Point) { return v1[0] * v2[0] + v1[1] * v2[1]; } export function len(v: Point) { return Math.sqrt(lenSquare(v)); } export function lenSquare(v: Point) { const x = v[0]; const y = v[1]; return x * x + y * y; } export function normalize(out: Point, v: Point) { const x = v[0]; const y = v[1]; const d = Math.sqrt(x * x + y * y); out[0] = x / d; out[1] = y / d; return out; } export function dist(p0: Point, p1: Point) { return Math.sqrt(distSquare(p0, p1)); } export function distSquare(p0: Point, p1: Point) { const dx = p1[0] - p0[0]; const dy = p1[1] - p0[1]; return dx * dx + dy * dy; } export function numberApproxEqual(a: number, b: number) { return ( a == b || Math.abs(a - b) <= Math.max(Math.abs(a), Math.abs(b)) * 0.001 ); } export function approxEqual(p0: Point, p1: Point) { return numberApproxEqual(p0[0], p1[0]) && numberApproxEqual(p0[1], p1[1]); } export function equal(p0: Point, p1: Point) { if (p0 === p1) { return true; } return p0[0] === p1[0] && p0[1] === p1[1]; } export function scale(out: Point, v: Point, s: number) { out[0] = v[0] * s; out[1] = v[1] * s; return out; } export function mul(out: Point, v1: Point, v2: Point) { out[0] = v1[0] * v2[0]; out[1] = v1[1] * v2[1]; return out; } export function scaleAndAdd(out: Point, v1: Point, v2: Point, s: number) { out[0] = v1[0] + v2[0] * s; out[1] = v1[1] + v2[1] * s; return out; } export function add(out: Point, v1: Point, v2: Point) { out[0] = v1[0] + v2[0]; out[1] = v1[1] + v2[1]; return out; } export function sub(out: Point, v1: Point, v2: Point) { out[0] = v1[0] - v2[0]; out[1] = v1[1] - v2[1]; return out; } export function cross(a: Point, b: Point) { return a[0] * b[1] - b[0] * a[1]; } export const LINE_TYPE_SEG = 0 as const; export const LINE_TYPE_RAY = 1 as const; export const LINE_TYPE_LINE = 2 as const; export type LINE_TYPES = | typeof LINE_TYPE_RAY | typeof LINE_TYPE_SEG | typeof LINE_TYPE_LINE; function checkInLine(t: number, lineType: LINE_TYPES) { if (lineType === LINE_TYPE_SEG) { return t >= 0 && t <= 1; } else if (lineType === LINE_TYPE_RAY) { return t >= 0; } else { return true; } } export function lineIntersection( out: Point, line1: Line, line2: Line, line1Type: LINE_TYPES, line2Type: LINE_TYPES ) { const dx1 = line1.v[0] * line1.l; const dx2 = line2.v[0] * line2.l; const dy1 = line1.v[1] * line1.l; const dy2 = line2.v[1] * line2.l; const cross = dy2 * dx1 - dx2 * dy1; if (cross === 0) { return false; } const tmp1 = line1.p[1] - line2.p[1]; const tmp2 = line1.p[0] - line2.p[0]; const t1 = (dx2 * tmp1 - dy2 * tmp2) / cross; out[0] = line1.p[0] + t1 * dx1; out[1] = line1.p[1] + t1 * dy1; if (!checkInLine(t1, line1Type)) { return false; } const t2 = (dx1 * tmp1 - dy1 * tmp2) / cross; return checkInLine(t2, line2Type); } export function pointToLineDistance( pt: Point, line: Line, lineType: LINE_TYPES ) { // https://gist.github.com/mattdesl/47412d930dcd8cd765c871a65532ffac if (!line.l) { return dist(pt, line.p); } let t = ((pt[0] - line.p[0]) * line.v[0] + (pt[1] - line.p[1]) * line.v[1]) / line.l; if (lineType === LINE_TYPE_SEG) { t = Math.max(0, Math.min(1, t)); } else if (lineType === LINE_TYPE_RAY) { t = Math.max(0, t); } const len = line.l * t; const x0 = line.p[0] + line.v[0] * len; const y0 = line.p[1] + line.v[1] * len; const dx = x0 - pt[0]; const dy = y0 - pt[1]; return Math.sqrt(dx * dx + dy * dy); } export function area(points: Point[]) { // Signed polygon area const n = points.length; const end = n; const start = 0; if (n < 3) { return 0; } let area = 0; for (let i = end - 1, j = start; j < end; ) { const x0 = points[i][0]; const y0 = points[i][1]; const x1 = points[j][0]; const y1 = points[j][1]; i = j; j++; area += x0 * y1 - x1 * y0; } return area; } const v0: Point = [0, 0]; const v1: Point = [0, 0]; export function triangleArea(p0: Point, p1: Point, p2: Point) { sub(v0, p1, p0); sub(v1, p2, p1); return cross(v0, v1); }