/**
* Created by Alex Bol on 3/10/2017.
*/
"use strict";
import Flatten from '../flatten';
import * as Intersection from '../algorithms/intersection';
import {convertToString} from "../utils/attributes";
import {Shape} from "./shape";
import {Errors} from "../utils/errors";
/**
* Class representing a segment
* @type {Segment}
*/
export class Segment extends Shape {
/**
*
* @param {Point} ps - start point
* @param {Point} pe - end point
*/
constructor(...args) {
super()
/**
* Start point
* @type {Point}
*/
this.ps = new Flatten.Point();
/**
* End Point
* @type {Point}
*/
this.pe = new Flatten.Point();
if (args.length === 0) {
return;
}
if (args.length === 1 && args[0] instanceof Array && args[0].length === 4) {
let coords = args[0];
this.ps = new Flatten.Point(coords[0], coords[1]);
this.pe = new Flatten.Point(coords[2], coords[3]);
return;
}
if (args.length === 1 && args[0] instanceof Object && args[0].name === "segment") {
let {ps, pe} = args[0];
this.ps = new Flatten.Point(ps.x, ps.y);
this.pe = new Flatten.Point(pe.x, pe.y);
return;
}
// second point omitted issue #84
if (args.length === 1 && args[0] instanceof Flatten.Point) {
this.ps = args[0].clone();
return;
}
if (args.length === 2 && args[0] instanceof Flatten.Point && args[1] instanceof Flatten.Point) {
this.ps = args[0].clone();
this.pe = args[1].clone();
return;
}
if (args.length === 4) {
this.ps = new Flatten.Point(args[0], args[1]);
this.pe = new Flatten.Point(args[2], args[3]);
return;
}
throw Errors.ILLEGAL_PARAMETERS;
}
/**
* Return new cloned instance of segment
* @returns {Segment}
*/
clone() {
return new Flatten.Segment(this.start, this.end);
}
/**
* Start point
* @returns {Point}
*/
get start() {
return this.ps;
}
/**
* End point
* @returns {Point}
*/
get end() {
return this.pe;
}
/**
* Returns array of start and end point
* @returns [Point,Point]
*/
get vertices() {
return [this.ps.clone(), this.pe.clone()];
}
/**
* Length of a segment
* @returns {number}
*/
get length() {
return this.start.distanceTo(this.end)[0];
}
/**
* Slope of the line - angle to axe x in radians from 0 to 2PI
* @returns {number}
*/
get slope() {
let vec = new Flatten.Vector(this.start, this.end);
return vec.slope;
}
/**
* Bounding box
* @returns {Box}
*/
get box() {
return new Flatten.Box(
Math.min(this.start.x, this.end.x),
Math.min(this.start.y, this.end.y),
Math.max(this.start.x, this.end.x),
Math.max(this.start.y, this.end.y)
)
}
/**
* Returns true if equals to query segment, false otherwise
* @param {Seg} seg - query segment
* @returns {boolean}
*/
equalTo(seg) {
return this.ps.equalTo(seg.ps) && this.pe.equalTo(seg.pe);
}
/**
* Returns true if segment contains point
* @param {Point} pt Query point
* @returns {boolean}
*/
contains(pt) {
return Flatten.Utils.EQ_0(this.distanceToPoint(pt));
}
/**
* Returns array of intersection points between segment and other shape
* @param {Shape} shape - Shape of the one of supported types <br/>
* @returns {Point[]}
*/
intersect(shape) {
if (shape instanceof Flatten.Point) {
return this.contains(shape) ? [shape] : [];
}
if (shape instanceof Flatten.Line) {
return Intersection.intersectSegment2Line(this, shape);
}
if (shape instanceof Flatten.Ray) {
return Intersection.intersectRay2Segment(shape, this);
}
if (shape instanceof Flatten.Segment) {
return Intersection.intersectSegment2Segment(this, shape);
}
if (shape instanceof Flatten.Circle) {
return Intersection.intersectSegment2Circle(this, shape);
}
if (shape instanceof Flatten.Box) {
return Intersection.intersectSegment2Box(this, shape);
}
if (shape instanceof Flatten.Arc) {
return Intersection.intersectSegment2Arc(this, shape);
}
if (shape instanceof Flatten.Polygon) {
return Intersection.intersectSegment2Polygon(this, shape);
}
}
/**
* Calculate distance and shortest segment from segment to shape and return as array [distance, shortest segment]
* @param {Shape} shape Shape of the one of supported types Point, Line, Circle, Segment, Arc, Polygon or Planar Set
* @returns {number} distance from segment to shape
* @returns {Segment} shortest segment between segment and shape (started at segment, ended at shape)
*/
distanceTo(shape) {
if (shape instanceof Flatten.Point) {
let [dist, shortest_segment] = Flatten.Distance.point2segment(shape, this);
shortest_segment = shortest_segment.reverse();
return [dist, shortest_segment];
}
if (shape instanceof Flatten.Circle) {
let [dist, shortest_segment] = Flatten.Distance.segment2circle(this, shape);
return [dist, shortest_segment];
}
if (shape instanceof Flatten.Line) {
let [dist, shortest_segment] = Flatten.Distance.segment2line(this, shape);
return [dist, shortest_segment];
}
if (shape instanceof Flatten.Segment) {
let [dist, shortest_segment] = Flatten.Distance.segment2segment(this, shape);
return [dist, shortest_segment];
}
if (shape instanceof Flatten.Arc) {
let [dist, shortest_segment] = Flatten.Distance.segment2arc(this, shape);
return [dist, shortest_segment];
}
if (shape instanceof Flatten.Polygon) {
let [dist, shortest_segment] = Flatten.Distance.shape2polygon(this, shape);
return [dist, shortest_segment];
}
if (shape instanceof Flatten.PlanarSet) {
let [dist, shortest_segment] = Flatten.Distance.shape2planarSet(this, shape);
return [dist, shortest_segment];
}
}
/**
* Returns unit vector in the direction from start to end
* @returns {Vector}
*/
tangentInStart() {
let vec = new Flatten.Vector(this.start, this.end);
return vec.normalize();
}
/**
* Return unit vector in the direction from end to start
* @returns {Vector}
*/
tangentInEnd() {
let vec = new Flatten.Vector(this.end, this.start);
return vec.normalize();
}
/**
* Returns new segment with swapped start and end points
* @returns {Segment}
*/
reverse() {
return new Segment(this.end, this.start);
}
/**
* When point belongs to segment, return array of two segments split by given point,
* if point is inside segment. Returns clone of this segment if query point is incident
* to start or end point of the segment. Returns empty array if point does not belong to segment
* @param {Point} pt Query point
* @returns {Segment[]}
*/
split(pt) {
if (this.start.equalTo(pt))
return [null, this.clone()];
if (this.end.equalTo(pt))
return [this.clone(), null];
return [
new Flatten.Segment(this.start, pt),
new Flatten.Segment(pt, this.end)
]
}
/**
* Return middle point of the segment
* @returns {Point}
*/
middle() {
return new Flatten.Point((this.start.x + this.end.x) / 2, (this.start.y + this.end.y) / 2);
}
/**
* Get point at given length
* @param {number} length - The length along the segment
* @returns {Point}
*/
pointAtLength(length) {
if (length > this.length || length < 0) return null;
if (length == 0) return this.start;
if (length == this.length) return this.end;
let factor = length / this.length;
return new Flatten.Point(
(this.end.x - this.start.x) * factor + this.start.x,
(this.end.y - this.start.y) * factor + this.start.y
);
}
distanceToPoint(pt) {
let [dist, ...rest] = Flatten.Distance.point2segment(pt, this);
return dist;
};
definiteIntegral(ymin = 0.0) {
let dx = this.end.x - this.start.x;
let dy1 = this.start.y - ymin;
let dy2 = this.end.y - ymin;
return (dx * (dy1 + dy2) / 2);
}
/**
* Return new segment transformed using affine transformation matrix
* @param {Matrix} matrix - affine transformation matrix
* @returns {Segment} - transformed segment
*/
transform(matrix = new Flatten.Matrix()) {
return new Segment(this.ps.transform(matrix), this.pe.transform(matrix))
}
/**
* Returns true if segment start is equal to segment end up to DP_TOL
* @returns {boolean}
*/
isZeroLength() {
return this.ps.equalTo(this.pe)
}
/**
* Sort given array of points from segment start to end, assuming all points lay on the segment
* @param {Point[]} - array of points
* @returns {Point[]} new array sorted
*/
sortPoints(pts) {
let line = new Flatten.Line(this.start, this.end);
return line.sortPoints(pts);
}
get name() {
return "segment"
}
/**
* Return string to draw segment in svg
* @param {Object} attrs - an object with attributes for svg path element,
* like "stroke", "strokeWidth" <br/>
* Defaults are stroke:"black", strokeWidth:"1"
* @returns {string}
*/
svg(attrs = {}) {
return `\n<line x1="${this.start.x}" y1="${this.start.y}" x2="${this.end.x}" y2="${this.end.y}" ${convertToString(attrs)} />`;
}
}
Flatten.Segment = Segment;
/**
* Shortcut method to create new segment
*/
export const segment = (...args) => new Flatten.Segment(...args);
Flatten.segment = segment