math_Line3.ts

import {EPS10} from '../math';
import {Sphere} from "../bv/Sphere";
import {Vec3} from './Vec3';

/**
 * @todo: write tests
 * @class Line3
 * Represents a line segment in 3d space.
 * @param {Vec3} [p0] - First point of the line
 * @param {Vec3} [p1] - Second point of the line
 */
export class Line3 {

    /**
     * First point of the line
     * @public
     * @type: Vec3
     */
    public p0: Vec3;

    /**
     * Second point of the line
     * @public
     * @type: Vec3
     */
    public p1: Vec3;

    constructor(p0?: Vec3, p1?: Vec3) {
        this.p0 = p0 || new Vec3();
        this.p1 = p1 || new Vec3();
    }

    public getMagnitude(): number {
        return this.p0.distance(this.p1);
    }

    public getSphereIntersection(sphere: Sphere): [Vec3] | [Vec3, Vec3] | [] {
        let p0 = this.p0,
            p1 = this.p1;

        let cx = sphere.center.x,
            cy = sphere.center.y,
            cz = sphere.center.z;

        let px = p0.x,
            py = p0.y,
            pz = p0.z;

        let vx = p1.x - px,
            vy = p1.y - py,
            vz = p1.z - pz;

        let A = vx * vx + vy * vy + vz * vz,
            B = 2.0 * (px * vx + py * vy + pz * vz - vx * cx - vy * cy - vz * cz),
            C = px * px - 2 * px * cx + cx * cx + py * py - 2 * py * cy + cy * cy +
                pz * pz - 2 * pz * cz + cz * cz - sphere.radius * sphere.radius;
        let D = B * B - 4 * A * C;

        if (D < 0) {
            // no solutions
            return [];
        }

        let t1 = (-B - Math.sqrt(D)) / (2.0 * A);

        let solution1 = new Vec3(
            p0.x * (1 - t1) + t1 * p1.x,
            p0.y * (1 - t1) + t1 * p1.y,
            p0.z * (1 - t1) + t1 * p1.z
        );

        if (D == 0) {
            return [solution1];
        }

        let t2 = (-B + Math.sqrt(D)) / (2.0 * A);
        let solution2 = new Vec3(
            p0.x * (1 - t2) + t2 * p1.x,
            p0.y * (1 - t2) + t2 * p1.y,
            p0.z * (1 - t2) + t2 * p1.z);

        // prefer a solution that's on the line segment itself
        if (Math.abs(t1 - 0.5) < Math.abs(t2 - 0.5)) {
            return [solution1, solution2];
        }

        return [solution2, solution1];
    }

    public intersects(line: Line3, res: Vec3, res2?: Vec3): boolean {

        let p13 = this.p0.sub(line.p0),
            p43 = line.p1.sub(line.p0);

        if (Math.abs(p43.x) < EPS10 && Math.abs(p43.y) < EPS10 && Math.abs(p43.z) < EPS10) {
            return false;
        }

        let p21 = this.p1.sub(this.p0);

        if (Math.abs(p21.x) < EPS10 && Math.abs(p21.y) < EPS10 && Math.abs(p21.z) < EPS10) {
            return false;
        }

        let d1343 = p13.x * p43.x + p13.y * p43.y + p13.z * p43.z,
            d4321 = p43.x * p21.x + p43.y * p21.y + p43.z * p21.z,
            d1321 = p13.x * p21.x + p13.y * p21.y + p13.z * p21.z,
            d4343 = p43.x * p43.x + p43.y * p43.y + p43.z * p43.z,
            d2121 = p21.x * p21.x + p21.y * p21.y + p21.z * p21.z;

        let denom = d2121 * d4343 - d4321 * d4321;

        if (Math.abs(denom) < EPS10) {
            return false;
        }

        let numer = d1343 * d4321 - d1321 * d4343;

        let mua = numer / denom;

        res.x = this.p0.x + mua * p21.x;
        res.y = this.p0.y + mua * p21.y;
        res.z = this.p0.z + mua * p21.z;

        if (res2) {

            let mub = (d1343 + d4321 * mua) / d4343;

            res2.x = line.p0.x + mub * p43.x;
            res2.y = line.p0.y + mub * p43.y;
            res2.z = line.p0.z + mub * p43.z;
        }

        return true;
    }

    public getNearestDistancePoint(point: Vec3, res: Vec3): boolean {

        let p0 = this.p0,
            p1 = this.p1;

        let mag = this.getMagnitude();

        let u =
            (
                ((point.x - p0.x) * (p1.x - p0.x)) +
                ((point.y - p0.y) * (p1.y - p0.y)) +
                ((point.z - p0.z) * (p1.z - p0.z))
            ) /
            (mag * mag);

        res.x = p0.x + u * (p1.x - p0.x);
        res.y = p0.y + u * (p1.y - p0.y);
        res.z = p0.z + u * (p1.z - p0.z);

        if (u < 0.0 || u > 1.0) {
            return false;
        }

        return true;
    }
}