UNPKG

@flatten-js/core

Version:

Javascript library for 2d geometry

217 lines (182 loc) 6.47 kB
"use strict"; import Flatten from '../flatten'; import * as Intersection from "../algorithms/intersection"; /** * Class representing a ray (a half-infinite line). * @type {Ray} */ export class Ray { /** * Ray may be constructed by setting an <b>origin</b> point and a <b>normal</b> vector, so that any point <b>x</b> * on a ray fit an equation: <br /> * (<b>x</b> - <b>origin</b>) * <b>vector</b> = 0 <br /> * Ray defined by constructor is a right semi-infinite line with respect to the normal vector <br/> * If normal vector is omitted ray is considered horizontal (normal vector is (0,1)). <br/> * Don't be confused: direction of the normal vector is orthogonal to the ray <br/> * @param {Point} pt - start point * @param {Vector} norm - normal vector */ constructor(...args) { this.pt = new Flatten.Point(); this.norm = new Flatten.Vector(0,1); if (args.length == 0) { return; } if (args.length >= 1 && args[0] instanceof Flatten.Point) { this.pt = args[0].clone(); } if (args.length === 1) { return; } if (args.length === 2 && args[1] instanceof Flatten.Vector) { this.norm = args[1].clone(); return; } // if (args.length == 2 && typeof (args[0]) == "number" && typeof (args[1]) == "number") { // this.pt = new Flatten.Point(args[0], args[1]); // return; // } throw Flatten.Errors.ILLEGAL_PARAMETERS; } /** * Return new cloned instance of ray * @returns {Ray} */ clone() { return new Ray(this.pt, this.norm); } /** * Slope of the ray - angle in radians between ray 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; } /** * Returns half-infinite bounding box of the ray * @returns {Box} - bounding box */ get box() { let slope = this.slope; return new Flatten.Box( slope > Math.PI/2 && slope < 3*Math.PI/2 ? Number.NEGATIVE_INFINITY : this.pt.x, slope >= 0 && slope <= Math.PI ? this.pt.y : Number.NEGATIVE_INFINITY, slope >= Math.PI/2 && slope <= 3*Math.PI/2 ? this.pt.x : Number.POSITIVE_INFINITY, slope >= Math.PI && slope <= 2*Math.PI || slope == 0 ? this.pt.y : Number.POSITIVE_INFINITY ) } /** * Return ray start point * @returns {Point} - ray start point */ get start() { return this.pt; } /** * Ray has no end point? * @returns {undefined} */ get end() {return undefined;} /** * Return positive infinity number as length * @returns {number} */ get length() {return Number.POSITIVE_INFINITY;} /** * Returns true if point belongs to ray * @param {Point} pt Query point * @returns {boolean} */ contains(pt) { if (this.pt.equalTo(pt)) { return true; } /* Ray contains point if vector to point is orthogonal to the ray normal vector and cross product from vector to point is positive */ let vec = new Flatten.Vector(this.pt, pt); return Flatten.Utils.EQ_0(this.norm.dot(vec)) && Flatten.Utils.GE(vec.cross(this.norm),0); } /** * Split ray with point and return array of segment and new ray * @param {Point} pt * @returns [Segment,Ray] */ split(pt) { if (!this.contains(pt)) return []; if (this.pt.equalTo(pt)) { return [this] } return [ new Flatten.Segment(this.pt, pt), new Flatten.Ray(pt, this.norm) ] } /** * Returns array of intersection points between ray and segment or arc * @param {Segment|Arc} - Shape to intersect with ray * @returns {Array} array of intersection points */ intersect(shape) { if (shape instanceof Flatten.Segment) { return this.intersectRay2Segment(this, shape); } if (shape instanceof Flatten.Arc) { return this.intersectRay2Arc(this, shape); } } intersectRay2Segment(ray, segment) { let ip = []; if (ray.box.not_intersect(segment.box)) { return ip; } let line = new Flatten.Line(ray.start, ray.norm); let ip_tmp = line.intersect(segment); for (let pt of ip_tmp) { // if (Flatten.Utils.GE(pt.x, ray.start.x)) { if (ray.contains(pt)) { ip.push(pt); } } /* If there were two intersection points between line and ray, and now there is exactly one left, it means ray starts between these points and there is another intersection point - start of the ray */ if (ip_tmp.length == 2 && ip.length == 1 && ray.start.on(line)) { ip.push(ray.start); } return ip; } intersectRay2Arc(ray, arc) { let ip = []; if (ray.box.not_intersect(arc.box)) { return ip; } let line = new Flatten.Line(ray.start, ray.norm); let ip_tmp = line.intersect(arc); for (let pt of ip_tmp) { // if (Flatten.Utils.GE(pt.x, ray.start.x)) { if (ray.contains(pt)) { ip.push(pt); } } return ip; } /** * Return string to draw svg segment representing ray inside given box * @param {Box} box Box representing drawing area * @param {Object} attrs - an object with attributes of svg segment element */ svg(box, attrs = {}) { let line = new Flatten.Line(this.pt, this.norm); let ip = Intersection.intersectLine2Box(line, box); ip = ip.filter( pt => this.contains(pt) ); if (ip.length === 0 || ip.length === 2) return ""; let segment = new Flatten.Segment(this.pt, ip[0]); return segment.svg(attrs); } }; Flatten.Ray = Ray; export const ray = (...args) => new Flatten.Ray(...args); Flatten.ray = ray;