|
@@ -1,6 +1,6 @@
|
|
|
/**
|
|
|
* @author Mugen87 / https://github.com/Mugen87
|
|
|
- * Port from https://github.com/mapbox/earcut (v2.1.2)
|
|
|
+ * Port from https://github.com/mapbox/earcut (v2.1.5)
|
|
|
*/
|
|
|
|
|
|
var Earcut = {
|
|
@@ -14,14 +14,13 @@ var Earcut = {
|
|
|
outerNode = linkedList( data, 0, outerLen, dim, true ),
|
|
|
triangles = [];
|
|
|
|
|
|
- if ( ! outerNode ) return triangles;
|
|
|
+ if ( ! outerNode || outerNode.next === outerNode.prev ) return triangles;
|
|
|
|
|
|
var minX, minY, maxX, maxY, x, y, invSize;
|
|
|
|
|
|
if ( hasHoles ) outerNode = eliminateHoles( data, holeIndices, outerNode, dim );
|
|
|
|
|
|
// if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
|
|
|
-
|
|
|
if ( data.length > 80 * dim ) {
|
|
|
|
|
|
minX = maxX = data[ 0 ];
|
|
@@ -39,7 +38,6 @@ var Earcut = {
|
|
|
}
|
|
|
|
|
|
// minX, minY and invSize are later used to transform coords into integers for z-order calculation
|
|
|
-
|
|
|
invSize = Math.max( maxX - minX, maxY - minY );
|
|
|
invSize = invSize !== 0 ? 1 / invSize : 0;
|
|
|
|
|
@@ -54,7 +52,6 @@ var Earcut = {
|
|
|
};
|
|
|
|
|
|
// create a circular doubly linked list from polygon points in the specified winding order
|
|
|
-
|
|
|
function linkedList( data, start, end, dim, clockwise ) {
|
|
|
|
|
|
var i, last;
|
|
@@ -81,14 +78,13 @@ function linkedList( data, start, end, dim, clockwise ) {
|
|
|
}
|
|
|
|
|
|
// eliminate colinear or duplicate points
|
|
|
-
|
|
|
function filterPoints( start, end ) {
|
|
|
|
|
|
if ( ! start ) return start;
|
|
|
if ( ! end ) end = start;
|
|
|
|
|
|
- var p = start, again;
|
|
|
-
|
|
|
+ var p = start,
|
|
|
+ again;
|
|
|
do {
|
|
|
|
|
|
again = false;
|
|
@@ -113,19 +109,17 @@ function filterPoints( start, end ) {
|
|
|
}
|
|
|
|
|
|
// main ear slicing loop which triangulates a polygon (given as a linked list)
|
|
|
-
|
|
|
function earcutLinked( ear, triangles, dim, minX, minY, invSize, pass ) {
|
|
|
|
|
|
if ( ! ear ) return;
|
|
|
|
|
|
// interlink polygon nodes in z-order
|
|
|
-
|
|
|
if ( ! pass && invSize ) indexCurve( ear, minX, minY, invSize );
|
|
|
|
|
|
- var stop = ear, prev, next;
|
|
|
+ var stop = ear,
|
|
|
+ prev, next;
|
|
|
|
|
|
// iterate through ears, slicing them one by one
|
|
|
-
|
|
|
while ( ear.prev !== ear.next ) {
|
|
|
|
|
|
prev = ear.prev;
|
|
@@ -140,7 +134,7 @@ function earcutLinked( ear, triangles, dim, minX, minY, invSize, pass ) {
|
|
|
|
|
|
removeNode( ear );
|
|
|
|
|
|
- // skipping the next vertice leads to less sliver triangles
|
|
|
+ // skipping the next vertex leads to less sliver triangles
|
|
|
ear = next.next;
|
|
|
stop = next.next;
|
|
|
|
|
@@ -151,11 +145,9 @@ function earcutLinked( ear, triangles, dim, minX, minY, invSize, pass ) {
|
|
|
ear = next;
|
|
|
|
|
|
// if we looped through the whole remaining polygon and can't find any more ears
|
|
|
-
|
|
|
if ( ear === stop ) {
|
|
|
|
|
|
// try filtering points and slicing again
|
|
|
-
|
|
|
if ( ! pass ) {
|
|
|
|
|
|
earcutLinked( filterPoints( ear ), triangles, dim, minX, minY, invSize, 1 );
|
|
@@ -184,7 +176,6 @@ function earcutLinked( ear, triangles, dim, minX, minY, invSize, pass ) {
|
|
|
}
|
|
|
|
|
|
// check whether a polygon node forms a valid ear with adjacent nodes
|
|
|
-
|
|
|
function isEar( ear ) {
|
|
|
|
|
|
var a = ear.prev,
|
|
@@ -198,12 +189,8 @@ function isEar( ear ) {
|
|
|
|
|
|
while ( p !== ear.prev ) {
|
|
|
|
|
|
- if ( pointInTriangle( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) && area( p.prev, p, p.next ) >= 0 ) {
|
|
|
-
|
|
|
- return false;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
+ if ( pointInTriangle( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) &&
|
|
|
+ area( p.prev, p, p.next ) >= 0 ) return false;
|
|
|
p = p.next;
|
|
|
|
|
|
}
|
|
@@ -221,57 +208,65 @@ function isEarHashed( ear, minX, minY, invSize ) {
|
|
|
if ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear
|
|
|
|
|
|
// triangle bbox; min & max are calculated like this for speed
|
|
|
-
|
|
|
var minTX = a.x < b.x ? ( a.x < c.x ? a.x : c.x ) : ( b.x < c.x ? b.x : c.x ),
|
|
|
minTY = a.y < b.y ? ( a.y < c.y ? a.y : c.y ) : ( b.y < c.y ? b.y : c.y ),
|
|
|
maxTX = a.x > b.x ? ( a.x > c.x ? a.x : c.x ) : ( b.x > c.x ? b.x : c.x ),
|
|
|
maxTY = a.y > b.y ? ( a.y > c.y ? a.y : c.y ) : ( b.y > c.y ? b.y : c.y );
|
|
|
|
|
|
// z-order range for the current triangle bbox;
|
|
|
-
|
|
|
var minZ = zOrder( minTX, minTY, minX, minY, invSize ),
|
|
|
maxZ = zOrder( maxTX, maxTY, minX, minY, invSize );
|
|
|
|
|
|
- // first look for points inside the triangle in increasing z-order
|
|
|
+ var p = ear.prevZ,
|
|
|
+ n = ear.nextZ;
|
|
|
|
|
|
- var p = ear.nextZ;
|
|
|
-
|
|
|
- while ( p && p.z <= maxZ ) {
|
|
|
+ // look for points inside the triangle in both directions
|
|
|
+ while ( p && p.z >= minZ && n && n.z <= maxZ ) {
|
|
|
|
|
|
if ( p !== ear.prev && p !== ear.next &&
|
|
|
- pointInTriangle( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) &&
|
|
|
- area( p.prev, p, p.next ) >= 0 ) return false;
|
|
|
- p = p.nextZ;
|
|
|
-
|
|
|
- }
|
|
|
+ pointInTriangle( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) &&
|
|
|
+ area( p.prev, p, p.next ) >= 0 ) return false;
|
|
|
+ p = p.prevZ;
|
|
|
|
|
|
- // then look for points in decreasing z-order
|
|
|
+ if ( n !== ear.prev && n !== ear.next &&
|
|
|
+ pointInTriangle( a.x, a.y, b.x, b.y, c.x, c.y, n.x, n.y ) &&
|
|
|
+ area( n.prev, n, n.next ) >= 0 ) return false;
|
|
|
+ n = n.nextZ;
|
|
|
|
|
|
- p = ear.prevZ;
|
|
|
+ }
|
|
|
|
|
|
+ // look for remaining points in decreasing z-order
|
|
|
while ( p && p.z >= minZ ) {
|
|
|
|
|
|
if ( p !== ear.prev && p !== ear.next &&
|
|
|
- pointInTriangle( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) &&
|
|
|
- area( p.prev, p, p.next ) >= 0 ) return false;
|
|
|
-
|
|
|
+ pointInTriangle( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) &&
|
|
|
+ area( p.prev, p, p.next ) >= 0 ) return false;
|
|
|
p = p.prevZ;
|
|
|
|
|
|
}
|
|
|
|
|
|
+ // look for remaining points in increasing z-order
|
|
|
+ while ( n && n.z <= maxZ ) {
|
|
|
+
|
|
|
+ if ( n !== ear.prev && n !== ear.next &&
|
|
|
+ pointInTriangle( a.x, a.y, b.x, b.y, c.x, c.y, n.x, n.y ) &&
|
|
|
+ area( n.prev, n, n.next ) >= 0 ) return false;
|
|
|
+ n = n.nextZ;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
// go through all polygon nodes and cure small local self-intersections
|
|
|
-
|
|
|
function cureLocalIntersections( start, triangles, dim ) {
|
|
|
|
|
|
var p = start;
|
|
|
-
|
|
|
do {
|
|
|
|
|
|
- var a = p.prev, b = p.next.next;
|
|
|
+ var a = p.prev,
|
|
|
+ b = p.next.next;
|
|
|
|
|
|
if ( ! equals( a, b ) && intersects( a, p, p.next, b ) && locallyInside( a, b ) && locallyInside( b, a ) ) {
|
|
|
|
|
@@ -280,7 +275,6 @@ function cureLocalIntersections( start, triangles, dim ) {
|
|
|
triangles.push( b.i / dim );
|
|
|
|
|
|
// remove two nodes involved
|
|
|
-
|
|
|
removeNode( p );
|
|
|
removeNode( p.next );
|
|
|
|
|
@@ -297,32 +291,25 @@ function cureLocalIntersections( start, triangles, dim ) {
|
|
|
}
|
|
|
|
|
|
// try splitting polygon into two and triangulate them independently
|
|
|
-
|
|
|
function splitEarcut( start, triangles, dim, minX, minY, invSize ) {
|
|
|
|
|
|
// look for a valid diagonal that divides the polygon into two
|
|
|
-
|
|
|
var a = start;
|
|
|
-
|
|
|
do {
|
|
|
|
|
|
var b = a.next.next;
|
|
|
-
|
|
|
while ( b !== a.prev ) {
|
|
|
|
|
|
if ( a.i !== b.i && isValidDiagonal( a, b ) ) {
|
|
|
|
|
|
// split the polygon in two by the diagonal
|
|
|
-
|
|
|
var c = splitPolygon( a, b );
|
|
|
|
|
|
// filter colinear points around the cuts
|
|
|
-
|
|
|
a = filterPoints( a, a.next );
|
|
|
c = filterPoints( c, c.next );
|
|
|
|
|
|
// run earcut on each half
|
|
|
-
|
|
|
earcutLinked( a, triangles, dim, minX, minY, invSize );
|
|
|
earcutLinked( c, triangles, dim, minX, minY, invSize );
|
|
|
return;
|
|
@@ -340,10 +327,10 @@ function splitEarcut( start, triangles, dim, minX, minY, invSize ) {
|
|
|
}
|
|
|
|
|
|
// link every hole into the outer loop, producing a single-ring polygon without holes
|
|
|
-
|
|
|
function eliminateHoles( data, holeIndices, outerNode, dim ) {
|
|
|
|
|
|
- var queue = [], i, len, start, end, list;
|
|
|
+ var queue = [],
|
|
|
+ i, len, start, end, list;
|
|
|
|
|
|
for ( i = 0, len = holeIndices.length; i < len; i ++ ) {
|
|
|
|
|
@@ -358,7 +345,6 @@ function eliminateHoles( data, holeIndices, outerNode, dim ) {
|
|
|
queue.sort( compareX );
|
|
|
|
|
|
// process holes from left to right
|
|
|
-
|
|
|
for ( i = 0; i < queue.length; i ++ ) {
|
|
|
|
|
|
eliminateHole( queue[ i ], outerNode );
|
|
@@ -377,15 +363,12 @@ function compareX( a, b ) {
|
|
|
}
|
|
|
|
|
|
// find a bridge between vertices that connects hole with an outer ring and and link it
|
|
|
-
|
|
|
function eliminateHole( hole, outerNode ) {
|
|
|
|
|
|
outerNode = findHoleBridge( hole, outerNode );
|
|
|
-
|
|
|
if ( outerNode ) {
|
|
|
|
|
|
var b = splitPolygon( outerNode, hole );
|
|
|
-
|
|
|
filterPoints( b, b.next );
|
|
|
|
|
|
}
|
|
@@ -393,7 +376,6 @@ function eliminateHole( hole, outerNode ) {
|
|
|
}
|
|
|
|
|
|
// David Eberly's algorithm for finding a bridge between hole and outer polygon
|
|
|
-
|
|
|
function findHoleBridge( hole, outerNode ) {
|
|
|
|
|
|
var p = outerNode,
|
|
@@ -404,17 +386,14 @@ function findHoleBridge( hole, outerNode ) {
|
|
|
|
|
|
// find a segment intersected by a ray from the hole's leftmost point to the left;
|
|
|
// segment's endpoint with lesser x will be potential connection point
|
|
|
-
|
|
|
do {
|
|
|
|
|
|
if ( hy <= p.y && hy >= p.next.y && p.next.y !== p.y ) {
|
|
|
|
|
|
var x = p.x + ( hy - p.y ) * ( p.next.x - p.x ) / ( p.next.y - p.y );
|
|
|
-
|
|
|
if ( x <= hx && x > qx ) {
|
|
|
|
|
|
qx = x;
|
|
|
-
|
|
|
if ( x === hx ) {
|
|
|
|
|
|
if ( hy === p.y ) return p;
|
|
@@ -451,7 +430,7 @@ function findHoleBridge( hole, outerNode ) {
|
|
|
while ( p !== stop ) {
|
|
|
|
|
|
if ( hx >= p.x && p.x >= mx && hx !== p.x &&
|
|
|
- pointInTriangle( hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y ) ) {
|
|
|
+ pointInTriangle( hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y ) ) {
|
|
|
|
|
|
tan = Math.abs( hy - p.y ) / ( hx - p.x ); // tangential
|
|
|
|
|
@@ -473,11 +452,9 @@ function findHoleBridge( hole, outerNode ) {
|
|
|
}
|
|
|
|
|
|
// interlink polygon nodes in z-order
|
|
|
-
|
|
|
function indexCurve( start, minX, minY, invSize ) {
|
|
|
|
|
|
var p = start;
|
|
|
-
|
|
|
do {
|
|
|
|
|
|
if ( p.z === null ) p.z = zOrder( p.x, p.y, minX, minY, invSize );
|
|
@@ -496,10 +473,10 @@ function indexCurve( start, minX, minY, invSize ) {
|
|
|
|
|
|
// Simon Tatham's linked list merge sort algorithm
|
|
|
// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
|
|
|
-
|
|
|
function sortLinked( list ) {
|
|
|
|
|
|
- var i, p, q, e, tail, numMerges, pSize, qSize, inSize = 1;
|
|
|
+ var i, p, q, e, tail, numMerges, pSize, qSize,
|
|
|
+ inSize = 1;
|
|
|
|
|
|
do {
|
|
|
|
|
@@ -513,7 +490,6 @@ function sortLinked( list ) {
|
|
|
numMerges ++;
|
|
|
q = p;
|
|
|
pSize = 0;
|
|
|
-
|
|
|
for ( i = 0; i < inSize; i ++ ) {
|
|
|
|
|
|
pSize ++;
|
|
@@ -562,11 +538,9 @@ function sortLinked( list ) {
|
|
|
}
|
|
|
|
|
|
// z-order of a point given coords and inverse of the longer side of data bbox
|
|
|
-
|
|
|
function zOrder( x, y, minX, minY, invSize ) {
|
|
|
|
|
|
// coords are transformed into non-negative 15-bit integer range
|
|
|
-
|
|
|
x = 32767 * ( x - minX ) * invSize;
|
|
|
y = 32767 * ( y - minY ) * invSize;
|
|
|
|
|
@@ -585,14 +559,13 @@ function zOrder( x, y, minX, minY, invSize ) {
|
|
|
}
|
|
|
|
|
|
// find the leftmost node of a polygon ring
|
|
|
-
|
|
|
function getLeftmost( start ) {
|
|
|
|
|
|
- var p = start, leftmost = start;
|
|
|
-
|
|
|
+ var p = start,
|
|
|
+ leftmost = start;
|
|
|
do {
|
|
|
|
|
|
- if ( p.x < leftmost.x ) leftmost = p;
|
|
|
+ if ( p.x < leftmost.x || ( p.x === leftmost.x && p.y < leftmost.y ) ) leftmost = p;
|
|
|
p = p.next;
|
|
|
|
|
|
} while ( p !== start );
|
|
@@ -602,26 +575,23 @@ function getLeftmost( start ) {
|
|
|
}
|
|
|
|
|
|
// check if a point lies within a convex triangle
|
|
|
-
|
|
|
function pointInTriangle( ax, ay, bx, by, cx, cy, px, py ) {
|
|
|
|
|
|
return ( cx - px ) * ( ay - py ) - ( ax - px ) * ( cy - py ) >= 0 &&
|
|
|
- ( ax - px ) * ( by - py ) - ( bx - px ) * ( ay - py ) >= 0 &&
|
|
|
- ( bx - px ) * ( cy - py ) - ( cx - px ) * ( by - py ) >= 0;
|
|
|
+ ( ax - px ) * ( by - py ) - ( bx - px ) * ( ay - py ) >= 0 &&
|
|
|
+ ( bx - px ) * ( cy - py ) - ( cx - px ) * ( by - py ) >= 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
// check if a diagonal between two polygon nodes is valid (lies in polygon interior)
|
|
|
-
|
|
|
function isValidDiagonal( a, b ) {
|
|
|
|
|
|
return a.next.i !== b.i && a.prev.i !== b.i && ! intersectsPolygon( a, b ) &&
|
|
|
- locallyInside( a, b ) && locallyInside( b, a ) && middleInside( a, b );
|
|
|
+ locallyInside( a, b ) && locallyInside( b, a ) && middleInside( a, b );
|
|
|
|
|
|
}
|
|
|
|
|
|
// signed area of a triangle
|
|
|
-
|
|
|
function area( p, q, r ) {
|
|
|
|
|
|
return ( q.y - p.y ) * ( r.x - q.x ) - ( q.x - p.x ) * ( r.y - q.y );
|
|
@@ -629,7 +599,6 @@ function area( p, q, r ) {
|
|
|
}
|
|
|
|
|
|
// check if two points are equal
|
|
|
-
|
|
|
function equals( p1, p2 ) {
|
|
|
|
|
|
return p1.x === p2.x && p1.y === p2.y;
|
|
@@ -637,32 +606,23 @@ function equals( p1, p2 ) {
|
|
|
}
|
|
|
|
|
|
// check if two segments intersect
|
|
|
-
|
|
|
function intersects( p1, q1, p2, q2 ) {
|
|
|
|
|
|
- if ( ( equals( p1, q1 ) && equals( p2, q2 ) ) ||
|
|
|
- ( equals( p1, q2 ) && equals( p2, q1 ) ) ) return true;
|
|
|
-
|
|
|
+ if ( ( equals( p1, p2 ) && equals( q1, q2 ) ) ||
|
|
|
+ ( equals( p1, q2 ) && equals( p2, q1 ) ) ) return true;
|
|
|
return area( p1, q1, p2 ) > 0 !== area( p1, q1, q2 ) > 0 &&
|
|
|
- area( p2, q2, p1 ) > 0 !== area( p2, q2, q1 ) > 0;
|
|
|
+ area( p2, q2, p1 ) > 0 !== area( p2, q2, q1 ) > 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
// check if a polygon diagonal intersects any polygon segments
|
|
|
-
|
|
|
function intersectsPolygon( a, b ) {
|
|
|
|
|
|
var p = a;
|
|
|
-
|
|
|
do {
|
|
|
|
|
|
if ( p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i &&
|
|
|
- intersects( p, p.next, a, b ) ) {
|
|
|
-
|
|
|
- return true;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
+ intersects( p, p.next, a, b ) ) return true;
|
|
|
p = p.next;
|
|
|
|
|
|
} while ( p !== a );
|
|
@@ -672,7 +632,6 @@ function intersectsPolygon( a, b ) {
|
|
|
}
|
|
|
|
|
|
// check if a polygon diagonal is locally inside the polygon
|
|
|
-
|
|
|
function locallyInside( a, b ) {
|
|
|
|
|
|
return area( a.prev, a, a.next ) < 0 ?
|
|
@@ -682,23 +641,17 @@ function locallyInside( a, b ) {
|
|
|
}
|
|
|
|
|
|
// check if the middle point of a polygon diagonal is inside the polygon
|
|
|
-
|
|
|
function middleInside( a, b ) {
|
|
|
|
|
|
var p = a,
|
|
|
inside = false,
|
|
|
px = ( a.x + b.x ) / 2,
|
|
|
py = ( a.y + b.y ) / 2;
|
|
|
-
|
|
|
do {
|
|
|
|
|
|
if ( ( ( p.y > py ) !== ( p.next.y > py ) ) && p.next.y !== p.y &&
|
|
|
- ( px < ( p.next.x - p.x ) * ( py - p.y ) / ( p.next.y - p.y ) + p.x ) ) {
|
|
|
-
|
|
|
+ ( px < ( p.next.x - p.x ) * ( py - p.y ) / ( p.next.y - p.y ) + p.x ) )
|
|
|
inside = ! inside;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
p = p.next;
|
|
|
|
|
|
} while ( p !== a );
|
|
@@ -709,7 +662,6 @@ function middleInside( a, b ) {
|
|
|
|
|
|
// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;
|
|
|
// if one belongs to the outer ring and another to a hole, it merges it into a single ring
|
|
|
-
|
|
|
function splitPolygon( a, b ) {
|
|
|
|
|
|
var a2 = new Node( a.i, a.x, a.y ),
|
|
@@ -734,7 +686,6 @@ function splitPolygon( a, b ) {
|
|
|
}
|
|
|
|
|
|
// create a node and optionally link it with previous one (in a circular doubly linked list)
|
|
|
-
|
|
|
function insertNode( i, x, y, last ) {
|
|
|
|
|
|
var p = new Node( i, x, y );
|
|
@@ -769,14 +720,14 @@ function removeNode( p ) {
|
|
|
|
|
|
function Node( i, x, y ) {
|
|
|
|
|
|
- // vertice index in coordinates array
|
|
|
+ // vertex index in coordinates array
|
|
|
this.i = i;
|
|
|
|
|
|
// vertex coordinates
|
|
|
this.x = x;
|
|
|
this.y = y;
|
|
|
|
|
|
- // previous and next vertice nodes in a polygon ring
|
|
|
+ // previous and next vertex nodes in a polygon ring
|
|
|
this.prev = null;
|
|
|
this.next = null;
|
|
|
|
|
@@ -795,7 +746,6 @@ function Node( i, x, y ) {
|
|
|
function signedArea( data, start, end, dim ) {
|
|
|
|
|
|
var sum = 0;
|
|
|
-
|
|
|
for ( var i = start, j = end - dim; i < end; i += dim ) {
|
|
|
|
|
|
sum += ( data[ j ] - data[ i ] ) * ( data[ i + 1 ] + data[ j + 1 ] );
|