poly2tri
Version:
A 2D constrained Delaunay triangulation library
1,740 lines (1,526 loc) • 80.7 kB
JavaScript
(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 + <script> :
* 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