/**
* @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);
}
}