|
@@ -1,5 +1,5 @@
|
|
|
/**
|
|
|
- * Port from https://github.com/mapbox/earcut (v2.2.2)
|
|
|
+ * Port from https://github.com/mapbox/earcut (v2.2.4)
|
|
|
*/
|
|
|
|
|
|
const Earcut = {
|
|
@@ -36,11 +36,11 @@ const 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;
|
|
|
+ invSize = invSize !== 0 ? 32767 / invSize : 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
- earcutLinked( outerNode, triangles, dim, minX, minY, invSize );
|
|
|
+ earcutLinked( outerNode, triangles, dim, minX, minY, invSize, 0 );
|
|
|
|
|
|
return triangles;
|
|
|
|
|
@@ -125,9 +125,9 @@ function earcutLinked( ear, triangles, dim, minX, minY, invSize, pass ) {
|
|
|
if ( invSize ? isEarHashed( ear, minX, minY, invSize ) : isEar( ear ) ) {
|
|
|
|
|
|
// cut off the triangle
|
|
|
- triangles.push( prev.i / dim );
|
|
|
- triangles.push( ear.i / dim );
|
|
|
- triangles.push( next.i / dim );
|
|
|
+ triangles.push( prev.i / dim | 0 );
|
|
|
+ triangles.push( ear.i / dim | 0 );
|
|
|
+ triangles.push( next.i / dim | 0 );
|
|
|
|
|
|
removeNode( ear );
|
|
|
|
|
@@ -182,11 +182,19 @@ function isEar( ear ) {
|
|
|
if ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear
|
|
|
|
|
|
// now make sure we don't have other points inside the potential ear
|
|
|
- let p = ear.next.next;
|
|
|
+ const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y;
|
|
|
|
|
|
- while ( p !== ear.prev ) {
|
|
|
+ // triangle bbox; min & max are calculated like this for speed
|
|
|
+ const x0 = ax < bx ? ( ax < cx ? ax : cx ) : ( bx < cx ? bx : cx ),
|
|
|
+ y0 = ay < by ? ( ay < cy ? ay : cy ) : ( by < cy ? by : cy ),
|
|
|
+ x1 = ax > bx ? ( ax > cx ? ax : cx ) : ( bx > cx ? bx : cx ),
|
|
|
+ y1 = ay > by ? ( ay > cy ? ay : cy ) : ( by > cy ? by : cy );
|
|
|
+
|
|
|
+ let p = c.next;
|
|
|
+ while ( p !== a ) {
|
|
|
|
|
|
- if ( pointInTriangle( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) &&
|
|
|
+ if ( p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 &&
|
|
|
+ pointInTriangle( ax, ay, bx, by, cx, cy, p.x, p.y ) &&
|
|
|
area( p.prev, p, p.next ) >= 0 ) return false;
|
|
|
p = p.next;
|
|
|
|
|
@@ -204,15 +212,17 @@ function isEarHashed( ear, minX, minY, invSize ) {
|
|
|
|
|
|
if ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear
|
|
|
|
|
|
+ const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y;
|
|
|
+
|
|
|
// triangle bbox; min & max are calculated like this for speed
|
|
|
- const 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 );
|
|
|
+ const x0 = ax < bx ? ( ax < cx ? ax : cx ) : ( bx < cx ? bx : cx ),
|
|
|
+ y0 = ay < by ? ( ay < cy ? ay : cy ) : ( by < cy ? by : cy ),
|
|
|
+ x1 = ax > bx ? ( ax > cx ? ax : cx ) : ( bx > cx ? bx : cx ),
|
|
|
+ y1 = ay > by ? ( ay > cy ? ay : cy ) : ( by > cy ? by : cy );
|
|
|
|
|
|
// z-order range for the current triangle bbox;
|
|
|
- const minZ = zOrder( minTX, minTY, minX, minY, invSize ),
|
|
|
- maxZ = zOrder( maxTX, maxTY, minX, minY, invSize );
|
|
|
+ const minZ = zOrder( x0, y0, minX, minY, invSize ),
|
|
|
+ maxZ = zOrder( x1, y1, minX, minY, invSize );
|
|
|
|
|
|
let p = ear.prevZ,
|
|
|
n = ear.nextZ;
|
|
@@ -220,14 +230,12 @@ function isEarHashed( ear, minX, minY, invSize ) {
|
|
|
// 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;
|
|
|
+ if ( p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c &&
|
|
|
+ pointInTriangle( ax, ay, bx, by, cx, cy, p.x, p.y ) && area( p.prev, p, p.next ) >= 0 ) return false;
|
|
|
p = p.prevZ;
|
|
|
|
|
|
- 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;
|
|
|
+ if ( n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c &&
|
|
|
+ pointInTriangle( ax, ay, bx, by, cx, cy, n.x, n.y ) && area( n.prev, n, n.next ) >= 0 ) return false;
|
|
|
n = n.nextZ;
|
|
|
|
|
|
}
|
|
@@ -235,9 +243,8 @@ function isEarHashed( ear, minX, minY, invSize ) {
|
|
|
// 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;
|
|
|
+ if ( p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c &&
|
|
|
+ pointInTriangle( ax, ay, bx, by, cx, cy, p.x, p.y ) && area( p.prev, p, p.next ) >= 0 ) return false;
|
|
|
p = p.prevZ;
|
|
|
|
|
|
}
|
|
@@ -245,9 +252,8 @@ function isEarHashed( ear, minX, minY, invSize ) {
|
|
|
// 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;
|
|
|
+ if ( n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c &&
|
|
|
+ pointInTriangle( ax, ay, bx, by, cx, cy, n.x, n.y ) && area( n.prev, n, n.next ) >= 0 ) return false;
|
|
|
n = n.nextZ;
|
|
|
|
|
|
}
|
|
@@ -267,9 +273,9 @@ function cureLocalIntersections( start, triangles, dim ) {
|
|
|
|
|
|
if ( ! equals( a, b ) && intersects( a, p, p.next, b ) && locallyInside( a, b ) && locallyInside( b, a ) ) {
|
|
|
|
|
|
- triangles.push( a.i / dim );
|
|
|
- triangles.push( p.i / dim );
|
|
|
- triangles.push( b.i / dim );
|
|
|
+ triangles.push( a.i / dim | 0 );
|
|
|
+ triangles.push( p.i / dim | 0 );
|
|
|
+ triangles.push( b.i / dim | 0 );
|
|
|
|
|
|
// remove two nodes involved
|
|
|
removeNode( p );
|
|
@@ -307,8 +313,8 @@ function splitEarcut( start, triangles, dim, minX, minY, invSize ) {
|
|
|
c = filterPoints( c, c.next );
|
|
|
|
|
|
// run earcut on each half
|
|
|
- earcutLinked( a, triangles, dim, minX, minY, invSize );
|
|
|
- earcutLinked( c, triangles, dim, minX, minY, invSize );
|
|
|
+ earcutLinked( a, triangles, dim, minX, minY, invSize, 0 );
|
|
|
+ earcutLinked( c, triangles, dim, minX, minY, invSize, 0 );
|
|
|
return;
|
|
|
|
|
|
}
|
|
@@ -344,8 +350,7 @@ function eliminateHoles( data, holeIndices, outerNode, dim ) {
|
|
|
// process holes from left to right
|
|
|
for ( i = 0; i < queue.length; i ++ ) {
|
|
|
|
|
|
- eliminateHole( queue[ i ], outerNode );
|
|
|
- outerNode = filterPoints( outerNode, outerNode.next );
|
|
|
+ outerNode = eliminateHole( queue[ i ], outerNode );
|
|
|
|
|
|
}
|
|
|
|
|
@@ -362,26 +367,29 @@ function compareX( a, b ) {
|
|
|
// find a bridge between vertices that connects hole with an outer ring and link it
|
|
|
function eliminateHole( hole, outerNode ) {
|
|
|
|
|
|
- outerNode = findHoleBridge( hole, outerNode );
|
|
|
- if ( outerNode ) {
|
|
|
-
|
|
|
- const b = splitPolygon( outerNode, hole );
|
|
|
+ const bridge = findHoleBridge( hole, outerNode );
|
|
|
+ if ( ! bridge ) {
|
|
|
|
|
|
- // filter collinear points around the cuts
|
|
|
- filterPoints( outerNode, outerNode.next );
|
|
|
- filterPoints( b, b.next );
|
|
|
+ return outerNode;
|
|
|
|
|
|
}
|
|
|
|
|
|
+ const bridgeReverse = splitPolygon( bridge, hole );
|
|
|
+
|
|
|
+ // filter collinear points around the cuts
|
|
|
+ filterPoints( bridgeReverse, bridgeReverse.next );
|
|
|
+ return filterPoints( bridge, bridge.next );
|
|
|
+
|
|
|
}
|
|
|
|
|
|
// David Eberly's algorithm for finding a bridge between hole and outer polygon
|
|
|
function findHoleBridge( hole, outerNode ) {
|
|
|
|
|
|
- let p = outerNode;
|
|
|
- const hx = hole.x;
|
|
|
- const hy = hole.y;
|
|
|
- let qx = - Infinity, m;
|
|
|
+ let p = outerNode,
|
|
|
+ qx = - Infinity,
|
|
|
+ m;
|
|
|
+
|
|
|
+ const hx = hole.x, hy = hole.y;
|
|
|
|
|
|
// 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
|
|
@@ -393,14 +401,8 @@ function findHoleBridge( hole, outerNode ) {
|
|
|
if ( x <= hx && x > qx ) {
|
|
|
|
|
|
qx = x;
|
|
|
- if ( x === hx ) {
|
|
|
-
|
|
|
- if ( hy === p.y ) return p;
|
|
|
- if ( hy === p.next.y ) return p.next;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
m = p.x < p.next.x ? p : p.next;
|
|
|
+ if ( x === hx ) return m; // hole touches outer segment; pick leftmost endpoint
|
|
|
|
|
|
}
|
|
|
|
|
@@ -412,8 +414,6 @@ function findHoleBridge( hole, outerNode ) {
|
|
|
|
|
|
if ( ! m ) return null;
|
|
|
|
|
|
- if ( hx === qx ) return m; // hole touches outer segment; pick leftmost endpoint
|
|
|
-
|
|
|
// look for points inside the triangle of hole point, segment intersection and endpoint;
|
|
|
// if there are no points found, we have a valid connection;
|
|
|
// otherwise choose the point of the minimum angle with the ray as connection point
|
|
@@ -462,7 +462,7 @@ function indexCurve( start, minX, minY, invSize ) {
|
|
|
let p = start;
|
|
|
do {
|
|
|
|
|
|
- if ( p.z === null ) p.z = zOrder( p.x, p.y, minX, minY, invSize );
|
|
|
+ if ( p.z === 0 ) p.z = zOrder( p.x, p.y, minX, minY, invSize );
|
|
|
p.prevZ = p.prev;
|
|
|
p.nextZ = p.next;
|
|
|
p = p.next;
|
|
@@ -546,8 +546,8 @@ function sortLinked( list ) {
|
|
|
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;
|
|
|
+ x = ( x - minX ) * invSize | 0;
|
|
|
+ y = ( y - minY ) * invSize | 0;
|
|
|
|
|
|
x = ( x | ( x << 8 ) ) & 0x00FF00FF;
|
|
|
x = ( x | ( x << 4 ) ) & 0x0F0F0F0F;
|
|
@@ -582,19 +582,19 @@ 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;
|
|
|
+ return ( cx - px ) * ( ay - py ) >= ( ax - px ) * ( cy - py ) &&
|
|
|
+ ( ax - px ) * ( by - py ) >= ( bx - px ) * ( ay - py ) &&
|
|
|
+ ( bx - px ) * ( cy - py ) >= ( cx - px ) * ( by - py );
|
|
|
|
|
|
}
|
|
|
|
|
|
// 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 ) && // doesn't intersect other edges
|
|
|
- ( locallyInside( a, b ) && locallyInside( b, a ) && middleInside( a, b ) && // locally visible
|
|
|
- ( area( a.prev, a, b.prev ) || area( a, b.prev, b ) ) || // does not create opposite-facing sectors
|
|
|
- equals( a, b ) && area( a.prev, a, a.next ) > 0 && area( b.prev, b, b.next ) > 0 ); // special zero-length case
|
|
|
+ return a.next.i !== b.i && a.prev.i !== b.i && ! intersectsPolygon( a, b ) && // dones't intersect other edges
|
|
|
+ ( locallyInside( a, b ) && locallyInside( b, a ) && middleInside( a, b ) && // locally visible
|
|
|
+ ( area( a.prev, a, b.prev ) || area( a, b.prev, b ) ) || // does not create opposite-facing sectors
|
|
|
+ equals( a, b ) && area( a.prev, a, a.next ) > 0 && area( b.prev, b, b.next ) > 0 ); // special zero-length case
|
|
|
|
|
|
}
|
|
|
|
|
@@ -651,7 +651,7 @@ function intersectsPolygon( a, b ) {
|
|
|
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 );
|
|
@@ -679,7 +679,7 @@ function middleInside( a, b ) {
|
|
|
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;
|
|
|
|
|
@@ -761,7 +761,7 @@ function Node( i, x, y ) {
|
|
|
this.next = null;
|
|
|
|
|
|
// z-order curve value
|
|
|
- this.z = null;
|
|
|
+ this.z = 0;
|
|
|
|
|
|
// previous and next nodes in z-order
|
|
|
this.prevZ = null;
|