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
Binary file modified examples/screenshots/webgl_modifier_tessellation.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
134 changes: 67 additions & 67 deletions src/extras/Earcut.js
Original file line number Diff line number Diff line change
@@ -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 = {
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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 );

Expand Down Expand Up @@ -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;

Expand All @@ -204,50 +212,48 @@ 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 ) &&
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;

}

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

}

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

}
Expand All @@ -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 );
Expand Down Expand Up @@ -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;

}
Expand Down Expand Up @@ -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 );

}

Expand All @@ -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
Expand All @@ -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

}

Expand All @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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

}

Expand Down Expand Up @@ -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 );
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand Down