romgrk-2d-geometry
Version:
Javascript library for 2d geometry
636 lines (515 loc) • 17 kB
text/typescript
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))
}