UNPKG

romgrk-2d-geometry

Version:

Javascript library for 2d geometry

636 lines (515 loc) 17 kB
import * as Utils from '../utils/utils' import { Edge, Circle, Point, Polygon, Line, Segment, Box, Arc, Ray, Shape, Vector, } from '../classes' export function intersectLine2Line(line1: Line, line2: Line): Point[] { let ip = []; let [A1, B1, C1] = line1.standard; let [A2, B2, C2] = line2.standard; /* Cramer's rule */ let det = A1 * B2 - B1 * A2; let detX = C1 * B2 - B1 * C2; let detY = A1 * C2 - C1 * A2; if (!Utils.EQ_0(det)) { let x, y; if (B1 === 0) { // vertical line x = C1/A1, where A1 == +1 or -1 x = C1/A1; y = detY / det; } else if (B2 === 0) { // vertical line x = C2/A2, where A2 = +1 or -1 x = C2/A2; y = detY / det; } else if (A1 === 0) { // horizontal line y = C1/B1, where B1 = +1 or -1 x = detX / det; y = C1/B1; } else if (A2 === 0) { // horizontal line y = C2/B2, where B2 = +1 or -1 x = detX / det; y = C2/B2; } else { x = detX / det; y = detY / det; } ip.push(new Point(x, y)); } return ip; } export function intersectLine2Circle(line: Line, circle: Circle): Point[] { let ip = []; let prj = circle.pc.projectionOn(line); // projection of circle center on a line let dist = circle.pc.distanceTo(prj)[0]; // distance from circle center to projection if (Utils.EQ(dist, circle.r)) { // line tangent to circle - return single intersection point ip.push(prj); } else if (Utils.LT(dist, circle.r)) { // return two intersection points let delta = Math.sqrt(circle.r * circle.r - dist * dist); let v_trans, pt; v_trans = line.norm.rotate90CCW().multiply(delta); pt = prj.translate(v_trans); ip.push(pt); v_trans = line.norm.rotate90CW().multiply(delta); pt = prj.translate(v_trans); ip.push(pt); } return ip; } export function intersectLine2Box(line: Line, box: Box): Point[] { let ips = []; for (let seg of box.toSegments()) { let ips_tmp = intersectSegment2Line(seg, line); for (let pt of ips_tmp) { if (!ptInIntPoints(pt, ips)) { ips.push(pt); } } } return ips; } export function intersectLine2Arc(line: Line, arc: Arc): Point[] { let ip = []; if (intersectLine2Box(line, arc.box).length === 0) { return ip; } let circle = new Circle(arc.pc, arc.r); let ip_tmp = intersectLine2Circle(line, circle); for (let pt of ip_tmp) { if (pt.on(arc)) { ip.push(pt); } } return ip; } export function intersectSegment2Line(seg: Segment, line: Line): Point[] { let ip = []; // Boundary cases if (seg.start.on(line)) { ip.push(seg.start); } // If both ends lay on line, return two intersection points if (seg.end.on(line) && !seg.isZeroLength()) { ip.push(seg.end); } if (ip.length > 0) { return ip; // done, intersection found } // If zero-length segment and nothing found, return no intersections if (seg.isZeroLength()) { return ip; } // Not a boundary case, check if both points are on the same side and // hence there is no intersection if (seg.start.leftTo(line) && seg.end.leftTo(line) || !seg.start.leftTo(line) && !seg.end.leftTo(line)) { return ip; } // Calculate intersection between lines let line1 = new Line(seg.start, seg.end); return intersectLine2Line(line1, line); } export function intersectSegment2Segment(seg1: Segment, seg2: Segment): Point[] { let ip = []; // quick reject if (seg1.box.notIntersect(seg2.box)) { return ip; } // Special case of seg1 zero length if (seg1.isZeroLength()) { if (seg1.start.on(seg2)) { ip.push(seg1.start); } return ip; } // Special case of seg2 zero length if (seg2.isZeroLength()) { if (seg2.start.on(seg1)) { ip.push(seg2.start); } return ip; } // Neither seg1 nor seg2 is zero length let line1 = new Line(seg1.start, seg1.end); let line2 = new Line(seg2.start, seg2.end); // Check overlapping between segments in case of incidence // If segments touching, add one point. If overlapping, add two points if (line1.incidentTo(line2)) { if (seg1.start.on(seg2)) { ip.push(seg1.start); } if (seg1.end.on(seg2)) { ip.push(seg1.end); } if (seg2.start.on(seg1) && !seg2.start.equalTo(seg1.start) && !seg2.start.equalTo(seg1.end)) { ip.push(seg2.start); } if (seg2.end.on(seg1) && !seg2.end.equalTo(seg1.start) && !seg2.end.equalTo(seg1.end)) { ip.push(seg2.end); } } else { /* not incident - parallel or intersect */ // Calculate intersection between lines let new_ip = intersectLine2Line(line1, line2); if (new_ip.length > 0) { if (isPointInSegmentBox(new_ip[0], seg1) && isPointInSegmentBox(new_ip[0], seg2)) { ip.push(new_ip[0]); } } } return ip; } function isPointInSegmentBox(point: Point, segment: Segment) { const box = segment.box; return Utils.LE(point.x, box.xmax) && Utils.GE(point.x, box.xmin) && Utils.LE(point.y, box.ymax) && Utils.GE(point.y, box.ymin) } export function intersectSegment2Circle(segment: Segment, circle: Circle): Point[] { let ips = []; if (segment.box.notIntersect(circle.box)) { return ips; } // Special case of zero length segment if (segment.isZeroLength()) { let [dist, _] = segment.start.distanceTo(circle.pc); if (Utils.EQ(dist, circle.r)) { ips.push(segment.start); } return ips; } // Non zero-length segment let line = new Line(segment.start, segment.end); let ips_tmp = intersectLine2Circle(line, circle); for (let ip of ips_tmp) { if (ip.on(segment)) { ips.push(ip); } } return ips; } export function intersectSegment2Arc(segment: Segment, arc): Point[] { let ip = []; if (segment.box.notIntersect(arc.box)) { return ip; } // Special case of zero-length segment if (segment.isZeroLength()) { if (segment.start.on(arc)) { ip.push(segment.start); } return ip; } // Non-zero length segment let line = new Line(segment.start, segment.end); let circle = new Circle(arc.pc, arc.r); let ip_tmp = intersectLine2Circle(line, circle); for (let pt of ip_tmp) { if (pt.on(segment) && pt.on(arc)) { ip.push(pt); } } return ip; } export function intersectSegment2Box(segment: Segment, box: Box): Point[] { let ips = []; for (let seg of box.toSegments()) { let ips_tmp = intersectSegment2Segment(seg, segment); for (let ip of ips_tmp) { ips.push(ip); } } return ips; } export function intersectCircle2Circle(circle1: Circle, circle2: Circle): Point[] { let ip = []; if (circle1.box.notIntersect(circle2.box)) { return ip; } let vec = new Vector(circle1.pc, circle2.pc); let r1 = circle1.r; let r2 = circle2.r; // Degenerated circle if (Utils.EQ_0(r1) || Utils.EQ_0(r2)) return ip; // In case of equal circles return one leftmost point if (Utils.EQ_0(vec.x) && Utils.EQ_0(vec.y) && Utils.EQ(r1, r2)) { ip.push(circle1.pc.translate(-r1, 0)); return ip; } let dist = circle1.pc.distanceTo(circle2.pc)[0]; if (Utils.GT(dist, r1 + r2)) // circles too far, no intersections return ip; if (Utils.LT(dist, Math.abs(r1 - r2))) // one circle is contained within another, no intersections return ip; // Normalize vector. vec.x /= dist; vec.y /= dist; let pt; // Case of touching from outside or from inside - single intersection point // TODO: check this specifically not sure if correct if (Utils.EQ(dist, r1 + r2) || Utils.EQ(dist, Math.abs(r1 - r2))) { pt = circle1.pc.translate(r1 * vec.x, r1 * vec.y); ip.push(pt); return ip; } // Case of two intersection points // Distance from first center to center of common chord: // a = (r1^2 - r2^2 + d^2) / 2d // Separate for better accuracy let a = (r1 * r1) / (2 * dist) - (r2 * r2) / (2 * dist) + dist / 2; let mid_pt = circle1.pc.translate(a * vec.x, a * vec.y); let h = Math.sqrt(r1 * r1 - a * a); // let norm; // norm = vec.rotate90CCW().multiply(h); pt = mid_pt.translate(vec.rotate90CCW().multiply(h)); ip.push(pt); // norm = vec.rotate90CW(); pt = mid_pt.translate(vec.rotate90CW().multiply(h)); ip.push(pt); return ip; } export function intersectCircle2Box(circle: Circle, box: Box): Point[] { let ips = []; for (let seg of box.toSegments()) { let ips_tmp = intersectSegment2Circle(seg, circle); for (let ip of ips_tmp) { ips.push(ip); } } return ips; } export function intersectArc2Arc(arc1: Arc, arc2: Arc): Point[] { let ip = []; if (arc1.box.notIntersect(arc2.box)) { return ip; } // Special case: overlapping arcs // May return up to 4 intersection points if (arc1.pc.equalTo(arc2.pc) && Utils.EQ(arc1.r, arc2.r)) { let pt; pt = arc1.start; if (pt.on(arc2)) ip.push(pt); pt = arc1.end; if (pt.on(arc2)) ip.push(pt); pt = arc2.start; if (pt.on(arc1)) ip.push(pt); pt = arc2.end; if (pt.on(arc1)) ip.push(pt); return ip; } // Common case let circle1 = new Circle(arc1.pc, arc1.r); let circle2 = new Circle(arc2.pc, arc2.r); let ip_tmp = circle1.intersect(circle2); for (let pt of ip_tmp) { if (pt.on(arc1) && pt.on(arc2)) { ip.push(pt); } } return ip; } export function intersectArc2Circle(arc: Arc, circle: Circle): Point[] { let ip = []; if (arc.box.notIntersect(circle.box)) { return ip; } // Case when arc center incident to circle center // Return arc's end points as 2 intersection points if (circle.pc.equalTo(arc.pc) && Utils.EQ(circle.r, arc.r)) { ip.push(arc.start); ip.push(arc.end); return ip; } // Common case let circle1 = circle; let circle2 = new Circle(arc.pc, arc.r); let ip_tmp = intersectCircle2Circle(circle1, circle2); for (let pt of ip_tmp) { if (pt.on(arc)) { ip.push(pt); } } return ip; } export function intersectArc2Box(arc: Arc, box: Box): Point[] { let ips = []; for (let seg of box.toSegments()) { let ips_tmp = intersectSegment2Arc(seg, arc); for (let ip of ips_tmp) { ips.push(ip); } } return ips; } export function intersectEdge2Segment(edge, segment: Segment): Point[] { return edge.isSegment() ? intersectSegment2Segment(edge.shape, segment) : intersectSegment2Arc(segment, edge.shape); } export function intersectEdge2Arc(edge, arc): Point[] { return edge.isSegment() ? intersectSegment2Arc(edge.shape, arc) : intersectArc2Arc(edge.shape, arc); } export function intersectEdge2Line(edge, line): Point[] { return edge.isSegment() ? intersectSegment2Line(edge.shape, line) : intersectLine2Arc(line, edge.shape); } export function intersectEdge2Circle(edge, circle: Circle): Point[] { return edge.isSegment() ? intersectSegment2Circle(edge.shape, circle) : intersectArc2Circle(edge.shape, circle); } export function intersectSegment2Polygon(segment: Segment, polygon: Polygon): Point[] { let ip = []; for (let edge of polygon.edges) { for (let pt of intersectEdge2Segment(edge, segment)) { ip.push(pt); } } return ip; } export function intersectArc2Polygon(arc: Arc, polygon: Polygon): Point[] { let ip = []; for (let edge of polygon.edges) { for (let pt of intersectEdge2Arc(edge, arc)) { ip.push(pt); } } return ip; } export function intersectLine2Polygon(line, polygon: Polygon): Point[] { let ip = []; if (polygon.isEmpty()) { return ip; } for (let edge of polygon.edges) { for (let pt of intersectEdge2Line(edge, line)) { if (!ptInIntPoints(pt, ip)) { ip.push(pt); } } } return line.sortPoints(ip); } export function intersectCircle2Polygon(circle: Circle, polygon: Polygon): Point[] { let ip = []; if (polygon.isEmpty()) { return ip; } for (let edge of polygon.edges) { for (let pt of intersectEdge2Circle(edge, circle)) { ip.push(pt); } } return ip; } export function intersectEdge2Edge(edge1, edge2): Point[] { const shape1 = edge1.shape; const shape2 = edge2.shape; return edge1.isSegment() ? (edge2.isSegment() ? intersectSegment2Segment(shape1, shape2) : intersectSegment2Arc(shape1, shape2)) : (edge2.isSegment() ? intersectSegment2Arc(shape2, shape1) : intersectArc2Arc(shape1, shape2)); } export function intersectEdge2Polygon(edge, polygon: Polygon): Point[] { let ip = []; if (polygon.isEmpty() || edge.shape.box.notIntersect(polygon.box)) { return ip; } let resp_edges = polygon.edges.search(edge.shape.box); for (let resp_edge of resp_edges) { for (let pt of intersectEdge2Edge(edge, resp_edge)) { ip.push(pt); } } return ip; } export function intersectMultiline2Polygon(multiline, polygon: Polygon): Point[] { let ip = []; if (polygon.isEmpty() || multiline.size === 0) { return ip; } for (let edge of multiline) { let ip_edge = intersectEdge2Polygon(edge, polygon); let ip_sorted = edge.shape.sortPoints(ip_edge); // TODO: support arc edge ip = [...ip, ...ip_sorted]; } return ip; } export function intersectPolygon2Polygon(polygon1, polygon2): Point[] { let ip = []; if (polygon1.isEmpty() || polygon2.isEmpty()) { return ip; } if (polygon1.box.notIntersect(polygon2.box)) { return ip; } for (let edge1 of polygon1.edges) { for (let pt of intersectEdge2Polygon(edge1, polygon2)) { ip.push(pt); } } return ip; } export function intersectBox2Box(box1: Box, box2: Box): Point[] { let ip = []; for (let segment1 of box1.toSegments()) { for (let segment2 of box2.toSegments()) { for (let pt of intersectSegment2Segment(segment1, segment2)) { ip.push(pt) } } } return ip; } export function intersectShape2Polygon(shape: Shape<any>, polygon: Polygon): Point[] { if (shape instanceof Line) { return intersectLine2Polygon(shape, polygon); } else if (shape instanceof Segment) { return intersectSegment2Polygon(shape, polygon); } else if (shape instanceof Arc) { return intersectArc2Polygon(shape, polygon); } else { return []; } } function ptInIntPoints(new_pt, ip) { return ip.some( pt => pt.equalTo(new_pt) ) } function createLineFromRay(ray) { return new Line(ray.start, ray.norm) } export function intersectRay2Segment(ray: Ray, segment: Segment): Point[] { return intersectSegment2Line(segment, createLineFromRay(ray)) .filter(pt => ray.contains(pt)); } export function intersectRay2Arc(ray: Ray, arc): Point[] { return intersectLine2Arc(createLineFromRay(ray), arc) .filter(pt => ray.contains(pt)) } export function intersectRay2Circle(ray: Ray, circle: Circle): Point[] { return intersectLine2Circle(createLineFromRay(ray), circle) .filter(pt => ray.contains(pt)) } export function intersectRay2Box(ray: Ray, box: Box): Point[] { return intersectLine2Box(createLineFromRay(ray), box) .filter(pt => ray.contains(pt)) } export function intersectRay2Line(ray: Ray, line): Point[] { return intersectLine2Line(createLineFromRay(ray), line) .filter(pt => ray.contains(pt)) } export function intersectRay2Ray(ray1: Ray, ray2: Ray): Point[] { return intersectLine2Line(createLineFromRay(ray1), createLineFromRay(ray2)) .filter(pt => ray1.contains(pt)) .filter(pt => ray2.contains(pt)) } export function intersectRay2Polygon(ray: Ray, polygon: Polygon): Point[] { return intersectLine2Polygon(createLineFromRay(ray), polygon) .filter(pt => ray.contains(pt)) }