Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Earcut: Upgrade to version 2.2.4. #24760

Merged
merged 14 commits into from
Oct 13, 2022
112 changes: 60 additions & 52 deletions src/extras/Earcut.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
/**
* Port from https://github.com/mapbox/earcut (v2.2.2)
* Port from https://github.com/mapbox/earcut (v2.2.4)
*
* There is one bug fix that is different from Earcut v2.2.4. Line 388-389.
*/

const Earcut = {
Expand Down Expand Up @@ -36,11 +38,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;

Expand Down Expand Up @@ -125,9 +127,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 );

Expand Down Expand Up @@ -182,11 +184,20 @@ 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;

Expand All @@ -204,29 +215,31 @@ 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;

// 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 ) &&
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 ) &&
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;

Expand All @@ -235,8 +248,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 ) &&
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;

Expand All @@ -245,8 +258,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 ) &&
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;

Expand All @@ -267,9 +280,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 );
Expand Down Expand Up @@ -307,8 +320,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;

}
Expand Down Expand Up @@ -344,8 +357,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 );

}

Expand All @@ -362,17 +374,20 @@ 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 bridge = findHoleBridge( hole, outerNode );

const b = splitPolygon( outerNode, hole );
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
return filterPoints( bridgeReverse, bridgeReverse.next ); // there is different from the implementation of Earcut v2.2.4
Mugen87 marked this conversation as resolved.
Show resolved Hide resolved
// return filterPoints( bridge, bridge.next );

}

// David Eberly's algorithm for finding a bridge between hole and outer polygon
Expand All @@ -393,15 +408,10 @@ 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

}

}
Expand All @@ -412,8 +422,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
Expand Down Expand Up @@ -462,7 +470,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;
Expand Down Expand Up @@ -546,8 +554,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;
Expand Down Expand Up @@ -582,9 +590,9 @@ 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 );

}

Expand Down Expand Up @@ -761,7 +769,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;
Expand Down