Source: model/Segment.js

/**
 * @file Class Segment.
 * @version March 22, 2017
 *
 * @author Olivier Pirson --- http://www.opimedia.be/
 * @license GPLv3 --- Copyright (C) 2017 Olivier Pirson
 */

/**
 * A segment,
 * represented by its two points.
 */
class Segment {
    /**
     * Constructs a segment from point a to point b.
     *
     * @param a: Point
     * @param b: Point != a
     */
    constructor(a, b) {
        assert(a instanceof Point, a);
        assert(b instanceof Point, b);
        assert(!a.isEquals(b));

        this._a = a;
        this._b = b;
    }


    /**
     * Returns the first point.
     *
     * @returns {Point}
     */
    get a() { return this._a; }


    /**
     * Returns the second point.
     *
     * @returns {Point}
     */
    get b() { return this._b; }



    /**
     * Compare two segments before with min points
     * and maybe after with max points.
     *
     * @param {Segment} other
     *
     * @returns {number} -1, 0 or 1
     */
    compare(other) {
        assert(other instanceof Segment, other);

        const compA = this.a.compare(this.b);
        const compOtherA = other.a.compare(other.b);

        let comp = (compA <= 0
                    ? (compOtherA <= 0
                       ? this.a.compare(other.a)
                       : this.a.compare(other.b))
                    : (compOtherA <= 0
                       ? this.b.compare(other.a)
                       : this.b.compare(other.b)));

        return (comp !== 0
                ? comp
                : (compA <= 0
                   ? (compOtherA <= 0
                      ? this.b.compare(other.b)
                      : this.b.compare(other.a))
                   : (compOtherA <= 0
                      ? this.a.compare(other.b)
                      : this.a.compare(other.a))));
    }


    /**
     * Returns true iff have at leat one common endpoint.
     */
    isCommonEndPoint(other) {
        assert(other instanceof Segment, other);

        return (this.a.isEquals(other.a) || this.a.isEquals(other.b)
                || this.b.isEquals(other.b) || this.b.isEquals(other.a));
    }


    /**
     * Returns true iff the segment is equals to the segment other,
     * namely iff they are same points (regardless of the order).
     *
     * @param {Segment} other
     *
     * @returns {boolean}
     */
    isEquals(other) {
        assert(other instanceof Segment, other);

        return (((this.a === other.a) && (this.b === other.b))
                || ((this.a === other.b) && (this.b === other.a)));
    }


    /**
     * Returns true iff point is an extremity point of the segment.
     *
     * @param {Point} point
     *
     * @returns {boolean}
     */
    isExtremityPoint(point) {
        assert(point instanceof Point, point);

        return point.isEquals(this.a) || point.isEquals(this.b);
    }


    /**
     * Returns true iff is a horizontal segment
     *
     * @returns {boolean}
     */
    isHorizontal() {
        return (this.a.y === this.b.y);
    }


    /**
     * Returns true iff p is in the segment (inclusive its extremities).
     *
     * @param {Point} p
     *
     * @returns {boolean}
     */
    isIn(p) {
        assert(p instanceof Point, p);

        if (!this.isInSameLine(p)) {   // not on the same line
            return false;
        }
        else if (this.isVertical()) {  // on the same vertical line
            return ((this.a.x <= p.x) && (p.x <= this.b.x)) || ((this.b.x <= p.x) && (p.x <= this.a.x));
        }
        else {                         // on the same line, not vertical
            return ((this.a.y <= p.y) && (p.y <= this.b.y)) || ((this.b.y <= p.y) && (p.y <= this.a.y));
        }
    }


    /**
     * Returns true iff p is in the same line that the segment.
     *
     * @param {Point} p
     *
     * @returns {boolean}
     */
    isInSameLine(p) {
        assert(p instanceof Point, p);

        return (new Triplet(this.a, this.b, p)).isCollinear();
    }


    /**
     * Returns true iff two segments intersect.
     *
     * @param {Segment} other
     *
     * @returns {boolean}
     */
    isIntersect(other) {
        assert(other instanceof Segment, other);

        return (this.isProperIntersect(other)
                || this.isIn(other.a) || this.isIn(other.b)
                || other.isIn(this.a) || other.isIn(this.b));
    }


    /**
     * Returns true iff p is in the segment (exclusive its extremities).
     *
     * @param {Point} p
     *
     * @returns {boolean}
     */
    isProperIn(p) {
        assert(p instanceof Point, p);

        if (!this.isInSameLine(p)) {   // not on the same line
            return false;
        }
        else if (this.isVertical()) {  // on the same line, not vertical
            return ((this.a.x < p.x) && (p.x < this.b.x)) || ((this.b.x < p.x) && (p.x < this.a.x));
        }
        else {                         // on the same vertical line
            return ((this.a.y < p.y) && (p.y < this.b.y)) || ((this.b.y < p.y) && (p.y < this.a.y));
        }
    }


    /**
     * Returns true iff two segments intersect without their endpoints.
     *
     * @param {Segment} other
     *
     * @returns {boolean}
     */
    isProperIntersect(other) {
        assert(other instanceof Segment, other);

        const abc_abd = ((new Triplet(this.a, this.b, other.a)).orientation()
                         *(new Triplet(this.a, this.b, other.b)).orientation());
        const cda_cdb = ((new Triplet(other.a, other.b, this.a)).orientation()
                         *(new Triplet(other.a, other.b, this.b)).orientation());

        return (abc_abd * cda_cdb !== 0) && (abc_abd < 0) && (cda_cdb < 0);
    }


    /**
     * Returns true iff the horizontal line of vertical coordonate y
     * (strictically) intersect the segment.
     */
    isProperIntersectWithHorizontalLine(y) {
        assert(typeof y === "number", y);

        return (((this.a.y < y) && (y < this.b.y))
                || ((this.b.y < y) && (y < this.a.y)));
    }


    /**
     * Returns true iff is a vertical segment
     *
     * @returns {boolean}
     */
    isVertical() {
        return (this.a.x === this.b.x);
    }


    /**
     * If segment.a <= segment.b
     * then return this,
     * else return a new segment with points swapped.
     *
     * @returns {Segment}
     */
    ordonned() {
        return (this.a.compare(this.b) <= 0
                ? this
                : new Segment(this.b, this.a));
    }


    /**
     * Returns the slope of the segment.
     * If the segment is vertical, then return null.
     *
     * @returns {number|null}
     */
    slope() {
        const diff_x = this.a.x - this.b.x;

        return (this.isVertical()
                ? null
                : (this.a.y - this.b.y)/diff_x);
    }


    /**
     * Returns a string representation of the segment.
     *
     * Each number is represented with at most precision figures after the decimal point.
     *
     * @returns {String}
     */
    toString(precision=2) {
        assert(Number.isInteger(precision), precision);
        assert(precision >= 0, precision);

        return (this.a.toString(precision)
                + ":" + this.b.toString(precision));
    }


    /**
     * Returns the y coordinate for the given x coordinate.
     * If the segment is vertical, then return null.
     *
     * @param {number} x
     *
     * @returns {number|null}
     */
    yOnLine(x) {
        assert(typeof x === "number", x);

        return (this.isVertical()
                ? null
                : this.slope()*(x - this.a.x) + this.a.y);
    }
}