poly2tri
Version:
A 2D constrained Delaunay triangulation library
562 lines (513 loc) • 16.1 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:10 */
"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");
// ---------------------------------------------------------------------Triangle
/**
* Triangle class.<br>
* Triangle-based data structures are known to have better performance than
* quad-edge structures.
* See: J. Shewchuk, "Triangle: Engineering a 2D Quality Mesh Generator and
* Delaunay Triangulator", "Triangulations in CGAL"
*
* @constructor
* @struct
* @param {!XY} pa point object with {x,y}
* @param {!XY} pb point object with {x,y}
* @param {!XY} pc point object with {x,y}
*/
var Triangle = function(a, b, c) {
/**
* Triangle points
* @private
* @type {Array.<XY>}
*/
this.points_ = [a, b, c];
/**
* Neighbor list
* @private
* @type {Array.<Triangle>}
*/
this.neighbors_ = [null, null, null];
/**
* Has this triangle been marked as an interior triangle?
* @private
* @type {boolean}
*/
this.interior_ = false;
/**
* Flags to determine if an edge is a Constrained edge
* @private
* @type {Array.<boolean>}
*/
this.constrained_edge = [false, false, false];
/**
* Flags to determine if an edge is a Delauney edge
* @private
* @type {Array.<boolean>}
*/
this.delaunay_edge = [false, false, false];
};
var p2s = xy.toString;
/**
* For pretty printing ex. <code>"[(5;42)(10;20)(21;30)]"</code>.
* @public
* @return {string}
*/
Triangle.prototype.toString = function() {
return ("[" + p2s(this.points_[0]) + p2s(this.points_[1]) + p2s(this.points_[2]) + "]");
};
/**
* Get one vertice of the triangle.
* The output triangles of a triangulation 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 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"
* @param {number} index - vertice index: 0, 1 or 2
* @public
* @returns {XY}
*/
Triangle.prototype.getPoint = function(index) {
return this.points_[index];
};
/**
* For backward compatibility
* @function
* @deprecated use {@linkcode Triangle#getPoint} instead
*/
Triangle.prototype.GetPoint = Triangle.prototype.getPoint;
/**
* Get all 3 vertices of the triangle as an array
* @public
* @return {Array.<XY>}
*/
// Method added in the JavaScript version (was not present in the c++ version)
Triangle.prototype.getPoints = function() {
return this.points_;
};
/**
* @private
* @param {number} index
* @returns {?Triangle}
*/
Triangle.prototype.getNeighbor = function(index) {
return this.neighbors_[index];
};
/**
* Test if this Triangle contains the Point object given as parameter as one of its vertices.
* Only point references are compared, not values.
* @public
* @param {XY} point - point object with {x,y}
* @return {boolean} <code>True</code> if the Point object is of the Triangle's vertices,
* <code>false</code> otherwise.
*/
Triangle.prototype.containsPoint = function(point) {
var points = this.points_;
// Here we are comparing point references, not values
return (point === points[0] || point === points[1] || point === points[2]);
};
/**
* Test if this Triangle contains the Edge object given as parameter as its
* bounding edges. Only point references are compared, not values.
* @private
* @param {Edge} edge
* @return {boolean} <code>True</code> if the Edge object is of the Triangle's bounding
* edges, <code>false</code> otherwise.
*/
Triangle.prototype.containsEdge = function(edge) {
return this.containsPoint(edge.p) && this.containsPoint(edge.q);
};
/**
* Test if this Triangle contains the two Point objects given as parameters among its vertices.
* Only point references are compared, not values.
* @param {XY} p1 - point object with {x,y}
* @param {XY} p2 - point object with {x,y}
* @return {boolean}
*/
Triangle.prototype.containsPoints = function(p1, p2) {
return this.containsPoint(p1) && this.containsPoint(p2);
};
/**
* Has this triangle been marked as an interior triangle?
* @returns {boolean}
*/
Triangle.prototype.isInterior = function() {
return this.interior_;
};
/**
* Mark this triangle as an interior triangle
* @private
* @param {boolean} interior
* @returns {Triangle} this
*/
Triangle.prototype.setInterior = function(interior) {
this.interior_ = interior;
return this;
};
/**
* Update neighbor pointers.
* @private
* @param {XY} p1 - point object with {x,y}
* @param {XY} p2 - point object with {x,y}
* @param {Triangle} t Triangle object.
* @throws {Error} if can't find objects
*/
Triangle.prototype.markNeighborPointers = function(p1, p2, t) {
var points = this.points_;
// Here we are comparing point references, not values
if ((p1 === points[2] && p2 === points[1]) || (p1 === points[1] && p2 === points[2])) {
this.neighbors_[0] = t;
} else if ((p1 === points[0] && p2 === points[2]) || (p1 === points[2] && p2 === points[0])) {
this.neighbors_[1] = t;
} else if ((p1 === points[0] && p2 === points[1]) || (p1 === points[1] && p2 === points[0])) {
this.neighbors_[2] = t;
} else {
throw new Error('poly2tri Invalid Triangle.markNeighborPointers() call');
}
};
/**
* Exhaustive search to update neighbor pointers
* @private
* @param {!Triangle} t
*/
Triangle.prototype.markNeighbor = function(t) {
var points = this.points_;
if (t.containsPoints(points[1], points[2])) {
this.neighbors_[0] = t;
t.markNeighborPointers(points[1], points[2], this);
} else if (t.containsPoints(points[0], points[2])) {
this.neighbors_[1] = t;
t.markNeighborPointers(points[0], points[2], this);
} else if (t.containsPoints(points[0], points[1])) {
this.neighbors_[2] = t;
t.markNeighborPointers(points[0], points[1], this);
}
};
Triangle.prototype.clearNeighbors = function() {
this.neighbors_[0] = null;
this.neighbors_[1] = null;
this.neighbors_[2] = null;
};
Triangle.prototype.clearDelaunayEdges = function() {
this.delaunay_edge[0] = false;
this.delaunay_edge[1] = false;
this.delaunay_edge[2] = false;
};
/**
* Returns the point clockwise to the given point.
* @private
* @param {XY} p - point object with {x,y}
*/
Triangle.prototype.pointCW = function(p) {
var points = this.points_;
// Here we are comparing point references, not values
if (p === points[0]) {
return points[2];
} else if (p === points[1]) {
return points[0];
} else if (p === points[2]) {
return points[1];
} else {
return null;
}
};
/**
* Returns the point counter-clockwise to the given point.
* @private
* @param {XY} p - point object with {x,y}
*/
Triangle.prototype.pointCCW = function(p) {
var points = this.points_;
// Here we are comparing point references, not values
if (p === points[0]) {
return points[1];
} else if (p === points[1]) {
return points[2];
} else if (p === points[2]) {
return points[0];
} else {
return null;
}
};
/**
* Returns the neighbor clockwise to given point.
* @private
* @param {XY} p - point object with {x,y}
*/
Triangle.prototype.neighborCW = function(p) {
// Here we are comparing point references, not values
if (p === this.points_[0]) {
return this.neighbors_[1];
} else if (p === this.points_[1]) {
return this.neighbors_[2];
} else {
return this.neighbors_[0];
}
};
/**
* Returns the neighbor counter-clockwise to given point.
* @private
* @param {XY} p - point object with {x,y}
*/
Triangle.prototype.neighborCCW = function(p) {
// Here we are comparing point references, not values
if (p === this.points_[0]) {
return this.neighbors_[2];
} else if (p === this.points_[1]) {
return this.neighbors_[0];
} else {
return this.neighbors_[1];
}
};
Triangle.prototype.getConstrainedEdgeCW = function(p) {
// Here we are comparing point references, not values
if (p === this.points_[0]) {
return this.constrained_edge[1];
} else if (p === this.points_[1]) {
return this.constrained_edge[2];
} else {
return this.constrained_edge[0];
}
};
Triangle.prototype.getConstrainedEdgeCCW = function(p) {
// Here we are comparing point references, not values
if (p === this.points_[0]) {
return this.constrained_edge[2];
} else if (p === this.points_[1]) {
return this.constrained_edge[0];
} else {
return this.constrained_edge[1];
}
};
// Additional check from Java version (see issue #88)
Triangle.prototype.getConstrainedEdgeAcross = function(p) {
// Here we are comparing point references, not values
if (p === this.points_[0]) {
return this.constrained_edge[0];
} else if (p === this.points_[1]) {
return this.constrained_edge[1];
} else {
return this.constrained_edge[2];
}
};
Triangle.prototype.setConstrainedEdgeCW = function(p, ce) {
// Here we are comparing point references, not values
if (p === this.points_[0]) {
this.constrained_edge[1] = ce;
} else if (p === this.points_[1]) {
this.constrained_edge[2] = ce;
} else {
this.constrained_edge[0] = ce;
}
};
Triangle.prototype.setConstrainedEdgeCCW = function(p, ce) {
// Here we are comparing point references, not values
if (p === this.points_[0]) {
this.constrained_edge[2] = ce;
} else if (p === this.points_[1]) {
this.constrained_edge[0] = ce;
} else {
this.constrained_edge[1] = ce;
}
};
Triangle.prototype.getDelaunayEdgeCW = function(p) {
// Here we are comparing point references, not values
if (p === this.points_[0]) {
return this.delaunay_edge[1];
} else if (p === this.points_[1]) {
return this.delaunay_edge[2];
} else {
return this.delaunay_edge[0];
}
};
Triangle.prototype.getDelaunayEdgeCCW = function(p) {
// Here we are comparing point references, not values
if (p === this.points_[0]) {
return this.delaunay_edge[2];
} else if (p === this.points_[1]) {
return this.delaunay_edge[0];
} else {
return this.delaunay_edge[1];
}
};
Triangle.prototype.setDelaunayEdgeCW = function(p, e) {
// Here we are comparing point references, not values
if (p === this.points_[0]) {
this.delaunay_edge[1] = e;
} else if (p === this.points_[1]) {
this.delaunay_edge[2] = e;
} else {
this.delaunay_edge[0] = e;
}
};
Triangle.prototype.setDelaunayEdgeCCW = function(p, e) {
// Here we are comparing point references, not values
if (p === this.points_[0]) {
this.delaunay_edge[2] = e;
} else if (p === this.points_[1]) {
this.delaunay_edge[0] = e;
} else {
this.delaunay_edge[1] = e;
}
};
/**
* The neighbor across to given point.
* @private
* @param {XY} p - point object with {x,y}
* @returns {Triangle}
*/
Triangle.prototype.neighborAcross = function(p) {
// Here we are comparing point references, not values
if (p === this.points_[0]) {
return this.neighbors_[0];
} else if (p === this.points_[1]) {
return this.neighbors_[1];
} else {
return this.neighbors_[2];
}
};
/**
* @private
* @param {!Triangle} t Triangle object.
* @param {XY} p - point object with {x,y}
*/
Triangle.prototype.oppositePoint = function(t, p) {
var cw = t.pointCW(p);
return this.pointCW(cw);
};
/**
* Legalize triangle by rotating clockwise around oPoint
* @private
* @param {XY} opoint - point object with {x,y}
* @param {XY} npoint - point object with {x,y}
* @throws {Error} if oPoint can not be found
*/
Triangle.prototype.legalize = function(opoint, npoint) {
var points = this.points_;
// Here we are comparing point references, not values
if (opoint === points[0]) {
points[1] = points[0];
points[0] = points[2];
points[2] = npoint;
} else if (opoint === points[1]) {
points[2] = points[1];
points[1] = points[0];
points[0] = npoint;
} else if (opoint === points[2]) {
points[0] = points[2];
points[2] = points[1];
points[1] = npoint;
} else {
throw new Error('poly2tri Invalid Triangle.legalize() call');
}
};
/**
* Returns the index of a point in the triangle.
* The point *must* be a reference to one of the triangle's vertices.
* @private
* @param {XY} p - point object with {x,y}
* @returns {number} index 0, 1 or 2
* @throws {Error} if p can not be found
*/
Triangle.prototype.index = function(p) {
var points = this.points_;
// Here we are comparing point references, not values
if (p === points[0]) {
return 0;
} else if (p === points[1]) {
return 1;
} else if (p === points[2]) {
return 2;
} else {
throw new Error('poly2tri Invalid Triangle.index() call');
}
};
/**
* @private
* @param {XY} p1 - point object with {x,y}
* @param {XY} p2 - point object with {x,y}
* @return {number} index 0, 1 or 2, or -1 if errror
*/
Triangle.prototype.edgeIndex = function(p1, p2) {
var points = this.points_;
// Here we are comparing point references, not values
if (p1 === points[0]) {
if (p2 === points[1]) {
return 2;
} else if (p2 === points[2]) {
return 1;
}
} else if (p1 === points[1]) {
if (p2 === points[2]) {
return 0;
} else if (p2 === points[0]) {
return 2;
}
} else if (p1 === points[2]) {
if (p2 === points[0]) {
return 1;
} else if (p2 === points[1]) {
return 0;
}
}
return -1;
};
/**
* Mark an edge of this triangle as constrained.
* @private
* @param {number} index - edge index
*/
Triangle.prototype.markConstrainedEdgeByIndex = function(index) {
this.constrained_edge[index] = true;
};
/**
* Mark an edge of this triangle as constrained.
* @private
* @param {Edge} edge instance
*/
Triangle.prototype.markConstrainedEdgeByEdge = function(edge) {
this.markConstrainedEdgeByPoints(edge.p, edge.q);
};
/**
* Mark an edge of this triangle as constrained.
* This method takes two Point instances defining the edge of the triangle.
* @private
* @param {XY} p - point object with {x,y}
* @param {XY} q - point object with {x,y}
*/
Triangle.prototype.markConstrainedEdgeByPoints = function(p, q) {
var points = this.points_;
// Here we are comparing point references, not values
if ((q === points[0] && p === points[1]) || (q === points[1] && p === points[0])) {
this.constrained_edge[2] = true;
} else if ((q === points[0] && p === points[2]) || (q === points[2] && p === points[0])) {
this.constrained_edge[1] = true;
} else if ((q === points[1] && p === points[2]) || (q === points[2] && p === points[1])) {
this.constrained_edge[0] = true;
}
};
// ---------------------------------------------------------Exports (public API)
module.exports = Triangle;