UNPKG

@zenghawtin/graph2d

Version:

Javascript library for 2d geometry

375 lines (329 loc) 11.3 kB
/** * Created by Alex Bol on 2/20/2017. */ "use strict"; import Flatten from '../flatten'; import * as Intersection from '../algorithms/intersection'; import {Shape} from "./shape"; import {Matrix} from "./matrix"; import {Errors} from "../utils/errors"; let {vector} = Flatten; /** * Class representing a line * @type {Line} */ export class Line extends Shape { /** * Line may be constructed by point and normal vector or by two points that a line passes through * @param {Point} pt - point that a line passes through * @param {Vector|Point} norm - normal vector to a line or second point a line passes through */ constructor(...args) { super() /** * Point a line passes through * @type {Point} */ this.pt = new Flatten.Point(); /** * Normal vector to a line <br/> * Vector is normalized (length == 1)<br/> * Direction of the vector is chosen to satisfy inequality norm * p >= 0 * @type {Vector} */ this.norm = new Flatten.Vector(0, 1); if (args.length === 0) { return; } if (args.length === 1 && args[0] instanceof Object && args[0].name === "line") { let {pt, norm} = args[0]; this.pt = new Flatten.Point(pt); this.norm = new Flatten.Vector(norm); return; } if (args.length === 2) { let a1 = args[0]; let a2 = args[1]; if (a1 instanceof Flatten.Point && a2 instanceof Flatten.Point) { this.pt = a1; this.norm = Line.points2norm(a1, a2); if (this.norm.dot(vector(this.pt.x,this.pt.y)) >= 0) { this.norm.invert(); } return; } if (a1 instanceof Flatten.Point && a2 instanceof Flatten.Vector) { if (Flatten.Utils.EQ_0(a2.x) && Flatten.Utils.EQ_0(a2.y)) { throw Errors.ILLEGAL_PARAMETERS; } this.pt = a1.clone(); this.norm = a2.clone(); this.norm = this.norm.normalize(); if (this.norm.dot(vector(this.pt.x,this.pt.y)) >= 0) { this.norm.invert(); } return; } if (a1 instanceof Flatten.Vector && a2 instanceof Flatten.Point) { if (Flatten.Utils.EQ_0(a1.x) && Flatten.Utils.EQ_0(a1.y)) { throw Errors.ILLEGAL_PARAMETERS; } this.pt = a2.clone(); this.norm = a1.clone(); this.norm = this.norm.normalize(); if (this.norm.dot(vector(this.pt.x,this.pt.y)) >= 0) { this.norm.invert(); } return; } } throw Errors.ILLEGAL_PARAMETERS; } /** * Return new cloned instance of line * @returns {Line} */ clone() { return new Flatten.Line(this.pt, this.norm); } /* The following methods need for implementation of Edge interface /** * Line has no start point * @returns {undefined} */ get start() {return undefined;} /** * Line has no end point */ get end() {return undefined;} /** * Return positive infinity number as length * @returns {number} */ get length() {return Number.POSITIVE_INFINITY;} /** * Returns infinite box * @returns {Box} */ get box() { return new Flatten.Box( Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY ) } /** * Middle point is undefined * @returns {undefined} */ get middle() {return undefined} /** * Slope of the line - angle in radians between line and axe x from 0 to 2PI * @returns {number} - slope of the line */ get slope() { let vec = new Flatten.Vector(this.norm.y, -this.norm.x); return vec.slope; } /** * Get coefficients [A,B,C] of a standard line equation in the form Ax + By = C * @code [A, B, C] = line.standard * @returns {number[]} - array of coefficients */ get standard() { let A = this.norm.x; let B = this.norm.y; let C = this.norm.dot(vector(this.pt.x, this.pt.y)); return [A, B, C]; } /** * Return true if parallel or incident to other line * @param {Line} other_line - line to check * @returns {boolean} */ parallelTo(other_line) { return Flatten.Utils.EQ_0(this.norm.cross(other_line.norm)); } /** * Returns true if incident to other line * @param {Line} other_line - line to check * @returns {boolean} */ incidentTo(other_line) { return this.parallelTo(other_line) && this.pt.on(other_line); } /** * Returns true if point belongs to line * @param {Point} pt Query point * @returns {boolean} */ contains(pt) { if (this.pt.equalTo(pt)) { return true; } /* Line contains point if vector to point is orthogonal to the line normal vector */ let vec = new Flatten.Vector(this.pt, pt); return Flatten.Utils.EQ_0(this.norm.dot(vec)); } /** * Return coordinate of the point that lies on the line in the transformed * coordinate system where center is the projection of the point(0,0) to * the line and axe y is collinear to the normal vector. <br/> * This method assumes that point lies on the line and does not check it * @param {Point} pt - point on a line * @returns {number} */ coord(pt) { return vector(pt.x, pt.y).cross(this.norm); } /** * Returns array of intersection points * @param {Shape} shape - shape to intersect with * @returns {Point[]} */ intersect(shape) { if (shape instanceof Flatten.Point) { return this.contains(shape) ? [shape] : []; } if (shape instanceof Flatten.Line) { return Intersection.intersectLine2Line(this, shape); } if (shape instanceof Flatten.Ray) { return Intersection.intersectRay2Line(shape, this); } if (shape instanceof Flatten.Circle) { return Intersection.intersectLine2Circle(this, shape); } if (shape instanceof Flatten.Box) { return Intersection.intersectLine2Box(this, shape); } if (shape instanceof Flatten.Segment) { return Intersection.intersectSegment2Line(shape, this); } if (shape instanceof Flatten.Arc) { return Intersection.intersectLine2Arc(this, shape); } if (shape instanceof Flatten.Polygon) { return Intersection.intersectLine2Polygon(this, shape); } } /** * Calculate distance and shortest segment from line to shape and returns array [distance, shortest_segment] * @param {Shape} shape Shape of the one of the types Point, Circle, Segment, Arc, Polygon * @returns {[number, Segment]} */ distanceTo(shape) { if (shape instanceof Flatten.Point) { let [distance, shortest_segment] = Flatten.Distance.point2line(shape, this); shortest_segment = shortest_segment.reverse(); return [distance, shortest_segment]; } if (shape instanceof Flatten.Circle) { let [distance, shortest_segment] = Flatten.Distance.circle2line(shape, this); shortest_segment = shortest_segment.reverse(); return [distance, shortest_segment]; } if (shape instanceof Flatten.Segment) { let [distance, shortest_segment] = Flatten.Distance.segment2line(shape, this); return [distance, shortest_segment.reverse()]; } if (shape instanceof Flatten.Arc) { let [distance, shortest_segment] = Flatten.Distance.arc2line(shape, this); return [distance, shortest_segment.reverse()]; } if (shape instanceof Flatten.Polygon) { let [distance, shortest_segment] = Flatten.Distance.shape2polygon(this, shape); return [distance, shortest_segment]; } } /** * Split line with a point or array of points and return array of shapes * Assumed (but not checked) that all points lay on the line * @param {Point | Point[]} pt * @returns {MultilineShapes} */ split(pt) { if (pt instanceof Flatten.Point) { return [new Flatten.Ray(pt, this.norm), new Flatten.Ray(pt, this.norm)] } else { let multiline = new Flatten.Multiline([this]); let sorted_points = this.sortPoints(pt); multiline.split(sorted_points); return multiline.toShapes(); } } /** * Return new line rotated by angle * @param {number} angle - angle in radians * @param {Point} center - center of rotation */ rotate(angle, center = new Flatten.Point()) { return new Flatten.Line( this.pt.rotate(angle, center), this.norm.rotate(angle) ) } /** * Return new line transformed by affine transformation matrix * @param {Matrix} m - affine transformation matrix (a,b,c,d,tx,ty) * @returns {Line} */ transform(m) { return new Flatten.Line( this.pt.transform(m), this.norm.clone() ) } /** * Sort given array of points that lay on a line with respect to coordinate on a line * The method assumes that points lay on the line and does not check this * @param {Point[]} pts - array of points * @returns {Point[]} new array sorted */ sortPoints(pts) { return pts.slice().sort( (pt1, pt2) => { if (this.coord(pt1) < this.coord(pt2)) { return -1; } if (this.coord(pt1) > this.coord(pt2)) { return 1; } return 0; }) } get name() { return "line" } /** * Return string to draw svg segment representing line inside given box * @param {Box} box Box representing drawing area * @param {Object} attrs - an object with attributes of svg circle element */ svg(box, attrs = {}) { let ip = Intersection.intersectLine2Box(this, box); if (ip.length === 0) return ""; let ps = ip[0]; let pe = ip.length === 2 ? ip[1] : ip.find(pt => !pt.equalTo(ps)); if (pe === undefined) pe = ps; let segment = new Flatten.Segment(ps, pe); return segment.svg(attrs); } static points2norm(pt1, pt2) { if (pt1.equalTo(pt2)) { throw Errors.ILLEGAL_PARAMETERS; } let vec = new Flatten.Vector(pt1, pt2); let unit = vec.normalize(); return unit.rotate90CCW(); } } Flatten.Line = Line; /** * Function to create line equivalent to "new" constructor * @param args */ export const line = (...args) => new Flatten.Line(...args); Flatten.line = line;