import {Rectangle} from './rectangle' export class Point { x: number; y: number; } export class LineSegment { constructor(public x1: number, public y1: number, public x2: number, public y2: number) { } } export class PolyPoint extends Point { polyIndex: number; } /** tests if a point is Left|On|Right of an infinite line. * @param points P0, P1, and P2 * @return >0 for P2 left of the line through P0 and P1 * =0 for P2 on the line * <0 for P2 right of the line */ export function isLeft(P0: Point, P1: Point, P2: Point): number { return (P1.x - P0.x) * (P2.y - P0.y) - (P2.x - P0.x) * (P1.y - P0.y); } function above(p: Point, vi: Point, vj: Point): boolean { return isLeft(p, vi, vj) > 0; } function below(p: Point, vi: Point, vj: Point): boolean { return isLeft(p, vi, vj) < 0; } /** * returns the convex hull of a set of points using Andrew's monotone chain algorithm * see: http://geomalgorithms.com/a10-_hull-1.html#Monotone%20Chain * @param S array of points * @return the convex hull as an array of points */ export function ConvexHull(S: Point[]): Point[] { var P = S.slice(0).sort((a, b) => a.x !== b.x ? b.x - a.x : b.y - a.y); var n = S.length, i; var minmin = 0; var xmin = P[0].x; for (i = 1; i < n; ++i) { if (P[i].x !== xmin) break; } var minmax = i - 1; var H: Point[] = []; H.push(P[minmin]); // push minmin point onto stack if (minmax === n - 1) { // degenerate case: all x-coords == xmin if (P[minmax].y !== P[minmin].y) // a nontrivial segment H.push(P[minmax]); } else { // Get the indices of points with max x-coord and min|max y-coord var maxmin, maxmax = n - 1; var xmax = P[n - 1].x; for (i = n - 2; i >= 0; i--) if (P[i].x !== xmax) break; maxmin = i + 1; // Compute the lower hull on the stack H i = minmax; while (++i <= maxmin) { // the lower line joins P[minmin] with P[maxmin] if (isLeft(P[minmin], P[maxmin], P[i]) >= 0 && i < maxmin) continue; // ignore P[i] above or on the lower line while (H.length > 1) // there are at least 2 points on the stack { // test if P[i] is left of the line at the stack top if (isLeft(H[H.length - 2], H[H.length - 1], P[i]) > 0) break; // P[i] is a new hull vertex else H.length -= 1; // pop top point off stack } if (i != minmin) H.push(P[i]); } // Next, compute the upper hull on the stack H above the bottom hull if (maxmax != maxmin) // if distinct xmax points H.push(P[maxmax]); // push maxmax point onto stack var bot = H.length; // the bottom point of the upper hull stack i = maxmin; while (--i >= minmax) { // the upper line joins P[maxmax] with P[minmax] if (isLeft(P[maxmax], P[minmax], P[i]) >= 0 && i > minmax) continue; // ignore P[i] below or on the upper line while (H.length > bot) // at least 2 points on the upper stack { // test if P[i] is left of the line at the stack top if (isLeft(H[H.length - 2], H[H.length - 1], P[i]) > 0) break; // P[i] is a new hull vertex else H.length -= 1; // pop top point off stack } if (i != minmin) H.push(P[i]); // push P[i] onto stack } } return H; } // apply f to the points in P in clockwise order around the point p export function clockwiseRadialSweep(p: Point, P: Point[], f: (p: Point) => void) { P.slice(0).sort( (a, b) => Math.atan2(a.y - p.y, a.x - p.x) - Math.atan2(b.y - p.y, b.x - p.x) ).forEach(f); } function nextPolyPoint(p: PolyPoint, ps: PolyPoint[]): PolyPoint { if (p.polyIndex === ps.length - 1) return ps[0]; return ps[p.polyIndex + 1]; } function prevPolyPoint(p: PolyPoint, ps: PolyPoint[]): PolyPoint { if (p.polyIndex === 0) return ps[ps.length - 1]; return ps[p.polyIndex - 1]; } // tangent_PointPolyC(): fast binary search for tangents to a convex polygon // Input: P = a 2D point (exterior to the polygon) // n = number of polygon vertices // V = array of vertices for a 2D convex polygon // Output: rtan = index of rightmost tangent point V[rtan] // ltan = index of leftmost tangent point V[ltan] function tangent_PointPolyC(P: Point, V: Point[]): { rtan: number; ltan: number } { // Rtangent_PointPolyC and Ltangent_PointPolyC require polygon to be // "closed" with the first vertex duplicated at end, so V[n-1] = V[0]. let Vclosed = V.slice(0); // Copy V Vclosed.push(V[0]); // Add V[0] at end return { rtan: Rtangent_PointPolyC(P, Vclosed), ltan: Ltangent_PointPolyC(P, Vclosed) }; } // Rtangent_PointPolyC(): binary search for convex polygon right tangent // Input: P = a 2D point (exterior to the polygon) // n = number of polygon vertices // V = array of vertices for a 2D convex polygon with first // vertex duplicated as last, so V[n-1] = V[0] // Return: index "i" of rightmost tangent point V[i] function Rtangent_PointPolyC(P: Point, V: Point[]): number { var n = V.length - 1; // use binary search for large convex polygons var a: number, b: number, c: number; // indices for edge chain endpoints var upA: boolean, dnC: boolean; // test for up direction of edges a and c // rightmost tangent = maximum for the isLeft() ordering // test if V[0] is a local maximum if (below(P, V[1], V[0]) && !above(P, V[n - 1], V[0])) return 0; // V[0] is the maximum tangent point for (a = 0, b = n; ;) { // start chain = [0,n] with V[n]=V[0] if (b - a === 1) if (above(P, V[a], V[b])) return a; else return b; c = Math.floor((a + b) / 2); // midpoint of [a,b], and 0 number, t2: (a: Point, b: Point[]) => number, cmp1: (a: Point, b: Point, c: Point) => boolean, cmp2: (a: Point, b: Point, c: Point) => boolean): { t1: number; t2: number } { var ix1: number, ix2: number; // search indices for polygons 1 and 2 // first get the initial vertex on each polygon ix1 = t1(W[0], V); // right tangent from W[0] to V ix2 = t2(V[ix1], W); // left tangent from V[ix1] to W // ping-pong linear search until it stabilizes var done = false; // flag when done while (!done) { done = true; // assume done until... while (true) { if (ix1 === V.length - 1) ix1 = 0; if (cmp1(W[ix2], V[ix1], V[ix1 + 1])) break; ++ix1; // get Rtangent from W[ix2] to V } while (true) { if (ix2 === 0) ix2 = W.length - 1; if (cmp2(V[ix1], W[ix2], W[ix2 - 1])) break; --ix2; // get Ltangent from V[ix1] to W done = false; // not done if had to adjust this } } return { t1: ix1, t2: ix2 }; } export function LRtangent_PolyPolyC(V: Point[], W: Point[]): { t1: number; t2: number } { var rl = RLtangent_PolyPolyC(W, V); return { t1: rl.t2, t2: rl.t1 }; } export function RLtangent_PolyPolyC(V: Point[], W: Point[]): { t1: number; t2: number } { return tangent_PolyPolyC(V, W, Rtangent_PointPolyC, Ltangent_PointPolyC, above, below); } export function LLtangent_PolyPolyC(V: Point[], W: Point[]): { t1: number; t2: number } { return tangent_PolyPolyC(V, W, Ltangent_PointPolyC, Ltangent_PointPolyC, below, below); } export function RRtangent_PolyPolyC(V: Point[], W: Point[]): { t1: number; t2: number } { return tangent_PolyPolyC(V, W, Rtangent_PointPolyC, Rtangent_PointPolyC, above, above); } export class BiTangent { constructor(public t1: number, public t2: number) { } } export class BiTangents { rl: BiTangent; lr: BiTangent; ll: BiTangent; rr: BiTangent; } export class TVGPoint extends Point { vv: VisibilityVertex; } export class VisibilityVertex { constructor( public id: number, public polyid: number, public polyvertid: number, public p: TVGPoint) { p.vv = this; } } export class VisibilityEdge { constructor( public source: VisibilityVertex, public target: VisibilityVertex) { } length(): number { var dx = this.source.p.x - this.target.p.x; var dy = this.source.p.y - this.target.p.y; return Math.sqrt(dx * dx + dy * dy); } } export class TangentVisibilityGraph { V: VisibilityVertex[] = []; E: VisibilityEdge[] = []; constructor(public P: TVGPoint[][], g0?: { V: VisibilityVertex[]; E: VisibilityEdge[] }) { if (!g0) { var n = P.length; // For each node... for (var i = 0; i < n; i++) { var p = P[i]; // For each node vertex. for (var j = 0; j < p.length; ++j) { var pj = p[j], vv = new VisibilityVertex(this.V.length, i, j, pj); this.V.push(vv); // For the every iteration but the first, generate an // edge from the previous visibility vertex to the // current one. if (j > 0) this.E.push(new VisibilityEdge(p[j - 1].vv, vv)); } // Add a visibility edge from the first vertex to the last. if (p.length > 1) this.E.push(new VisibilityEdge(p[0].vv, p[p.length - 1].vv)); } for (var i = 0; i < n - 1; i++) { var Pi = P[i]; for (var j = i + 1; j < n; j++) { var Pj = P[j], t = tangents(Pi, Pj); for (var q in t) { var c = t[q], source = Pi[c.t1], target = Pj[c.t2]; this.addEdgeIfVisible(source, target, i, j); } } } } else { this.V = g0.V.slice(0); this.E = g0.E.slice(0); } } addEdgeIfVisible(u: TVGPoint, v: TVGPoint, i1: number, i2: number) { if (!this.intersectsPolys(new LineSegment(u.x, u.y, v.x, v.y), i1, i2)) { this.E.push(new VisibilityEdge(u.vv, v.vv)); } } addPoint(p: TVGPoint, i1: number): VisibilityVertex { var n = this.P.length; this.V.push(new VisibilityVertex(this.V.length, n, 0, p)); for (var i = 0; i < n; ++i) { if (i === i1) continue; var poly = this.P[i], t = tangent_PointPolyC(p, poly); this.addEdgeIfVisible(p, poly[t.ltan], i1, i); this.addEdgeIfVisible(p, poly[t.rtan], i1, i); } return p.vv; } private intersectsPolys(l: LineSegment, i1: number, i2: number): boolean { for (var i = 0, n = this.P.length; i < n; ++i) { if (i != i1 && i != i2 && intersects(l, this.P[i]).length > 0) { return true; } } return false; } } function intersects(l: LineSegment, P: Point[]) { var ints = []; for (var i = 1, n = P.length; i < n; ++i) { var int = Rectangle.lineIntersection( l.x1, l.y1, l.x2, l.y2, P[i - 1].x, P[i - 1].y, P[i].x, P[i].y ); if (int) ints.push(int); } return ints; } export function tangents(V: Point[], W: Point[]): BiTangents { var m = V.length - 1, n = W.length - 1; var bt = new BiTangents(); for (var i = 0; i < m; ++i) { for (var j = 0; j < n; ++j) { var v1 = V[i == 0 ? m - 1 : i - 1]; var v2 = V[i]; var v3 = V[i + 1]; var w1 = W[j == 0 ? n - 1 : j - 1]; var w2 = W[j]; var w3 = W[j + 1]; var v1v2w2 = isLeft(v1, v2, w2); var v2w1w2 = isLeft(v2, w1, w2); var v2w2w3 = isLeft(v2, w2, w3); var w1w2v2 = isLeft(w1, w2, v2); var w2v1v2 = isLeft(w2, v1, v2); var w2v2v3 = isLeft(w2, v2, v3); if (v1v2w2 >= 0 && v2w1w2 >= 0 && v2w2w3 < 0 && w1w2v2 >= 0 && w2v1v2 >= 0 && w2v2v3 < 0) { bt.ll = new BiTangent(i, j); } else if (v1v2w2 <= 0 && v2w1w2 <= 0 && v2w2w3 > 0 && w1w2v2 <= 0 && w2v1v2 <= 0 && w2v2v3 > 0) { bt.rr = new BiTangent(i, j); } else if (v1v2w2 <= 0 && v2w1w2 > 0 && v2w2w3 <= 0 && w1w2v2 >= 0 && w2v1v2 < 0 && w2v2v3 >= 0) { bt.rl = new BiTangent(i, j); } else if (v1v2w2 >= 0 && v2w1w2 < 0 && v2w2w3 >= 0 && w1w2v2 <= 0 && w2v1v2 > 0 && w2v2v3 <= 0) { bt.lr = new BiTangent(i, j); } } } return bt; } function isPointInsidePoly(p: Point, poly: Point[]): boolean { for (var i = 1, n = poly.length; i < n; ++i) if (below(poly[i - 1], poly[i], p)) return false; return true; } function isAnyPInQ(p: Point[], q: Point[]): boolean { return !p.every(v => !isPointInsidePoly(v, q)); } export function polysOverlap(p: Point[], q: Point[]): boolean { if (isAnyPInQ(p, q)) return true; if (isAnyPInQ(q, p)) return true; for (var i = 1, n = p.length; i < n; ++i) { var v = p[i], u = p[i - 1]; if (intersects(new LineSegment(u.x, u.y, v.x, v.y), q).length > 0) return true; } return false; }