| |
| |
| |
| |
|
|
| var Earcut = { |
|
|
| triangulate: function ( data, holeIndices, dim ) { |
|
|
| dim = dim || 2; |
|
|
| var hasHoles = holeIndices && holeIndices.length, |
| outerLen = hasHoles ? holeIndices[ 0 ] * dim : data.length, |
| outerNode = linkedList( data, 0, outerLen, dim, true ), |
| triangles = []; |
|
|
| if ( ! outerNode ) return triangles; |
|
|
| var minX, minY, maxX, maxY, x, y, invSize; |
|
|
| if ( hasHoles ) outerNode = eliminateHoles( data, holeIndices, outerNode, dim ); |
|
|
| |
|
|
| if ( data.length > 80 * dim ) { |
|
|
| minX = maxX = data[ 0 ]; |
| minY = maxY = data[ 1 ]; |
|
|
| for ( var i = dim; i < outerLen; i += dim ) { |
|
|
| x = data[ i ]; |
| y = data[ i + 1 ]; |
| if ( x < minX ) minX = x; |
| if ( y < minY ) minY = y; |
| if ( x > maxX ) maxX = x; |
| if ( y > maxY ) maxY = y; |
|
|
| } |
|
|
| |
|
|
| invSize = Math.max( maxX - minX, maxY - minY ); |
| invSize = invSize !== 0 ? 1 / invSize : 0; |
|
|
| } |
|
|
| earcutLinked( outerNode, triangles, dim, minX, minY, invSize ); |
|
|
| return triangles; |
|
|
| } |
|
|
| }; |
|
|
| |
|
|
| function linkedList( data, start, end, dim, clockwise ) { |
|
|
| var i, last; |
|
|
| if ( clockwise === ( signedArea( data, start, end, dim ) > 0 ) ) { |
|
|
| for ( i = start; i < end; i += dim ) last = insertNode( i, data[ i ], data[ i + 1 ], last ); |
|
|
| } else { |
|
|
| for ( i = end - dim; i >= start; i -= dim ) last = insertNode( i, data[ i ], data[ i + 1 ], last ); |
|
|
| } |
|
|
| if ( last && equals( last, last.next ) ) { |
|
|
| removeNode( last ); |
| last = last.next; |
|
|
| } |
|
|
| return last; |
|
|
| } |
|
|
| |
|
|
| function filterPoints( start, end ) { |
|
|
| if ( ! start ) return start; |
| if ( ! end ) end = start; |
|
|
| var p = start, again; |
|
|
| do { |
|
|
| again = false; |
|
|
| if ( ! p.steiner && ( equals( p, p.next ) || area( p.prev, p, p.next ) === 0 ) ) { |
|
|
| removeNode( p ); |
| p = end = p.prev; |
| if ( p === p.next ) break; |
| again = true; |
|
|
| } else { |
|
|
| p = p.next; |
|
|
| } |
|
|
| } while ( again || p !== end ); |
|
|
| return end; |
|
|
| } |
|
|
| |
|
|
| function earcutLinked( ear, triangles, dim, minX, minY, invSize, pass ) { |
|
|
| if ( ! ear ) return; |
|
|
| |
|
|
| if ( ! pass && invSize ) indexCurve( ear, minX, minY, invSize ); |
|
|
| var stop = ear, prev, next; |
|
|
| |
|
|
| while ( ear.prev !== ear.next ) { |
|
|
| prev = ear.prev; |
| next = ear.next; |
|
|
| if ( invSize ? isEarHashed( ear, minX, minY, invSize ) : isEar( ear ) ) { |
|
|
| |
| triangles.push( prev.i / dim ); |
| triangles.push( ear.i / dim ); |
| triangles.push( next.i / dim ); |
|
|
| removeNode( ear ); |
|
|
| |
| ear = next.next; |
| stop = next.next; |
|
|
| continue; |
|
|
| } |
|
|
| ear = next; |
|
|
| |
|
|
| if ( ear === stop ) { |
|
|
| |
|
|
| if ( ! pass ) { |
|
|
| earcutLinked( filterPoints( ear ), triangles, dim, minX, minY, invSize, 1 ); |
|
|
| |
|
|
| } else if ( pass === 1 ) { |
|
|
| ear = cureLocalIntersections( ear, triangles, dim ); |
| earcutLinked( ear, triangles, dim, minX, minY, invSize, 2 ); |
|
|
| |
|
|
| } else if ( pass === 2 ) { |
|
|
| splitEarcut( ear, triangles, dim, minX, minY, invSize ); |
|
|
| } |
|
|
| break; |
|
|
| } |
|
|
| } |
|
|
| } |
|
|
| |
|
|
| function isEar( ear ) { |
|
|
| var a = ear.prev, |
| b = ear, |
| c = ear.next; |
|
|
| if ( area( a, b, c ) >= 0 ) return false; |
|
|
| |
| var p = ear.next.next; |
|
|
| 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; |
|
|
| } |
|
|
| p = p.next; |
|
|
| } |
|
|
| return true; |
|
|
| } |
|
|
| function isEarHashed( ear, minX, minY, invSize ) { |
|
|
| var a = ear.prev, |
| b = ear, |
| c = ear.next; |
|
|
| if ( area( a, b, c ) >= 0 ) return false; |
|
|
| |
|
|
| 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 ); |
|
|
| |
|
|
| var minZ = zOrder( minTX, minTY, minX, minY, invSize ), |
| maxZ = zOrder( maxTX, maxTY, minX, minY, invSize ); |
|
|
| |
|
|
| var p = ear.nextZ; |
|
|
| while ( p && p.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; |
|
|
| } |
|
|
| |
|
|
| p = ear.prevZ; |
|
|
| 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; |
|
|
| p = p.prevZ; |
|
|
| } |
|
|
| return true; |
|
|
| } |
|
|
| |
|
|
| function cureLocalIntersections( start, triangles, dim ) { |
|
|
| var p = start; |
|
|
| do { |
|
|
| var a = p.prev, b = p.next.next; |
|
|
| 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 ); |
|
|
| |
|
|
| removeNode( p ); |
| removeNode( p.next ); |
|
|
| p = start = b; |
|
|
| } |
|
|
| p = p.next; |
|
|
| } while ( p !== start ); |
|
|
| return p; |
|
|
| } |
|
|
| |
|
|
| function splitEarcut( start, triangles, dim, minX, minY, invSize ) { |
|
|
| |
|
|
| var a = start; |
|
|
| do { |
|
|
| var b = a.next.next; |
|
|
| while ( b !== a.prev ) { |
|
|
| if ( a.i !== b.i && isValidDiagonal( a, b ) ) { |
|
|
| |
|
|
| var c = splitPolygon( a, b ); |
|
|
| |
|
|
| a = filterPoints( a, a.next ); |
| c = filterPoints( c, c.next ); |
|
|
| |
|
|
| earcutLinked( a, triangles, dim, minX, minY, invSize ); |
| earcutLinked( c, triangles, dim, minX, minY, invSize ); |
| return; |
|
|
| } |
|
|
| b = b.next; |
|
|
| } |
|
|
| a = a.next; |
|
|
| } while ( a !== start ); |
|
|
| } |
|
|
| |
|
|
| function eliminateHoles( data, holeIndices, outerNode, dim ) { |
|
|
| var queue = [], i, len, start, end, list; |
|
|
| for ( i = 0, len = holeIndices.length; i < len; i ++ ) { |
|
|
| start = holeIndices[ i ] * dim; |
| end = i < len - 1 ? holeIndices[ i + 1 ] * dim : data.length; |
| list = linkedList( data, start, end, dim, false ); |
| if ( list === list.next ) list.steiner = true; |
| queue.push( getLeftmost( list ) ); |
|
|
| } |
|
|
| queue.sort( compareX ); |
|
|
| |
|
|
| for ( i = 0; i < queue.length; i ++ ) { |
|
|
| eliminateHole( queue[ i ], outerNode ); |
| outerNode = filterPoints( outerNode, outerNode.next ); |
|
|
| } |
|
|
| return outerNode; |
|
|
| } |
|
|
| function compareX( a, b ) { |
|
|
| return a.x - b.x; |
|
|
| } |
|
|
| |
|
|
| function eliminateHole( hole, outerNode ) { |
|
|
| outerNode = findHoleBridge( hole, outerNode ); |
|
|
| if ( outerNode ) { |
|
|
| var b = splitPolygon( outerNode, hole ); |
|
|
| filterPoints( b, b.next ); |
|
|
| } |
|
|
| } |
|
|
| |
|
|
| function findHoleBridge( hole, outerNode ) { |
|
|
| var p = outerNode, |
| hx = hole.x, |
| hy = hole.y, |
| qx = - Infinity, |
| m; |
|
|
| |
| |
|
|
| 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; |
| if ( hy === p.next.y ) return p.next; |
|
|
| } |
|
|
| m = p.x < p.next.x ? p : p.next; |
|
|
| } |
|
|
| } |
|
|
| p = p.next; |
|
|
| } while ( p !== outerNode ); |
|
|
| if ( ! m ) return null; |
|
|
| if ( hx === qx ) return m.prev; |
|
|
| |
| |
| |
|
|
| var stop = m, |
| mx = m.x, |
| my = m.y, |
| tanMin = Infinity, |
| tan; |
|
|
| p = m.next; |
|
|
| 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 ) ) { |
|
|
| tan = Math.abs( hy - p.y ) / ( hx - p.x ); |
|
|
| if ( ( tan < tanMin || ( tan === tanMin && p.x > m.x ) ) && locallyInside( p, hole ) ) { |
|
|
| m = p; |
| tanMin = tan; |
|
|
| } |
|
|
| } |
|
|
| p = p.next; |
|
|
| } |
|
|
| return m; |
|
|
| } |
|
|
| |
|
|
| function indexCurve( start, minX, minY, invSize ) { |
|
|
| var p = start; |
|
|
| do { |
|
|
| if ( p.z === null ) p.z = zOrder( p.x, p.y, minX, minY, invSize ); |
| p.prevZ = p.prev; |
| p.nextZ = p.next; |
| p = p.next; |
|
|
| } while ( p !== start ); |
|
|
| p.prevZ.nextZ = null; |
| p.prevZ = null; |
|
|
| sortLinked( p ); |
|
|
| } |
|
|
| |
| |
|
|
| function sortLinked( list ) { |
|
|
| var i, p, q, e, tail, numMerges, pSize, qSize, inSize = 1; |
|
|
| do { |
|
|
| p = list; |
| list = null; |
| tail = null; |
| numMerges = 0; |
|
|
| while ( p ) { |
|
|
| numMerges ++; |
| q = p; |
| pSize = 0; |
|
|
| for ( i = 0; i < inSize; i ++ ) { |
|
|
| pSize ++; |
| q = q.nextZ; |
| if ( ! q ) break; |
|
|
| } |
|
|
| qSize = inSize; |
|
|
| while ( pSize > 0 || ( qSize > 0 && q ) ) { |
|
|
| if ( pSize !== 0 && ( qSize === 0 || ! q || p.z <= q.z ) ) { |
|
|
| e = p; |
| p = p.nextZ; |
| pSize --; |
|
|
| } else { |
|
|
| e = q; |
| q = q.nextZ; |
| qSize --; |
|
|
| } |
|
|
| if ( tail ) tail.nextZ = e; |
| else list = e; |
|
|
| e.prevZ = tail; |
| tail = e; |
|
|
| } |
|
|
| p = q; |
|
|
| } |
|
|
| tail.nextZ = null; |
| inSize *= 2; |
|
|
| } while ( numMerges > 1 ); |
|
|
| return list; |
|
|
| } |
|
|
| |
|
|
| function zOrder( x, y, minX, minY, invSize ) { |
|
|
| |
|
|
| x = 32767 * ( x - minX ) * invSize; |
| y = 32767 * ( y - minY ) * invSize; |
|
|
| x = ( x | ( x << 8 ) ) & 0x00FF00FF; |
| x = ( x | ( x << 4 ) ) & 0x0F0F0F0F; |
| x = ( x | ( x << 2 ) ) & 0x33333333; |
| x = ( x | ( x << 1 ) ) & 0x55555555; |
|
|
| y = ( y | ( y << 8 ) ) & 0x00FF00FF; |
| y = ( y | ( y << 4 ) ) & 0x0F0F0F0F; |
| y = ( y | ( y << 2 ) ) & 0x33333333; |
| y = ( y | ( y << 1 ) ) & 0x55555555; |
|
|
| return x | ( y << 1 ); |
|
|
| } |
|
|
| |
|
|
| function getLeftmost( start ) { |
|
|
| var p = start, leftmost = start; |
|
|
| do { |
|
|
| if ( p.x < leftmost.x ) leftmost = p; |
| p = p.next; |
|
|
| } while ( p !== start ); |
|
|
| return leftmost; |
|
|
| } |
|
|
| |
|
|
| 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; |
|
|
| } |
|
|
| |
|
|
| 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 ); |
|
|
| } |
|
|
| |
|
|
| function area( p, q, r ) { |
|
|
| return ( q.y - p.y ) * ( r.x - q.x ) - ( q.x - p.x ) * ( r.y - q.y ); |
|
|
| } |
|
|
| |
|
|
| function equals( p1, p2 ) { |
|
|
| return p1.x === p2.x && p1.y === p2.y; |
|
|
| } |
|
|
| |
|
|
| function intersects( p1, q1, p2, q2 ) { |
|
|
| if ( ( equals( p1, q1 ) && equals( p2, 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; |
|
|
| } |
|
|
| |
|
|
| 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; |
|
|
| } |
|
|
| p = p.next; |
|
|
| } while ( p !== a ); |
|
|
| return false; |
|
|
| } |
|
|
| |
|
|
| function locallyInside( a, b ) { |
|
|
| return area( a.prev, a, a.next ) < 0 ? |
| area( a, b, a.next ) >= 0 && area( a, a.prev, b ) >= 0 : |
| area( a, b, a.prev ) < 0 || area( a, a.next, b ) < 0; |
|
|
| } |
|
|
| |
|
|
| 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 ) ) { |
|
|
| inside = ! inside; |
|
|
| } |
|
|
| p = p.next; |
|
|
| } while ( p !== a ); |
|
|
| return inside; |
|
|
| } |
|
|
| |
| |
|
|
| function splitPolygon( a, b ) { |
|
|
| var a2 = new Node( a.i, a.x, a.y ), |
| b2 = new Node( b.i, b.x, b.y ), |
| an = a.next, |
| bp = b.prev; |
|
|
| a.next = b; |
| b.prev = a; |
|
|
| a2.next = an; |
| an.prev = a2; |
|
|
| b2.next = a2; |
| a2.prev = b2; |
|
|
| bp.next = b2; |
| b2.prev = bp; |
|
|
| return b2; |
|
|
| } |
|
|
| |
|
|
| function insertNode( i, x, y, last ) { |
|
|
| var p = new Node( i, x, y ); |
|
|
| if ( ! last ) { |
|
|
| p.prev = p; |
| p.next = p; |
|
|
| } else { |
|
|
| p.next = last.next; |
| p.prev = last; |
| last.next.prev = p; |
| last.next = p; |
|
|
| } |
|
|
| return p; |
|
|
| } |
|
|
| function removeNode( p ) { |
|
|
| p.next.prev = p.prev; |
| p.prev.next = p.next; |
|
|
| if ( p.prevZ ) p.prevZ.nextZ = p.nextZ; |
| if ( p.nextZ ) p.nextZ.prevZ = p.prevZ; |
|
|
| } |
|
|
| function Node( i, x, y ) { |
|
|
| |
| this.i = i; |
|
|
| |
| this.x = x; |
| this.y = y; |
|
|
| |
| this.prev = null; |
| this.next = null; |
|
|
| |
| this.z = null; |
|
|
| |
| this.prevZ = null; |
| this.nextZ = null; |
|
|
| |
| this.steiner = false; |
|
|
| } |
|
|
| 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 ] ); |
| j = i; |
|
|
| } |
|
|
| return sum; |
|
|
| } |
|
|
| export { Earcut }; |
|
|