poly2tri
Version:
A 2D constrained Delaunay triangulation library
542 lines (473 loc) • 14.8 kB
JavaScript
/*
* 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;