UNPKG

romgrk-2d-geometry

Version:

Javascript library for 2d geometry

346 lines (297 loc) 10.2 kB
import Errors from '../utils/errors' import * as Utils from '../utils/utils' import * as Distance from '../algorithms/distance'; import * as Intersection from '../algorithms/intersection'; import {convertToString} from "../utils/attributes"; import { PlanarSet } from '../data_structures/PlanarSet'; import * as geom from './index' import {Point} from './Point'; import {Shape} from './Shape'; /** * Class representing a segment * @type {Segment} */ export class Segment extends Shape<Segment> { static EMPTY = Object.freeze(new Segment(Point.EMPTY, Point.EMPTY)); /** Start point */ start: Point /** End Point */ end: Point constructor(); constructor(other: Segment); constructor(start: Point, end: Point); constructor(coords: [number, number, number, number]); constructor(x1: number, y1: number, x2: number, y2: number); constructor(...args) { super() this.start = Point.EMPTY; this.end = Point.EMPTY; if (args.length === 0) { return; } if (args.length === 1 && args[0] instanceof Array && args[0].length === 4) { let coords = args[0]; this.start = new Point(coords[0], coords[1]); this.end = new Point(coords[2], coords[3]); return; } if (args.length === 1 && args[0] instanceof Object && args[0].name === "segment") { let {ps, pe} = args[0]; this.start = new Point(ps.x, ps.y); this.end = new Point(pe.x, pe.y); return; } // second point omitted issue #84 if (args.length === 1 && args[0] instanceof Point) { this.start = args[0].clone(); return; } if (args.length === 2 && args[0] instanceof Point && args[1] instanceof Point) { this.start = args[0].clone(); this.end = args[1].clone(); return; } if (args.length === 4) { this.start = new Point(args[0], args[1]); this.end = new Point(args[2], args[3]); return; } throw Errors.ILLEGAL_PARAMETERS; } /** * Return new cloned instance of segment * @returns {Segment} */ clone() { return new geom.Segment(this.start, this.end); } /** * Returns array of start and end point * @returns [Point,Point] */ get vertices() { return [this.start.clone(), this.end.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 geom.Vector(this.start, this.end); return vec.slope; } /** * Bounding box * @returns {Box} */ get box() { return new geom.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.start.equalTo(seg.ps) && this.end.equalTo(seg.pe); } /** * Returns true if segment contains point * @param {Point} pt Query point * @returns {boolean} */ contains(pt) { return 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 Point) { return this.contains(shape) ? [shape] : []; } if (shape instanceof geom.Line) { return Intersection.intersectSegment2Line(this, shape); } if (shape instanceof geom.Ray) { return Intersection.intersectRay2Segment(shape, this); } if (shape instanceof geom.Segment) { return Intersection.intersectSegment2Segment(this, shape); } if (shape instanceof geom.Circle) { return Intersection.intersectSegment2Circle(this, shape); } if (shape instanceof geom.Box) { return Intersection.intersectSegment2Box(this, shape); } if (shape instanceof geom.Arc) { return Intersection.intersectSegment2Arc(this, shape); } if (shape instanceof geom.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 Point) { let [dist, shortest_segment] = Distance.point2segment(shape, this); shortest_segment = shortest_segment.reverse(); return [dist, shortest_segment]; } if (shape instanceof geom.Circle) { let [dist, shortest_segment] = Distance.segment2circle(this, shape); return [dist, shortest_segment]; } if (shape instanceof geom.Line) { let [dist, shortest_segment] = Distance.segment2line(this, shape); return [dist, shortest_segment]; } if (shape instanceof geom.Segment) { let [dist, shortest_segment] = Distance.segment2segment(this, shape); return [dist, shortest_segment]; } if (shape instanceof geom.Arc) { let [dist, shortest_segment] = Distance.segment2arc(this, shape); return [dist, shortest_segment]; } if (shape instanceof geom.Polygon) { let [dist, shortest_segment] = Distance.shape2polygon(this, shape); return [dist, shortest_segment]; } if (shape instanceof PlanarSet) { let [dist, shortest_segment] = Distance.shape2planarSet(this, shape); return [dist, shortest_segment]; } } /** * Returns unit vector in the direction from start to end * @returns {Vector} */ tangentInStart() { let vec = new geom.Vector(this.start, this.end); return vec.normalize(); } /** * Return unit vector in the direction from end to start */ tangentInEnd() { let vec = new geom.Vector(this.end, this.start); return vec.normalize(); } /** * Returns new segment with swapped start and end points */ 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 pt Query point */ split(pt: Point) { if (this.start.equalTo(pt)) return [null, this.clone()]; if (this.end.equalTo(pt)) return [this.clone(), null]; return [ new geom.Segment(this.start, pt), new geom.Segment(pt, this.end) ] } /** * Return middle point of the segment * @returns {Point} */ middle() { return new 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 <= 0) return this.start; if (length >= this.length) return this.end; let factor = length / this.length; return new 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] = 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 geom.Matrix()) { return new Segment(this.start.transform(matrix), this.end.transform(matrix)) } /** * Returns true if segment start is equal to segment end up to DP_TOL * @returns {boolean} */ isZeroLength() { return this.start.equalTo(this.end) } /** * 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 geom.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)} />`; } } /** * Shortcut method to create new segment */ export const segment = (...args) => // @ts-ignore new geom.Segment(...args);