浏览代码

ShapeUtils: Move triangulation code to separate file

Mugen87 7 年之前
父节点
当前提交
a644228a99
共有 5 个文件被更改,包括 849 次插入819 次删除
  1. 31 0
      docs/api/extras/Earcut.html
  2. 2 10
      docs/api/extras/ShapeUtils.html
  3. 1 0
      docs/list.js
  4. 812 0
      src/extras/Earcut.js
  5. 3 809
      src/extras/ShapeUtils.js

+ 31 - 0
docs/api/extras/Earcut.html

@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<base href="../../" />
+		<script src="list.js"></script>
+		<script src="page.js"></script>
+		<link type="text/css" rel="stylesheet" href="page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">
+		An implementation of the earcut polygon triangulation algorithm. The code is a port of [link:https://github.com/mapbox/earcut mapbox/earcut].
+		</div>
+
+		<h2>Methods</h2>
+
+		<h3>[method:Array triangulate]( data, holeIndices, dim )</h3>
+		<div>
+		data -- A flat array of vertice coordinates.<br /><br />
+		holeIndices -- An array of hole indices if any.<br /><br />
+		dim -- The number of coordinates per vertice in the input array.<br /><br />
+
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>

+ 2 - 10
docs/api/extras/ShapeUtils.html

@@ -36,15 +36,7 @@
 		x, y  components of a polygon.<br /><br />
 
 		Used internally by [page:Path Path],
-		[page:ExtrudeGeometry ExtrudeGeometry] and [page:ShapeBufferGeometry ShapeBufferGeometry].
-		</div>
-
-		<h3>[method:null triangulate]( contour, indices )</h3>
-		<div>
-		vertices -- flat array of vertices like [ x0,y0, x1,y1, x2,y2, ... ].<br />
-		holeIndices -- array of hole indices.<br /><br />
-
-		Performs a polygon triangulation based on earcut. The implementation is a port of [link:https://github.com/mapbox/earcut mapbox/earcut].
+		[page:ExtrudeGeometry ExtrudeGeometry] and [page:ShapeGeometry ShapeGeometry].
 		</div>
 
 		<h3>[method:null triangulateShape]( contour, holes )</h3>
@@ -52,7 +44,7 @@
 		contour -- 2D polygon.<br />
 		holes -- array of holes<br /><br />
 
-		Used by [page:ExtrudeGeometry ExtrudeGeometry] and [page:ShapeBufferGeometry ShapeBufferGeometry] to calculate faces in shapes with holes.
+		Used internally by [page:ExtrudeGeometry ExtrudeGeometry] and [page:ShapeGeometry ShapeGeometry] to calculate faces in shapes with holes.
 		</div>
 
 		<h2>Source</h2>

+ 1 - 0
docs/list.js

@@ -103,6 +103,7 @@ var list = {
 		},
 
 		"Extras": {
+			"Earcut": "api/extras/Earcut",
 			"SceneUtils": "api/extras/SceneUtils",
 			"ShapeUtils": "api/extras/ShapeUtils"
 		},

+ 812 - 0
src/extras/Earcut.js

@@ -0,0 +1,812 @@
+/**
+ * @author Mugen87 / https://github.com/Mugen87
+ *
+ * Port from https://github.com/mapbox/earcut (v2.1.2)
+ *
+ */
+
+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 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 ];
+			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;
+
+			}
+
+			// 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;
+
+		}
+
+		earcutLinked( outerNode, triangles, dim, minX, minY, invSize );
+
+		return triangles;
+
+	}
+
+};
+
+// create a circular doubly linked list from polygon points in the specified winding order
+
+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;
+
+}
+
+// eliminate colinear or duplicate points
+
+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;
+
+}
+
+// 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;
+
+	// iterate through ears, slicing them one by one
+
+	while ( ear.prev !== ear.next ) {
+
+		prev = ear.prev;
+		next = ear.next;
+
+		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 );
+
+			removeNode( ear );
+
+			// skipping the next vertice leads to less sliver triangles
+			ear = next.next;
+			stop = next.next;
+
+			continue;
+
+		}
+
+		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 );
+
+				// if this didn't work, try curing all small self-intersections locally
+
+			} else if ( pass === 1 ) {
+
+				ear = cureLocalIntersections( ear, triangles, dim );
+				earcutLinked( ear, triangles, dim, minX, minY, invSize, 2 );
+
+			// as a last resort, try splitting the remaining polygon into two
+
+			} else if ( pass === 2 ) {
+
+				splitEarcut( ear, triangles, dim, minX, minY, invSize );
+
+			}
+
+			break;
+
+		}
+
+	}
+
+}
+
+// check whether a polygon node forms a valid ear with adjacent nodes
+
+function isEar( ear ) {
+
+	var a = ear.prev,
+		b = ear,
+		c = ear.next;
+
+	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
+	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; // 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.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;
+
+	}
+
+	// then look for points in decreasing z-order
+
+	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;
+
+}
+
+// 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;
+
+		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 );
+
+			// remove two nodes involved
+
+			removeNode( p );
+			removeNode( p.next );
+
+			p = start = b;
+
+		}
+
+		p = p.next;
+
+	} while ( p !== start );
+
+	return p;
+
+}
+
+// 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;
+
+			}
+
+			b = b.next;
+
+		}
+
+		a = a.next;
+
+	} while ( a !== start );
+
+}
+
+// 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;
+
+	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 );
+
+	// process holes from left to right
+
+	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;
+
+}
+
+// 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 );
+
+	}
+
+}
+
+// David Eberly's algorithm for finding a bridge between hole and outer polygon
+
+function findHoleBridge( hole, outerNode ) {
+
+	var p = outerNode,
+		hx = hole.x,
+		hy = hole.y,
+		qx = - Infinity,
+		m;
+
+	// 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;
+					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; // hole touches outer segment; pick lower 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
+
+	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 ); // tangential
+
+			if ( ( tan < tanMin || ( tan === tanMin && p.x > m.x ) ) && locallyInside( p, hole ) ) {
+
+				m = p;
+				tanMin = tan;
+
+			}
+
+		}
+
+		p = p.next;
+
+	}
+
+	return m;
+
+}
+
+// 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 );
+		p.prevZ = p.prev;
+		p.nextZ = p.next;
+		p = p.next;
+
+	} while ( p !== start );
+
+	p.prevZ.nextZ = null;
+	p.prevZ = null;
+
+	sortLinked( p );
+
+}
+
+// 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;
+
+	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;
+
+}
+
+// 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;
+
+	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 );
+
+}
+
+// find the leftmost node of a polygon ring
+
+function getLeftmost( start ) {
+
+	var p = start, leftmost = start;
+
+	do {
+
+		if ( p.x < leftmost.x ) leftmost = p;
+		p = p.next;
+
+	} while ( p !== start );
+
+	return leftmost;
+
+}
+
+// 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;
+
+}
+
+// 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 );
+
+}
+
+// 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 );
+
+}
+
+// check if two points are equal
+
+function equals( p1, p2 ) {
+
+	return p1.x === p2.x && p1.y === p2.y;
+
+}
+
+// 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;
+
+	return area( p1, q1, p2 ) > 0 !== area( p1, q1, q2 ) > 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;
+
+		}
+
+		p = p.next;
+
+	} while ( p !== a );
+
+	return false;
+
+}
+
+// check if a polygon diagonal is locally inside the polygon
+
+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;
+
+}
+
+// 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 ) ) {
+
+			inside = ! inside;
+
+		}
+
+		p = p.next;
+
+	} while ( p !== a );
+
+	return inside;
+
+}
+
+// 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 ),
+		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;
+
+}
+
+// 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 );
+
+	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 ) {
+
+	// vertice index in coordinates array
+	this.i = i;
+
+	// vertex coordinates
+	this.x = x;
+	this.y = y;
+
+	// previous and next vertice nodes in a polygon ring
+	this.prev = null;
+	this.next = null;
+
+	// z-order curve value
+	this.z = null;
+
+	// previous and next nodes in z-order
+	this.prevZ = null;
+	this.nextZ = null;
+
+	// indicates whether this is a steiner point
+	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 };

+ 3 - 809
src/extras/ShapeUtils.js

@@ -2,6 +2,8 @@
  * @author zz85 / http://www.lab4games.net/zz85/blog
  */
 
+import { Earcut } from './Earcut.js';
+
 var ShapeUtils = {
 
 	// calculate area of the contour polygon
@@ -27,814 +29,6 @@ var ShapeUtils = {
 
 	},
 
-	triangulate: function ( vertices, holeIndices ) {
-
-		// Port from: https://github.com/mapbox/earcut (v2.1.2)
-
-		function earcut( 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 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 ];
-				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;
-
-				}
-
-				// 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;
-
-			}
-
-			earcutLinked( outerNode, triangles, dim, minX, minY, invSize );
-
-			return triangles;
-
-		}
-
-		// create a circular doubly linked list from polygon points in the specified winding order
-
-		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;
-
-		}
-
-		// eliminate colinear or duplicate points
-
-		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;
-
-		}
-
-		// 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;
-
-			// iterate through ears, slicing them one by one
-
-			while ( ear.prev !== ear.next ) {
-
-				prev = ear.prev;
-				next = ear.next;
-
-				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 );
-
-					removeNode( ear );
-
-					// skipping the next vertice leads to less sliver triangles
-					ear = next.next;
-					stop = next.next;
-
-					continue;
-
-				}
-
-				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 );
-
-						// if this didn't work, try curing all small self-intersections locally
-
-					} else if ( pass === 1 ) {
-
-						ear = cureLocalIntersections( ear, triangles, dim );
-						earcutLinked( ear, triangles, dim, minX, minY, invSize, 2 );
-
-					// as a last resort, try splitting the remaining polygon into two
-
-					} else if ( pass === 2 ) {
-
-						splitEarcut( ear, triangles, dim, minX, minY, invSize );
-
-					}
-
-					break;
-
-				}
-
-			}
-
-		}
-
-		// check whether a polygon node forms a valid ear with adjacent nodes
-
-		function isEar( ear ) {
-
-			var a = ear.prev,
-				b = ear,
-				c = ear.next;
-
-			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
-			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; // 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.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;
-
-			}
-
-			// then look for points in decreasing z-order
-
-			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;
-
-		}
-
-		// 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;
-
-				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 );
-
-					// remove two nodes involved
-
-					removeNode( p );
-					removeNode( p.next );
-
-					p = start = b;
-
-				}
-
-				p = p.next;
-
-			} while ( p !== start );
-
-			return p;
-
-		}
-
-		// 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;
-
-					}
-
-					b = b.next;
-
-				}
-
-				a = a.next;
-
-			} while ( a !== start );
-
-		}
-
-		// 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;
-
-			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 );
-
-			// process holes from left to right
-
-			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;
-
-		}
-
-		// 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 );
-
-			}
-
-		}
-
-		// David Eberly's algorithm for finding a bridge between hole and outer polygon
-
-		function findHoleBridge( hole, outerNode ) {
-
-			var p = outerNode,
-				hx = hole.x,
-				hy = hole.y,
-				qx = - Infinity,
-				m;
-
-			// 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;
-							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; // hole touches outer segment; pick lower 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
-
-			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 ); // tangential
-
-					if ( ( tan < tanMin || ( tan === tanMin && p.x > m.x ) ) && locallyInside( p, hole ) ) {
-
-						m = p;
-						tanMin = tan;
-
-					}
-
-				}
-
-				p = p.next;
-
-			}
-
-			return m;
-
-		}
-
-		// 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 );
-				p.prevZ = p.prev;
-				p.nextZ = p.next;
-				p = p.next;
-
-			} while ( p !== start );
-
-			p.prevZ.nextZ = null;
-			p.prevZ = null;
-
-			sortLinked( p );
-
-		}
-
-		// 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;
-
-			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;
-
-		}
-
-		// 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;
-
-			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 );
-
-		}
-
-		// find the leftmost node of a polygon ring
-
-		function getLeftmost( start ) {
-
-			var p = start, leftmost = start;
-
-			do {
-
-				if ( p.x < leftmost.x ) leftmost = p;
-				p = p.next;
-
-			} while ( p !== start );
-
-			return leftmost;
-
-		}
-
-		// 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;
-
-		}
-
-		// 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 );
-
-		}
-
-		// 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 );
-
-		}
-
-		// check if two points are equal
-
-		function equals( p1, p2 ) {
-
-			return p1.x === p2.x && p1.y === p2.y;
-
-		}
-
-		// 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;
-
-			return area( p1, q1, p2 ) > 0 !== area( p1, q1, q2 ) > 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;
-
-				}
-
-				p = p.next;
-
-			} while ( p !== a );
-
-			return false;
-
-		}
-
-		// check if a polygon diagonal is locally inside the polygon
-
-		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;
-
-		}
-
-		// 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 ) ) {
-
-					inside = ! inside;
-
-				}
-
-				p = p.next;
-
-			} while ( p !== a );
-
-			return inside;
-
-		}
-
-		// 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 ),
-				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;
-
-		}
-
-		// 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 );
-
-			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 ) {
-
-			// vertice index in coordinates array
-			this.i = i;
-
-			// vertex coordinates
-			this.x = x;
-			this.y = y;
-
-			// previous and next vertice nodes in a polygon ring
-			this.prev = null;
-			this.next = null;
-
-			// z-order curve value
-			this.z = null;
-
-			// previous and next nodes in z-order
-			this.prevZ = null;
-			this.nextZ = null;
-
-			// indicates whether this is a steiner point
-			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;
-
-		}
-
-		return earcut( vertices, holeIndices );
-
-	},
-
 	triangulateShape: function ( contour, holes ) {
 
 		function removeDupEndPts( points ) {
@@ -882,7 +76,7 @@ var ShapeUtils = {
 
 		//
 
-		var triangles = ShapeUtils.triangulate( vertices, holeIndices );
+		var triangles = Earcut.triangulate( vertices, holeIndices );
 
 		//