math/Line2.tstypescript
import { EPS10 } from "../math";
import { Vec2 } from "./Vec2";

export class Line2 {
    public a: number;
    public b: number;
    public c: number;

    constructor(a = 0, b = 0, c = 0) {
        this.a = a;
        this.b = b;
        this.c = c;
    }

    static get(p0: Vec2, p1: Vec2): Line2 {
        return new Line2(p1.y - p0.y, p0.x - p1.x, p1.x * p0.y - p0.x * p1.y);
    }

    static getParallel(l: Line2, p: Vec2): Line2 {
        return new Line2(l.a, l.b, -l.a * p.x - l.b * p.y);
    }

    static getIntersection(L0: Line2, L1: Line2): Vec2 {
        let x = (L1.b * L0.c - L0.b * L1.c) / (L0.b * L1.a - L1.b * L0.a);
        return new Vec2(x, -(L0.c + L0.a * x) / L0.b);
    }

    static getSegmentCircleIntersectionInto(
        p0: Vec2,
        p1: Vec2,
        center: Vec2,
        radius: number,
        out0: Vec2,
        out1: Vec2
    ): 0 | 1 | 2 {
        if (radius < 0) {
            return 0;
        }

        const x0 = p0.x,
            y0 = p0.y,
            dx = p1.x - x0,
            dy = p1.y - y0,
            fx = x0 - center.x,
            fy = y0 - center.y,
            a = dx * dx + dy * dy,
            c = fx * fx + fy * fy - radius * radius;

        if (a === 0) {
            if (c < -EPS10 || c > EPS10) {
                return 0;
            }

            out0.x = x0;
            out0.y = y0;

            return 1;
        }

        const q = fx * dx + fy * dy,
            h = q * q - a * c,
            tolerance = EPS10 * a;

        if (h < -tolerance) {
            return 0;
        }

        if (h <= tolerance) {
            const numerator = -q;

            if (numerator < 0 || numerator > a) {
                return 0;
            }

            const t = numerator / a;

            out0.x = x0 + dx * t;
            out0.y = y0 + dy * t;

            return 1;
        }

        const sqrtH = Math.sqrt(h),
            numerator0 = -q - sqrtH,
            numerator1 = -q + sqrtH,
            hit0 = numerator0 >= 0 && numerator0 <= a,
            hit1 = numerator1 >= 0 && numerator1 <= a;

        if (!hit0 && !hit1) {
            return 0;
        }

        const invA = 1 / a;

        if (hit0) {
            const t0 = numerator0 * invA;

            out0.x = x0 + dx * t0;
            out0.y = y0 + dy * t0;

            if (!hit1) {
                return 1;
            }

            const t1 = numerator1 * invA;

            out1.x = x0 + dx * t1;
            out1.y = y0 + dy * t1;

            return 2;
        }

        const t1 = numerator1 * invA;

        out0.x = x0 + dx * t1;
        out0.y = y0 + dy * t1;

        return 1;
    }

    static getSegmentCircleIntersection(p0: Vec2, p1: Vec2, center: Vec2, radius: number): [] | [Vec2] | [Vec2, Vec2] {
        const out0 = new Vec2(),
            out1 = new Vec2(),
            hits = Line2.getSegmentCircleIntersectionInto(p0, p1, center, radius, out0, out1);

        if (hits === 0) {
            return [];
        }

        if (hits === 1) {
            return [out0];
        }

        return [out0, out1];
    }

    public intersects(l: Line2): Vec2 {
        return Line2.getIntersection(this, l);
    }

    /**
     * Finds the intersection of the line with a circle.
     * @param {Vec2} center - Circle center.
     * @param {number} radius - Circle radius.
     * @returns {Array.<Vec2>} Zero, one, or two intersection points.
     */
    public getCircleIntersection(center: Vec2, radius: number): [] | [Vec2] | [Vec2, Vec2] {
        const a = this.a,
            b = this.b,
            c = this.c,
            x = center.x,
            y = center.y,
            ab2 = a * a + b * b;

        if (ab2 === 0 || radius < 0) {
            return [];
        }

        const lineValue = a * x + b * y + c,
            discriminant = radius * radius * ab2 - lineValue * lineValue,
            tolerance = EPS10 * ab2;

        if (discriminant < -tolerance) {
            return [];
        }

        const invAb2 = 1 / ab2,
            projection = lineValue * invAb2,
            closestX = x - a * projection,
            closestY = y - b * projection;

        if (discriminant <= tolerance) {
            return [new Vec2(closestX, closestY)];
        }

        const offset = Math.sqrt(discriminant) * invAb2,
            dx = -b * offset,
            dy = a * offset;

        return [new Vec2(closestX + dx, closestY + dy), new Vec2(closestX - dx, closestY - dy)];
    }
}