UNPKG

poly2tri

Version:

A 2D constrained Delaunay triangulation library

1,740 lines (1,526 loc) 80.7 kB
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.poly2tri = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ module.exports={"version": "1.5.0"} },{}],2:[function(require,module,exports){ /* * Poly2Tri Copyright (c) 2009-2014, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * poly2tri.js (JavaScript port) (c) 2009-2014, Poly2Tri Contributors * https://github.com/r3mi/poly2tri.js * * All rights reserved. * * Distributed under the 3-clause BSD License, see LICENSE.txt */ /* jshint maxcomplexity:11 */ "use strict"; /* * Note * ==== * the structure of this JavaScript version of poly2tri intentionally follows * as closely as possible the structure of the reference C++ version, to make it * easier to keep the 2 versions in sync. */ // -------------------------------------------------------------------------Node /** * Advancing front node * @constructor * @private * @struct * @param {!XY} p - Point * @param {Triangle=} t triangle (optional) */ var Node = function(p, t) { /** @type {XY} */ this.point = p; /** @type {Triangle|null} */ this.triangle = t || null; /** @type {Node|null} */ this.next = null; /** @type {Node|null} */ this.prev = null; /** @type {number} */ this.value = p.x; }; // ---------------------------------------------------------------AdvancingFront /** * @constructor * @private * @struct * @param {Node} head * @param {Node} tail */ var AdvancingFront = function(head, tail) { /** @type {Node} */ this.head_ = head; /** @type {Node} */ this.tail_ = tail; /** @type {Node} */ this.search_node_ = head; }; /** @return {Node} */ AdvancingFront.prototype.head = function() { return this.head_; }; /** @param {Node} node */ AdvancingFront.prototype.setHead = function(node) { this.head_ = node; }; /** @return {Node} */ AdvancingFront.prototype.tail = function() { return this.tail_; }; /** @param {Node} node */ AdvancingFront.prototype.setTail = function(node) { this.tail_ = node; }; /** @return {Node} */ AdvancingFront.prototype.search = function() { return this.search_node_; }; /** @param {Node} node */ AdvancingFront.prototype.setSearch = function(node) { this.search_node_ = node; }; /** @return {Node} */ AdvancingFront.prototype.findSearchNode = function(/*x*/) { // TODO: implement BST index return this.search_node_; }; /** * @param {number} x value * @return {Node} */ AdvancingFront.prototype.locateNode = function(x) { var node = this.search_node_; /* jshint boss:true */ if (x < node.value) { while (node = node.prev) { if (x >= node.value) { this.search_node_ = node; return node; } } } else { while (node = node.next) { if (x < node.value) { this.search_node_ = node.prev; return node.prev; } } } return null; }; /** * @param {!XY} point - Point * @return {Node} */ AdvancingFront.prototype.locatePoint = function(point) { var px = point.x; var node = this.findSearchNode(px); var nx = node.point.x; if (px === nx) { // Here we are comparing point references, not values if (point !== node.point) { // We might have two nodes with same x value for a short time if (point === node.prev.point) { node = node.prev; } else if (point === node.next.point) { node = node.next; } else { throw new Error('poly2tri Invalid AdvancingFront.locatePoint() call'); } } } else if (px < nx) { /* jshint boss:true */ while (node = node.prev) { if (point === node.point) { break; } } } else { while (node = node.next) { if (point === node.point) { break; } } } if (node) { this.search_node_ = node; } return node; }; // ----------------------------------------------------------------------Exports module.exports = AdvancingFront; module.exports.Node = Node; },{}],3:[function(require,module,exports){ /* * Poly2Tri Copyright (c) 2009-2014, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * poly2tri.js (JavaScript port) (c) 2009-2014, Poly2Tri Contributors * https://github.com/r3mi/poly2tri.js * * All rights reserved. * * Distributed under the 3-clause BSD License, see LICENSE.txt */ "use strict"; /* * Function added in the JavaScript version (was not present in the c++ version) */ /** * assert and throw an exception. * * @private * @param {boolean} condition the condition which is asserted * @param {string} message the message which is display is condition is falsy */ function assert(condition, message) { if (!condition) { throw new Error(message || "Assert Failed"); } } module.exports = assert; },{}],4:[function(require,module,exports){ /* * Poly2Tri Copyright (c) 2009-2014, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * poly2tri.js (JavaScript port) (c) 2009-2014, Poly2Tri Contributors * https://github.com/r3mi/poly2tri.js * * All rights reserved. * * Distributed under the 3-clause BSD License, see LICENSE.txt */ "use strict"; /* * Note * ==== * the structure of this JavaScript version of poly2tri intentionally follows * as closely as possible the structure of the reference C++ version, to make it * easier to keep the 2 versions in sync. */ var xy = require('./xy'); // ------------------------------------------------------------------------Point /** * Construct a point * @example * var point = new poly2tri.Point(150, 150); * @public * @constructor * @struct * @param {number=} x coordinate (0 if undefined) * @param {number=} y coordinate (0 if undefined) */ var Point = function(x, y) { /** * @type {number} * @expose */ this.x = +x || 0; /** * @type {number} * @expose */ this.y = +y || 0; // All extra fields added to Point are prefixed with _p2t_ // to avoid collisions if custom Point class is used. /** * The edges this point constitutes an upper ending point * @private * @type {Array.<Edge>} */ this._p2t_edge_list = null; }; /** * For pretty printing * @example * "p=" + new poly2tri.Point(5,42) * // → "p=(5;42)" * @returns {string} <code>"(x;y)"</code> */ Point.prototype.toString = function() { return xy.toStringBase(this); }; /** * JSON output, only coordinates * @example * JSON.stringify(new poly2tri.Point(1,2)) * // → '{"x":1,"y":2}' */ Point.prototype.toJSON = function() { return { x: this.x, y: this.y }; }; /** * Creates a copy of this Point object. * @return {Point} new cloned point */ Point.prototype.clone = function() { return new Point(this.x, this.y); }; /** * Set this Point instance to the origo. <code>(0; 0)</code> * @return {Point} this (for chaining) */ Point.prototype.set_zero = function() { this.x = 0.0; this.y = 0.0; return this; // for chaining }; /** * Set the coordinates of this instance. * @param {number} x coordinate * @param {number} y coordinate * @return {Point} this (for chaining) */ Point.prototype.set = function(x, y) { this.x = +x || 0; this.y = +y || 0; return this; // for chaining }; /** * Negate this Point instance. (component-wise) * @return {Point} this (for chaining) */ Point.prototype.negate = function() { this.x = -this.x; this.y = -this.y; return this; // for chaining }; /** * Add another Point object to this instance. (component-wise) * @param {!Point} n - Point object. * @return {Point} this (for chaining) */ Point.prototype.add = function(n) { this.x += n.x; this.y += n.y; return this; // for chaining }; /** * Subtract this Point instance with another point given. (component-wise) * @param {!Point} n - Point object. * @return {Point} this (for chaining) */ Point.prototype.sub = function(n) { this.x -= n.x; this.y -= n.y; return this; // for chaining }; /** * Multiply this Point instance by a scalar. (component-wise) * @param {number} s scalar. * @return {Point} this (for chaining) */ Point.prototype.mul = function(s) { this.x *= s; this.y *= s; return this; // for chaining }; /** * Return the distance of this Point instance from the origo. * @return {number} distance */ Point.prototype.length = function() { return Math.sqrt(this.x * this.x + this.y * this.y); }; /** * Normalize this Point instance (as a vector). * @return {number} The original distance of this instance from the origo. */ Point.prototype.normalize = function() { var len = this.length(); this.x /= len; this.y /= len; return len; }; /** * Test this Point object with another for equality. * @param {!XY} p - any "Point like" object with {x,y} * @return {boolean} <code>true</code> if same x and y coordinates, <code>false</code> otherwise. */ Point.prototype.equals = function(p) { return this.x === p.x && this.y === p.y; }; // -----------------------------------------------------Point ("static" methods) /** * Negate a point component-wise and return the result as a new Point object. * @param {!XY} p - any "Point like" object with {x,y} * @return {Point} the resulting Point object. */ Point.negate = function(p) { return new Point(-p.x, -p.y); }; /** * Add two points component-wise and return the result as a new Point object. * @param {!XY} a - any "Point like" object with {x,y} * @param {!XY} b - any "Point like" object with {x,y} * @return {Point} the resulting Point object. */ Point.add = function(a, b) { return new Point(a.x + b.x, a.y + b.y); }; /** * Subtract two points component-wise and return the result as a new Point object. * @param {!XY} a - any "Point like" object with {x,y} * @param {!XY} b - any "Point like" object with {x,y} * @return {Point} the resulting Point object. */ Point.sub = function(a, b) { return new Point(a.x - b.x, a.y - b.y); }; /** * Multiply a point by a scalar and return the result as a new Point object. * @param {number} s - the scalar * @param {!XY} p - any "Point like" object with {x,y} * @return {Point} the resulting Point object. */ Point.mul = function(s, p) { return new Point(s * p.x, s * p.y); }; /** * Perform the cross product on either two points (this produces a scalar) * or a point and a scalar (this produces a point). * This function requires two parameters, either may be a Point object or a * number. * @param {XY|number} a - Point object or scalar. * @param {XY|number} b - Point object or scalar. * @return {Point|number} a Point object or a number, depending on the parameters. */ Point.cross = function(a, b) { if (typeof(a) === 'number') { if (typeof(b) === 'number') { return a * b; } else { return new Point(-a * b.y, a * b.x); } } else { if (typeof(b) === 'number') { return new Point(b * a.y, -b * a.x); } else { return a.x * b.y - a.y * b.x; } } }; // -----------------------------------------------------------------"Point-Like" /* * The following functions operate on "Point" or any "Point like" object * with {x,y} (duck typing). */ Point.toString = xy.toString; Point.compare = xy.compare; Point.cmp = xy.compare; // backward compatibility Point.equals = xy.equals; /** * Peform the dot product on two vectors. * @public * @param {!XY} a - any "Point like" object with {x,y} * @param {!XY} b - any "Point like" object with {x,y} * @return {number} The dot product */ Point.dot = function(a, b) { return a.x * b.x + a.y * b.y; }; // ---------------------------------------------------------Exports (public API) module.exports = Point; },{"./xy":11}],5:[function(require,module,exports){ /* * Poly2Tri Copyright (c) 2009-2014, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * poly2tri.js (JavaScript port) (c) 2009-2014, Poly2Tri Contributors * https://github.com/r3mi/poly2tri.js * * All rights reserved. * * Distributed under the 3-clause BSD License, see LICENSE.txt */ "use strict"; /* * Class added in the JavaScript version (was not present in the c++ version) */ var xy = require('./xy'); /** * Custom exception class to indicate invalid Point values * @constructor * @public * @extends Error * @struct * @param {string=} message - error message * @param {Array.<XY>=} points - invalid points */ var PointError = function(message, points) { this.name = "PointError"; /** * Invalid points * @public * @type {Array.<XY>} */ this.points = points = points || []; /** * Error message * @public * @type {string} */ this.message = message || "Invalid Points!"; for (var i = 0; i < points.length; i++) { this.message += " " + xy.toString(points[i]); } }; PointError.prototype = new Error(); PointError.prototype.constructor = PointError; module.exports = PointError; },{"./xy":11}],6:[function(require,module,exports){ (function (global){ /* * Poly2Tri Copyright (c) 2009-2014, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * poly2tri.js (JavaScript port) (c) 2009-2014, Poly2Tri Contributors * https://github.com/r3mi/poly2tri.js * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Poly2Tri nor the names of its contributors may be * used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ "use strict"; /** * Public API for poly2tri.js * @module poly2tri */ /** * If you are not using a module system (e.g. CommonJS, RequireJS), you can access this library * as a global variable <code>poly2tri</code> i.e. <code>window.poly2tri</code> in a browser. * @name poly2tri * @global * @public * @type {module:poly2tri} */ var previousPoly2tri = global.poly2tri; /** * For Browser + &lt;script&gt; : * reverts the {@linkcode poly2tri} global object to its previous value, * and returns a reference to the instance called. * * @example * var p = poly2tri.noConflict(); * @public * @return {module:poly2tri} instance called */ // (this feature is not automatically provided by browserify). exports.noConflict = function() { global.poly2tri = previousPoly2tri; return exports; }; /** * poly2tri library version * @public * @const {string} */ exports.VERSION = require('../dist/version.json').version; /** * Exports the {@linkcode PointError} class. * @public * @typedef {PointError} module:poly2tri.PointError * @function */ exports.PointError = require('./pointerror'); /** * Exports the {@linkcode Point} class. * @public * @typedef {Point} module:poly2tri.Point * @function */ exports.Point = require('./point'); /** * Exports the {@linkcode Triangle} class. * @public * @typedef {Triangle} module:poly2tri.Triangle * @function */ exports.Triangle = require('./triangle'); /** * Exports the {@linkcode SweepContext} class. * @public * @typedef {SweepContext} module:poly2tri.SweepContext * @function */ exports.SweepContext = require('./sweepcontext'); // Backward compatibility var sweep = require('./sweep'); /** * @function * @deprecated use {@linkcode SweepContext#triangulate} instead */ exports.triangulate = sweep.triangulate; /** * @deprecated use {@linkcode SweepContext#triangulate} instead * @property {function} Triangulate - use {@linkcode SweepContext#triangulate} instead */ exports.sweep = {Triangulate: sweep.triangulate}; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"../dist/version.json":1,"./point":4,"./pointerror":5,"./sweep":7,"./sweepcontext":8,"./triangle":9}],7:[function(require,module,exports){ /* * Poly2Tri Copyright (c) 2009-2014, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * poly2tri.js (JavaScript port) (c) 2009-2014, Poly2Tri Contributors * https://github.com/r3mi/poly2tri.js * * All rights reserved. * * Distributed under the 3-clause BSD License, see LICENSE.txt */ /* jshint latedef:nofunc, maxcomplexity:9 */ "use strict"; /** * This 'Sweep' module is present in order to keep this JavaScript version * as close as possible to the reference C++ version, even though almost all * functions could be declared as methods on the {@linkcode module:sweepcontext~SweepContext} object. * @module * @private */ /* * Note * ==== * the structure of this JavaScript version of poly2tri intentionally follows * as closely as possible the structure of the reference C++ version, to make it * easier to keep the 2 versions in sync. */ var assert = require('./assert'); var PointError = require('./pointerror'); var Triangle = require('./triangle'); var Node = require('./advancingfront').Node; // ------------------------------------------------------------------------utils var utils = require('./utils'); /** @const */ var EPSILON = utils.EPSILON; /** @const */ var Orientation = utils.Orientation; /** @const */ var orient2d = utils.orient2d; /** @const */ var inScanArea = utils.inScanArea; /** @const */ var isAngleObtuse = utils.isAngleObtuse; // ------------------------------------------------------------------------Sweep /** * Triangulate the polygon with holes and Steiner points. * Do this AFTER you've added the polyline, holes, and Steiner points * @private * @param {!SweepContext} tcx - SweepContext object */ function triangulate(tcx) { tcx.initTriangulation(); tcx.createAdvancingFront(); // Sweep points; build mesh sweepPoints(tcx); // Clean up finalizationPolygon(tcx); } /** * Start sweeping the Y-sorted point set from bottom to top * @param {!SweepContext} tcx - SweepContext object */ function sweepPoints(tcx) { var i, len = tcx.pointCount(); for (i = 1; i < len; ++i) { var point = tcx.getPoint(i); var node = pointEvent(tcx, point); var edges = point._p2t_edge_list; for (var j = 0; edges && j < edges.length; ++j) { edgeEventByEdge(tcx, edges[j], node); } } } /** * @param {!SweepContext} tcx - SweepContext object */ function finalizationPolygon(tcx) { // Get an Internal triangle to start with var t = tcx.front().head().next.triangle; var p = tcx.front().head().next.point; while (!t.getConstrainedEdgeCW(p)) { t = t.neighborCCW(p); } // Collect interior triangles constrained by edges tcx.meshClean(t); } /** * Find closes node to the left of the new point and * create a new triangle. If needed new holes and basins * will be filled to. * @param {!SweepContext} tcx - SweepContext object * @param {!XY} point Point */ function pointEvent(tcx, point) { var node = tcx.locateNode(point); var new_node = newFrontTriangle(tcx, point, node); // Only need to check +epsilon since point never have smaller // x value than node due to how we fetch nodes from the front if (point.x <= node.point.x + (EPSILON)) { fill(tcx, node); } //tcx.AddNode(new_node); fillAdvancingFront(tcx, new_node); return new_node; } function edgeEventByEdge(tcx, edge, node) { tcx.edge_event.constrained_edge = edge; tcx.edge_event.right = (edge.p.x > edge.q.x); if (isEdgeSideOfTriangle(node.triangle, edge.p, edge.q)) { return; } // For now we will do all needed filling // TODO: integrate with flip process might give some better performance // but for now this avoid the issue with cases that needs both flips and fills fillEdgeEvent(tcx, edge, node); edgeEventByPoints(tcx, edge.p, edge.q, node.triangle, edge.q); } function edgeEventByPoints(tcx, ep, eq, triangle, point) { if (isEdgeSideOfTriangle(triangle, ep, eq)) { return; } var p1 = triangle.pointCCW(point); var o1 = orient2d(eq, p1, ep); if (o1 === Orientation.COLLINEAR) { // TODO integrate here changes from C++ version // (C++ repo revision 09880a869095 dated March 8, 2011) throw new PointError('poly2tri EdgeEvent: Collinear not supported!', [eq, p1, ep]); } var p2 = triangle.pointCW(point); var o2 = orient2d(eq, p2, ep); if (o2 === Orientation.COLLINEAR) { // TODO integrate here changes from C++ version // (C++ repo revision 09880a869095 dated March 8, 2011) throw new PointError('poly2tri EdgeEvent: Collinear not supported!', [eq, p2, ep]); } if (o1 === o2) { // Need to decide if we are rotating CW or CCW to get to a triangle // that will cross edge if (o1 === Orientation.CW) { triangle = triangle.neighborCCW(point); } else { triangle = triangle.neighborCW(point); } edgeEventByPoints(tcx, ep, eq, triangle, point); } else { // This triangle crosses constraint so lets flippin start! flipEdgeEvent(tcx, ep, eq, triangle, point); } } function isEdgeSideOfTriangle(triangle, ep, eq) { var index = triangle.edgeIndex(ep, eq); if (index !== -1) { triangle.markConstrainedEdgeByIndex(index); var t = triangle.getNeighbor(index); if (t) { t.markConstrainedEdgeByPoints(ep, eq); } return true; } return false; } /** * Creates a new front triangle and legalize it * @param {!SweepContext} tcx - SweepContext object */ function newFrontTriangle(tcx, point, node) { var triangle = new Triangle(point, node.point, node.next.point); triangle.markNeighbor(node.triangle); tcx.addToMap(triangle); var new_node = new Node(point); new_node.next = node.next; new_node.prev = node; node.next.prev = new_node; node.next = new_node; if (!legalize(tcx, triangle)) { tcx.mapTriangleToNodes(triangle); } return new_node; } /** * Adds a triangle to the advancing front to fill a hole. * @param {!SweepContext} tcx - SweepContext object * @param node - middle node, that is the bottom of the hole */ function fill(tcx, node) { var triangle = new Triangle(node.prev.point, node.point, node.next.point); // TODO: should copy the constrained_edge value from neighbor triangles // for now constrained_edge values are copied during the legalize triangle.markNeighbor(node.prev.triangle); triangle.markNeighbor(node.triangle); tcx.addToMap(triangle); // Update the advancing front node.prev.next = node.next; node.next.prev = node.prev; // If it was legalized the triangle has already been mapped if (!legalize(tcx, triangle)) { tcx.mapTriangleToNodes(triangle); } //tcx.removeNode(node); } /** * Fills holes in the Advancing Front * @param {!SweepContext} tcx - SweepContext object */ function fillAdvancingFront(tcx, n) { // Fill right holes var node = n.next; while (node.next) { // TODO integrate here changes from C++ version // (C++ repo revision acf81f1f1764 dated April 7, 2012) if (isAngleObtuse(node.point, node.next.point, node.prev.point)) { break; } fill(tcx, node); node = node.next; } // Fill left holes node = n.prev; while (node.prev) { // TODO integrate here changes from C++ version // (C++ repo revision acf81f1f1764 dated April 7, 2012) if (isAngleObtuse(node.point, node.next.point, node.prev.point)) { break; } fill(tcx, node); node = node.prev; } // Fill right basins if (n.next && n.next.next) { if (isBasinAngleRight(n)) { fillBasin(tcx, n); } } } /** * The basin angle is decided against the horizontal line [1,0]. * @param {Node} node * @return {boolean} true if angle < 3*π/4 */ function isBasinAngleRight(node) { var ax = node.point.x - node.next.next.point.x; var ay = node.point.y - node.next.next.point.y; assert(ay >= 0, "unordered y"); return (ax >= 0 || Math.abs(ax) < ay); } /** * Returns true if triangle was legalized * @param {!SweepContext} tcx - SweepContext object * @return {boolean} */ function legalize(tcx, t) { // To legalize a triangle we start by finding if any of the three edges // violate the Delaunay condition for (var i = 0; i < 3; ++i) { if (t.delaunay_edge[i]) { continue; } var ot = t.getNeighbor(i); if (ot) { var p = t.getPoint(i); var op = ot.oppositePoint(t, p); var oi = ot.index(op); // If this is a Constrained Edge or a Delaunay Edge(only during recursive legalization) // then we should not try to legalize if (ot.constrained_edge[oi] || ot.delaunay_edge[oi]) { t.constrained_edge[i] = ot.constrained_edge[oi]; continue; } var inside = inCircle(p, t.pointCCW(p), t.pointCW(p), op); if (inside) { // Lets mark this shared edge as Delaunay t.delaunay_edge[i] = true; ot.delaunay_edge[oi] = true; // Lets rotate shared edge one vertex CW to legalize it rotateTrianglePair(t, p, ot, op); // We now got one valid Delaunay Edge shared by two triangles // This gives us 4 new edges to check for Delaunay // Make sure that triangle to node mapping is done only one time for a specific triangle var not_legalized = !legalize(tcx, t); if (not_legalized) { tcx.mapTriangleToNodes(t); } not_legalized = !legalize(tcx, ot); if (not_legalized) { tcx.mapTriangleToNodes(ot); } // Reset the Delaunay edges, since they only are valid Delaunay edges // until we add a new triangle or point. // XXX: need to think about this. Can these edges be tried after we // return to previous recursive level? t.delaunay_edge[i] = false; ot.delaunay_edge[oi] = false; // If triangle have been legalized no need to check the other edges since // the recursive legalization will handles those so we can end here. return true; } } } return false; } /** * <b>Requirement</b>:<br> * 1. a,b and c form a triangle.<br> * 2. a and d is know to be on opposite side of bc<br> * <pre> * a * + * / \ * / \ * b/ \c * +-------+ * / d \ * / \ * </pre> * <b>Fact</b>: d has to be in area B to have a chance to be inside the circle formed by * a,b and c<br> * d is outside B if orient2d(a,b,d) or orient2d(c,a,d) is CW<br> * This preknowledge gives us a way to optimize the incircle test * @param pa - triangle point, opposite d * @param pb - triangle point * @param pc - triangle point * @param pd - point opposite a * @return {boolean} true if d is inside circle, false if on circle edge */ function inCircle(pa, pb, pc, pd) { var adx = pa.x - pd.x; var ady = pa.y - pd.y; var bdx = pb.x - pd.x; var bdy = pb.y - pd.y; var adxbdy = adx * bdy; var bdxady = bdx * ady; var oabd = adxbdy - bdxady; if (oabd <= 0) { return false; } var cdx = pc.x - pd.x; var cdy = pc.y - pd.y; var cdxady = cdx * ady; var adxcdy = adx * cdy; var ocad = cdxady - adxcdy; if (ocad <= 0) { return false; } var bdxcdy = bdx * cdy; var cdxbdy = cdx * bdy; var alift = adx * adx + ady * ady; var blift = bdx * bdx + bdy * bdy; var clift = cdx * cdx + cdy * cdy; var det = alift * (bdxcdy - cdxbdy) + blift * ocad + clift * oabd; return det > 0; } /** * Rotates a triangle pair one vertex CW *<pre> * n2 n2 * P +-----+ P +-----+ * | t /| |\ t | * | / | | \ | * n1| / |n3 n1| \ |n3 * | / | after CW | \ | * |/ oT | | oT \| * +-----+ oP +-----+ * n4 n4 * </pre> */ function rotateTrianglePair(t, p, ot, op) { var n1, n2, n3, n4; n1 = t.neighborCCW(p); n2 = t.neighborCW(p); n3 = ot.neighborCCW(op); n4 = ot.neighborCW(op); var ce1, ce2, ce3, ce4; ce1 = t.getConstrainedEdgeCCW(p); ce2 = t.getConstrainedEdgeCW(p); ce3 = ot.getConstrainedEdgeCCW(op); ce4 = ot.getConstrainedEdgeCW(op); var de1, de2, de3, de4; de1 = t.getDelaunayEdgeCCW(p); de2 = t.getDelaunayEdgeCW(p); de3 = ot.getDelaunayEdgeCCW(op); de4 = ot.getDelaunayEdgeCW(op); t.legalize(p, op); ot.legalize(op, p); // Remap delaunay_edge ot.setDelaunayEdgeCCW(p, de1); t.setDelaunayEdgeCW(p, de2); t.setDelaunayEdgeCCW(op, de3); ot.setDelaunayEdgeCW(op, de4); // Remap constrained_edge ot.setConstrainedEdgeCCW(p, ce1); t.setConstrainedEdgeCW(p, ce2); t.setConstrainedEdgeCCW(op, ce3); ot.setConstrainedEdgeCW(op, ce4); // Remap neighbors // XXX: might optimize the markNeighbor by keeping track of // what side should be assigned to what neighbor after the // rotation. Now mark neighbor does lots of testing to find // the right side. t.clearNeighbors(); ot.clearNeighbors(); if (n1) { ot.markNeighbor(n1); } if (n2) { t.markNeighbor(n2); } if (n3) { t.markNeighbor(n3); } if (n4) { ot.markNeighbor(n4); } t.markNeighbor(ot); } /** * Fills a basin that has formed on the Advancing Front to the right * of given node.<br> * First we decide a left,bottom and right node that forms the * boundaries of the basin. Then we do a reqursive fill. * * @param {!SweepContext} tcx - SweepContext object * @param node - starting node, this or next node will be left node */ function fillBasin(tcx, node) { if (orient2d(node.point, node.next.point, node.next.next.point) === Orientation.CCW) { tcx.basin.left_node = node.next.next; } else { tcx.basin.left_node = node.next; } // Find the bottom and right node tcx.basin.bottom_node = tcx.basin.left_node; while (tcx.basin.bottom_node.next && tcx.basin.bottom_node.point.y >= tcx.basin.bottom_node.next.point.y) { tcx.basin.bottom_node = tcx.basin.bottom_node.next; } if (tcx.basin.bottom_node === tcx.basin.left_node) { // No valid basin return; } tcx.basin.right_node = tcx.basin.bottom_node; while (tcx.basin.right_node.next && tcx.basin.right_node.point.y < tcx.basin.right_node.next.point.y) { tcx.basin.right_node = tcx.basin.right_node.next; } if (tcx.basin.right_node === tcx.basin.bottom_node) { // No valid basins return; } tcx.basin.width = tcx.basin.right_node.point.x - tcx.basin.left_node.point.x; tcx.basin.left_highest = tcx.basin.left_node.point.y > tcx.basin.right_node.point.y; fillBasinReq(tcx, tcx.basin.bottom_node); } /** * Recursive algorithm to fill a Basin with triangles * * @param {!SweepContext} tcx - SweepContext object * @param node - bottom_node */ function fillBasinReq(tcx, node) { // if shallow stop filling if (isShallow(tcx, node)) { return; } fill(tcx, node); var o; if (node.prev === tcx.basin.left_node && node.next === tcx.basin.right_node) { return; } else if (node.prev === tcx.basin.left_node) { o = orient2d(node.point, node.next.point, node.next.next.point); if (o === Orientation.CW) { return; } node = node.next; } else if (node.next === tcx.basin.right_node) { o = orient2d(node.point, node.prev.point, node.prev.prev.point); if (o === Orientation.CCW) { return; } node = node.prev; } else { // Continue with the neighbor node with lowest Y value if (node.prev.point.y < node.next.point.y) { node = node.prev; } else { node = node.next; } } fillBasinReq(tcx, node); } function isShallow(tcx, node) { var height; if (tcx.basin.left_highest) { height = tcx.basin.left_node.point.y - node.point.y; } else { height = tcx.basin.right_node.point.y - node.point.y; } // if shallow stop filling if (tcx.basin.width > height) { return true; } return false; } function fillEdgeEvent(tcx, edge, node) { if (tcx.edge_event.right) { fillRightAboveEdgeEvent(tcx, edge, node); } else { fillLeftAboveEdgeEvent(tcx, edge, node); } } function fillRightAboveEdgeEvent(tcx, edge, node) { while (node.next.point.x < edge.p.x) { // Check if next node is below the edge if (orient2d(edge.q, node.next.point, edge.p) === Orientation.CCW) { fillRightBelowEdgeEvent(tcx, edge, node); } else { node = node.next; } } } function fillRightBelowEdgeEvent(tcx, edge, node) { if (node.point.x < edge.p.x) { if (orient2d(node.point, node.next.point, node.next.next.point) === Orientation.CCW) { // Concave fillRightConcaveEdgeEvent(tcx, edge, node); } else { // Convex fillRightConvexEdgeEvent(tcx, edge, node); // Retry this one fillRightBelowEdgeEvent(tcx, edge, node); } } } function fillRightConcaveEdgeEvent(tcx, edge, node) { fill(tcx, node.next); if (node.next.point !== edge.p) { // Next above or below edge? if (orient2d(edge.q, node.next.point, edge.p) === Orientation.CCW) { // Below if (orient2d(node.point, node.next.point, node.next.next.point) === Orientation.CCW) { // Next is concave fillRightConcaveEdgeEvent(tcx, edge, node); } else { // Next is convex /* jshint noempty:false */ } } } } function fillRightConvexEdgeEvent(tcx, edge, node) { // Next concave or convex? if (orient2d(node.next.point, node.next.next.point, node.next.next.next.point) === Orientation.CCW) { // Concave fillRightConcaveEdgeEvent(tcx, edge, node.next); } else { // Convex // Next above or below edge? if (orient2d(edge.q, node.next.next.point, edge.p) === Orientation.CCW) { // Below fillRightConvexEdgeEvent(tcx, edge, node.next); } else { // Above /* jshint noempty:false */ } } } function fillLeftAboveEdgeEvent(tcx, edge, node) { while (node.prev.point.x > edge.p.x) { // Check if next node is below the edge if (orient2d(edge.q, node.prev.point, edge.p) === Orientation.CW) { fillLeftBelowEdgeEvent(tcx, edge, node); } else { node = node.prev; } } } function fillLeftBelowEdgeEvent(tcx, edge, node) { if (node.point.x > edge.p.x) { if (orient2d(node.point, node.prev.point, node.prev.prev.point) === Orientation.CW) { // Concave fillLeftConcaveEdgeEvent(tcx, edge, node); } else { // Convex fillLeftConvexEdgeEvent(tcx, edge, node); // Retry this one fillLeftBelowEdgeEvent(tcx, edge, node); } } } function fillLeftConvexEdgeEvent(tcx, edge, node) { // Next concave or convex? if (orient2d(node.prev.point, node.prev.prev.point, node.prev.prev.prev.point) === Orientation.CW) { // Concave fillLeftConcaveEdgeEvent(tcx, edge, node.prev); } else { // Convex // Next above or below edge? if (orient2d(edge.q, node.prev.prev.point, edge.p) === Orientation.CW) { // Below fillLeftConvexEdgeEvent(tcx, edge, node.prev); } else { // Above /* jshint noempty:false */ } } } function fillLeftConcaveEdgeEvent(tcx, edge, node) { fill(tcx, node.prev); if (node.prev.point !== edge.p) { // Next above or below edge? if (orient2d(edge.q, node.prev.point, edge.p) === Orientation.CW) { // Below if (orient2d(node.point, node.prev.point, node.prev.prev.point) === Orientation.CW) { // Next is concave fillLeftConcaveEdgeEvent(tcx, edge, node); } else { // Next is convex /* jshint noempty:false */ } } } } function flipEdgeEvent(tcx, ep, eq, t, p) { var ot = t.neighborAcross(p); assert(ot, "FLIP failed due to missing triangle!"); var op = ot.oppositePoint(t, p); // Additional check from Java version (see issue #88) if (t.getConstrainedEdgeAcross(p)) { var index = t.index(p); throw new PointError("poly2tri Intersecting Constraints", [p, op, t.getPoint((index + 1) % 3), t.getPoint((index + 2) % 3)]); } if (inScanArea(p, t.pointCCW(p), t.pointCW(p), op)) { // Lets rotate shared edge one vertex CW rotateTrianglePair(t, p, ot, op); tcx.mapTriangleToNodes(t); tcx.mapTriangleToNodes(ot); // XXX: in the original C++ code for the next 2 lines, we are // comparing point values (and not pointers). In this JavaScript // code, we are comparing point references (pointers). This works // because we can't have 2 different points with the same values. // But to be really equivalent, we should use "Point.equals" here. if (p === eq && op === ep) { if (eq === tcx.edge_event.constrained_edge.q && ep === tcx.edge_event.constrained_edge.p) { t.markConstrainedEdgeByPoints(ep, eq); ot.markConstrainedEdgeByPoints(ep, eq); legalize(tcx, t); legalize(tcx, ot); } else { // XXX: I think one of the triangles should be legalized here? /* jshint noempty:false */ } } else { var o = orient2d(eq, op, ep); t = nextFlipTriangle(tcx, o, t, ot, p, op); flipEdgeEvent(tcx, ep, eq, t, p); } } else { var newP = nextFlipPoint(ep, eq, ot, op); flipScanEdgeEvent(tcx, ep, eq, t, ot, newP); edgeEventByPoints(tcx, ep, eq, t, p); } } /** * After a flip we have two triangles and know that only one will still be * intersecting the edge. So decide which to contiune with and legalize the other * * @param {!SweepContext} tcx - SweepContext object * @param o - should be the result of an orient2d( eq, op, ep ) * @param t - triangle 1 * @param ot - triangle 2 * @param p - a point shared by both triangles * @param op - another point shared by both triangles * @return returns the triangle still intersecting the edge */ function nextFlipTriangle(tcx, o, t, ot, p, op) { var edge_index; if (o === Orientation.CCW) { // ot is not crossing edge after flip edge_index = ot.edgeIndex(p, op); ot.delaunay_edge[edge_index] = true; legalize(tcx, ot); ot.clearDelaunayEdges(); return t; } // t is not crossing edge after flip edge_index = t.edgeIndex(p, op); t.delaunay_edge[edge_index] = true; legalize(tcx, t); t.clearDelaunayEdges(); return ot; } /** * When we need to traverse from one triangle to the next we need * the point in current triangle that is the opposite point to the next * triangle. */ function nextFlipPoint(ep, eq, ot, op) { var o2d = orient2d(eq, op, ep); if (o2d === Orientation.CW) { // Right return ot.pointCCW(op); } else if (o2d === Orientation.CCW) { // Left return ot.pointCW(op); } else { throw new PointError("poly2tri [Unsupported] nextFlipPoint: opposing point on constrained edge!", [eq, op, ep]); } } /** * Scan part of the FlipScan algorithm<br> * When a triangle pair isn't flippable we will scan for the next * point that is inside the flip triangle scan area. When found * we generate a new flipEdgeEvent * * @param {!SweepContext} tcx - SweepContext object * @param ep - last point on the edge we are traversing * @param eq - first point on the edge we are traversing * @param {!Triangle} flip_triangle - the current triangle sharing the point eq with edge * @param t * @param p */ function flipScanEdgeEvent(tcx, ep, eq, flip_triangle, t, p) { var ot = t.neighborAcross(p); assert(ot, "FLIP failed due to missing triangle"); var op = ot.oppositePoint(t, p); if (inScanArea(eq, flip_triangle.pointCCW(eq), flip_triangle.pointCW(eq), op)) { // flip with new edge op.eq flipEdgeEvent(tcx, eq, op, ot, op); } else { var newP = nextFlipPoint(ep, eq, ot, op); flipScanEdgeEvent(tcx, ep, eq, flip_triangle, ot, newP); } } // ----------------------------------------------------------------------Exports exports.triangulate = triangulate; },{"./advancingfront":2,"./assert":3,"./pointerror":5,"./triangle":9,"./utils":10}],8:[function(require,module,exports){ /* * Poly2Tri Copyright (c) 2009-2014, Poly2Tri Contributors * http://code.google.com/p/poly2tri/ * * poly2tri.js (JavaScript port) (c) 2009-2014, Poly2Tri Contributors * https://github.com/r3mi/poly2tri.js * * All rights reserved. * * Distributed under the 3-clause BSD License, see LICENSE.txt */ /* jshint maxcomplexity:6 */ "use strict"; /* * Note * ==== * the structure of this JavaScript version of poly2tri intentionally follows * as closely as possible the structure of the reference C++ version, to make it * easier to keep the 2 versions in sync. */ var PointError = require('./pointerror'); var Point = require('./point'); var Triangle = require('./triangle'); var sweep = require('./sweep'); var AdvancingFront = require('./advancingfront'); var Node = AdvancingFront.Node; // ------------------------------------------------------------------------utils /** * Initial triangle factor, seed triangle will extend 30% of * PointSet width to both left and right. * @private * @const */ var kAlpha = 0.3; // -------------------------------------------------------------------------Edge /** * Represents a simple polygon's edge * @constructor * @struct * @private * @param {Point} p1 * @param {Point} p2 * @throw {PointError} if p1 is same as p2 */ var Edge = function(p1, p2) { this.p = p1; this.q = p2; if (p1.y > p2.y) { this.q = p1; this.p = p2; } else if (p1.y === p2.y) { if (p1.x > p2.x) { this.q = p1; this.p = p2; } else if (p1.x === p2.x) { throw new PointError('poly2tri Invalid Edge constructor: repeated points!', [p1]); } } if (!this.q._p2t_edge_list) { this.q._p2t_edge_list = []; } this.q._p2t_edge_list.push(this); }; // ------------------------------------------------------------------------Basin /** * @constructor * @struct * @private */ var Basin = function() { /** @type {Node} */ this.left_node = null; /** @type {Node} */ this.bottom_node = null; /** @type {Node} */ this.right_node = null; /** @type {number} */ this.width = 0.0; /** @type {boolean} */ this.left_highest = false; }; Basin.prototype.clear = function() { this.left_node = null; this.bottom_node = null; this.right_node = null; this.width = 0.0; this.left_highest = false; }; // --------------------------------------------------------------------EdgeEvent /** * @constructor * @struct * @private */ var EdgeEvent = function() { /** @type {Edge} */ this.constrained_edge = null; /** @type {boolean} */ this.right = false; }; // ----------------------------------------------------SweepContext (public API) /** * SweepContext constructor option * @typedef {Object} SweepContextOptions * @property {boolean=} cloneArrays - if <code>true</code>, do a shallow copy of the Array parameters * (contour, holes). Points inside arrays are never copied. * Default is <code>false</code> : keep a reference to the array arguments, * who will be modified in place. */ /** * Constructor for the triangulation context. * It accepts a simple polyline (with non repeating points), * which defines the constrained edges. * * @example * var contour = [ * new poly2tri.Point(100, 100), * new poly2tri.Point(100, 300), * new poly2tri.Point(300, 300), * new poly2tri.Point(300, 100) * ]; * var swctx = new poly2tri.SweepContext(contour, {cloneArrays: true}); * @example * var contour = [{x:100, y:100}, {x:100, y:300}, {x:300, y:300}, {x:300, y:100}]; * var swctx = new poly2tri.SweepContext(contour, {cloneArrays: true}); * @constructor * @public * @struct * @param {Array.<XY>} contour - array of point objects. The points can be either {@linkcode Point} instances, * or any "Point like" custom class with <code>{x, y}</code> attributes. * @param {SweepContextOptions=} options - constructor options */ var SweepContext = function(contour, options) { options = options || {}; this.triangles_ = []; this.map_ = []; this.points_ = (options.cloneArrays ? contour.slice(0) : contour); this.edge_list = []; // Bounding box of all points. Computed at the start of the triangulation, // it is stored in case it is needed by the caller. this.pmin_ = this.pmax_ = null; /** * Advancing front * @private * @type {AdvancingFront} */ this.front_ = null; /** * head point used with advancing front * @private * @type {Point} */ this.head_ = null; /** * tail point used with advancing front * @private * @type {Point} */ this.tail_ = null; /** * @private * @type {Node} */ this.af_head_ = null; /** * @private * @type {Node} */ this.af_middle_ = null; /** * @private * @type {Node} */ this.af_tail_ = null; this.basin = new Basin(); this.edge_event = new EdgeEvent(); this.initEdges(this.points_); }; /** * Add a hole to the constraints * @example * var swctx = new poly2tri.SweepContext(contour); * var hole = [ * new poly2tri.Point(200, 200), * new poly2tri.Point(200, 250), * new poly2tri.Point(250, 250) * ]; * swctx.addHole(hole); * @example * var swctx = new poly2tri.SweepContext(contour); * swctx.addHole([{x:200, y:200}, {x:200, y:250}, {x:250, y:250}]); * @public * @param {Array.<XY>} polyline - array of "Point like" objects with {x,y} */ SweepContext.prototype.addHole = function(polyline) { this.initEdges(polyline); var i, len = polyline.length; for (i = 0; i < len; i++) { this.points_.push(polyline[i]); } return this; // for chaining }; /** * For backward compatibility * @function * @deprecated use {@linkcode SweepContext#addHole} instead