UNPKG

poly2tri

Version:

A 2D constrained Delaunay triangulation library

542 lines (473 loc) 14.8 kB
/* * 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 */ SweepContext.prototype.AddHole = SweepContext.prototype.addHole; /** * Add several holes to the constraints * @example * var swctx = new poly2tri.SweepContext(contour); * var holes = [ * [ new poly2tri.Point(200, 200), new poly2tri.Point(200, 250), new poly2tri.Point(250, 250) ], * [ new poly2tri.Point(300, 300), new poly2tri.Point(300, 350), new poly2tri.Point(350, 350) ] * ]; * swctx.addHoles(holes); * @example * var swctx = new poly2tri.SweepContext(contour); * var holes = [ * [{x:200, y:200}, {x:200, y:250}, {x:250, y:250}], * [{x:300, y:300}, {x:300, y:350}, {x:350, y:350}] * ]; * swctx.addHoles(holes); * @public * @param {Array.<Array.<XY>>} holes - array of array of "Point like" objects with {x,y} */ // Method added in the JavaScript version (was not present in the c++ version) SweepContext.prototype.addHoles = function(holes) { var i, len = holes.length; for (i = 0; i < len; i++) { this.initEdges(holes[i]); } this.points_ = this.points_.concat.apply(this.points_, holes); return this; // for chaining }; /** * Add a Steiner point to the constraints * @example * var swctx = new poly2tri.SweepContext(contour); * var point = new poly2tri.Point(150, 150); * swctx.addPoint(point); * @example * var swctx = new poly2tri.SweepContext(contour); * swctx.addPoint({x:150, y:150}); * @public * @param {XY} point - any "Point like" object with {x,y} */ SweepContext.prototype.addPoint = function(point) { this.points_.push(point); return this; // for chaining }; /** * For backward compatibility * @function * @deprecated use {@linkcode SweepContext#addPoint} instead */ SweepContext.prototype.AddPoint = SweepContext.prototype.addPoint; /** * Add several Steiner points to the constraints * @example * var swctx = new poly2tri.SweepContext(contour); * var points = [ * new poly2tri.Point(150, 150), * new poly2tri.Point(200, 250), * new poly2tri.Point(250, 250) * ]; * swctx.addPoints(points); * @example * var swctx = new poly2tri.SweepContext(contour); * swctx.addPoints([{x:150, y:150}, {x:200, y:250}, {x:250, y:250}]); * @public * @param {Array.<XY>} points - array of "Point like" object with {x,y} */ // Method added in the JavaScript version (was not present in the c++ version) SweepContext.prototype.addPoints = function(points) { this.points_ = this.points_.concat(points); return this; // for chaining }; /** * Triangulate the polygon with holes and Steiner points. * Do this AFTER you've added the polyline, holes, and Steiner points * @example * var swctx = new poly2tri.SweepContext(contour); * swctx.triangulate(); * var triangles = swctx.getTriangles(); * @public */ // Shortcut method for sweep.triangulate(SweepContext). // Method added in the JavaScript version (was not present in the c++ version) SweepContext.prototype.triangulate = function() { sweep.triangulate(this); return this; // for chaining }; /** * Get the bounding box of the provided constraints (contour, holes and * Steinter points). Warning : these values are not available if the triangulation * has not been done yet. * @public * @returns {{min:Point,max:Point}} object with 'min' and 'max' Point */ // Method added in the JavaScript version (was not present in the c++ version) SweepContext.prototype.getBoundingBox = function() { return {min: this.pmin_, max: this.pmax_}; }; /** * Get result of triangulation. * The output triangles have vertices which are references * to the initial input points (not copies): any custom fields in the * initial points can be retrieved in the output triangles. * @example * var swctx = new poly2tri.SweepContext(contour); * swctx.triangulate(); * var triangles = swctx.getTriangles(); * @example * var contour = [{x:100, y:100, id:1}, {x:100, y:300, id:2}, {x:300, y:300, id:3}]; * var swctx = new poly2tri.SweepContext(contour); * swctx.triangulate(); * var triangles = swctx.getTriangles(); * typeof triangles[0].getPoint(0).id * // → "number" * @public * @returns {array<Triangle>} array of triangles */ SweepContext.prototype.getTriangles = function() { return this.triangles_; }; /** * For backward compatibility * @function * @deprecated use {@linkcode SweepContext#getTriangles} instead */ SweepContext.prototype.GetTriangles = SweepContext.prototype.getTriangles; // ---------------------------------------------------SweepContext (private API) /** @private */ SweepContext.prototype.front = function() { return this.front_; }; /** @private */ SweepContext.prototype.pointCount = function() { return this.points_.length; }; /** @private */ SweepContext.prototype.head = function() { return this.head_; }; /** @private */ SweepContext.prototype.setHead = function(p1) { this.head_ = p1; }; /** @private */ SweepContext.prototype.tail = function() { return this.tail_; }; /** @private */ SweepContext.prototype.setTail = function(p1) { this.tail_ = p1; }; /** @private */ SweepContext.prototype.getMap = function() { return this.map_; }; /** @private */ SweepContext.prototype.initTriangulation = function() { var xmax = this.points_[0].x; var xmin = this.points_[0].x; var ymax = this.points_[0].y; var ymin = this.points_[0].y; // Calculate bounds var i, len = this.points_.length; for (i = 1; i < len; i++) { var p = this.points_[i]; /* jshint expr:true */ (p.x > xmax) && (xmax = p.x); (p.x < xmin) && (xmin = p.x); (p.y > ymax) && (ymax = p.y); (p.y < ymin) && (ymin = p.y); } this.pmin_ = new Point(xmin, ymin); this.pmax_ = new Point(xmax, ymax); var dx = kAlpha * (xmax - xmin); var dy = kAlpha * (ymax - ymin); this.head_ = new Point(xmax + dx, ymin - dy); this.tail_ = new Point(xmin - dx, ymin - dy); // Sort points along y-axis this.points_.sort(Point.compare); }; /** @private */ SweepContext.prototype.initEdges = function(polyline) { var i, len = polyline.length; for (i = 0; i < len; ++i) { this.edge_list.push(new Edge(polyline[i], polyline[(i + 1) % len])); } }; /** @private */ SweepContext.prototype.getPoint = function(index) { return this.points_[index]; }; /** @private */ SweepContext.prototype.addToMap = function(triangle) { this.map_.push(triangle); }; /** @private */ SweepContext.prototype.locateNode = function(point) { return this.front_.locateNode(point.x); }; /** @private */ SweepContext.prototype.createAdvancingFront = function() { var head; var middle; var tail; // Initial triangle var triangle = new Triangle(this.points_[0], this.tail_, this.head_); this.map_.push(triangle); head = new Node(triangle.getPoint(1), triangle); middle = new Node(triangle.getPoint(0), triangle); tail = new Node(triangle.getPoint(2)); this.front_ = new AdvancingFront(head, tail); head.next = middle; middle.next = tail; middle.prev = head; tail.prev = middle; }; /** @private */ SweepContext.prototype.removeNode = function(node) { // do nothing /* jshint unused:false */ }; /** @private */ SweepContext.prototype.mapTriangleToNodes = function(t) { for (var i = 0; i < 3; ++i) { if (!t.getNeighbor(i)) { var n = this.front_.locatePoint(t.pointCW(t.getPoint(i))); if (n) { n.triangle = t; } } } }; /** @private */ SweepContext.prototype.removeFromMap = function(triangle) { var i, map = this.map_, len = map.length; for (i = 0; i < len; i++) { if (map[i] === triangle) { map.splice(i, 1); break; } } }; /** * Do a depth first traversal to collect triangles * @private * @param {Triangle} triangle start */ SweepContext.prototype.meshClean = function(triangle) { // New implementation avoids recursive calls and use a loop instead. // Cf. issues # 57, 65 and 69. var triangles = [triangle], t, i; /* jshint boss:true */ while (t = triangles.pop()) { if (!t.isInterior()) { t.setInterior(true); this.triangles_.push(t); for (i = 0; i < 3; i++) { if (!t.constrained_edge[i]) { triangles.push(t.getNeighbor(i)); } } } } }; // ----------------------------------------------------------------------Exports module.exports = SweepContext;