diff --git a/Gruntfile.js b/Gruntfile.js index 926ee1e..6097eeb 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -26,8 +26,7 @@ module.exports = function(grunt) { "}(this));\n" }, src: [ - //"node_modules/earcut/dist/earcut.dev.js", - "src/earcut.dev.js", + "node_modules/earcut/src/earcut.js", "node_modules/Color/dist/Color.debug.js", "src/index.js" ], diff --git a/dist/Triangulate.debug.js b/dist/Triangulate.debug.js index 5b46411..299d4a8 100644 --- a/dist/Triangulate.debug.js +++ b/dist/Triangulate.debug.js @@ -1,16 +1,16 @@ -(function(global) { +(function(global) {'use strict'; -var earcut = (function() { +module.exports = earcut; - function earcut(data, holeIndices, dim) { +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 = []; + outerLen = hasHoles ? holeIndices[0] * dim : data.length, + outerNode = linkedList(data, 0, outerLen, dim, true), + triangles = []; if (!outerNode) return triangles; @@ -19,505 +19,511 @@ var earcut = (function() { 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; imaxX) maxX = x; - if (y>maxY) maxY = y; - } + 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 size are later used to transform coords into integers for z-order calculation - size = Math.max(maxX - minX, maxY - minY); + // minX, minY and size are later used to transform coords into integers for z-order calculation + size = Math.max(maxX - minX, maxY - minY); } earcutLinked(outerNode, triangles, dim, minX, minY, size); return triangles; - } +} // create a circular doubly linked list from polygon points in the specified winding order - function linkedList(data, start, end, dim, clockwise) { - var sum = 0, - i, j, last; - - // calculate original winding order of a polygon ring - for (i = start, j = end - dim; i0)) { - for (i = start; i 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); + 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) { +function filterPoints(start, end) { if (!start) return start; if (!end) end = start; var p = start, - again; + again; do { - again = false; + 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) return null; - again = true; + if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) { + removeNode(p); + p = end = p.prev; + if (p === p.next) return null; + again = true; - } else { - p = p.next; - } + } 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, size, pass) { +function earcutLinked(ear, triangles, dim, minX, minY, size, pass) { if (!ear) return; // interlink polygon nodes in z-order if (!pass && size) indexCurve(ear, minX, minY, size); var stop = ear, - prev, next; + prev, next; // iterate through ears, slicing them one by one while (ear.prev !== ear.next) { - prev = ear.prev; - next = ear.next; + prev = ear.prev; + next = ear.next; - if (size ? isEarHashed(ear, minX, minY, size) : isEar(ear)) { - // cut off the triangle - triangles.push(prev.i/dim); - triangles.push(ear.i/dim); - triangles.push(next.i/dim); + if (size ? isEarHashed(ear, minX, minY, size) : isEar(ear)) { + // cut off the triangle + triangles.push(prev.i / dim); + triangles.push(ear.i / dim); + triangles.push(next.i / dim); - removeNode(ear); + removeNode(ear); - // skipping the next vertice leads to less sliver triangles - ear = next.next; - stop = next.next; + // skipping the next vertice leads to less sliver triangles + ear = next.next; + stop = next.next; - continue; - } + continue; + } - ear = next; + 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, size, 1); + // 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, size, 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, size, 2); + // 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, size, 2); - // as a last resort, try splitting the remaining polygon into two - } else if (pass === 2) { - splitEarcut(ear, triangles, dim, minX, minY, size); - } + // as a last resort, try splitting the remaining polygon into two + } else if (pass === 2) { + splitEarcut(ear, triangles, dim, minX, minY, size); + } - break; - } + break; + } } - } +} // check whether a polygon node forms a valid ear with adjacent nodes - function isEar(ear) { +function isEar(ear) { var a = ear.prev, - b = ear, - c = ear.next; + b = ear, + c = ear.next; - if (area(a, b, c)>=0) return false; // reflex, can't be an 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 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; + 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, size) { +function isEarHashed(ear, minX, minY, size) { var a = ear.prev, - b = ear, - c = ear.next; + b = ear, + c = ear.next; - if (area(a, b, c)>=0) return false; // reflex, can't be an ear + 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.xb.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 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, size), - maxZ = zOrder(maxTX, maxTY, minX, minY, size); + maxZ = zOrder(maxTX, maxTY, minX, minY, size); // 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; + 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; + 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) { +function cureLocalIntersections(start, triangles, dim) { var p = start; do { - var a = p.prev, - b = p.next.next; + var a = p.prev, + b = p.next.next; - // a self-intersection where edge (v[i-1],v[i]) intersects (v[i+1],v[i+2]) - if (intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) { + 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); + triangles.push(p.i / dim); + triangles.push(b.i / dim); - // remove two nodes involved - removeNode(p); - removeNode(p.next); + // remove two nodes involved + removeNode(p); + removeNode(p.next); - p = start = b; - } - p = 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, size) { +function splitEarcut(start, triangles, dim, minX, minY, size) { // 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, size); - earcutLinked(c, triangles, dim, minX, minY, size); - return; + 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, size); + earcutLinked(c, triangles, dim, minX, minY, size); + return; + } + b = b.next; } - b = b.next; - } - a = a.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) { +function eliminateHoles(data, holeIndices, outerNode, dim) { var queue = [], - i, len, start, end, list; - - for (i = 0, len = holeIndices.length; i=p.next.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; - m = p.x= p.next.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; + 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, - tanMin = Infinity, - tan; + mx = m.x, + my = m.y, + tanMin = Infinity, + tan; p = m.next; while (p !== stop) { - if (hx>=p.x && p.x>=m.x && - pointInTriangle(hy= p.x && p.x >= mx && + 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 + tan = Math.abs(hy - p.y) / (hx - p.x); // tangential - if ((tanm.x)) && locallyInside(p, hole)) { - m = p; - tanMin = tan; + if ((tan < tanMin || (tan === tanMin && p.x > m.x)) && locallyInside(p, hole)) { + m = p; + tanMin = tan; + } } - } - p = p.next; + p = p.next; } return m; - } +} // interlink polygon nodes in z-order - function indexCurve(start, minX, minY, size) { +function indexCurve(start, minX, minY, size) { var p = start; do { - if (p.z === null) p.z = zOrder(p.x, p.y, minX, minY, size); - p.prevZ = p.prev; - p.nextZ = p.next; - p = p.next; + if (p.z === null) p.z = zOrder(p.x, p.y, minX, minY, size); + 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) { +function sortLinked(list) { var i, p, q, e, tail, numMerges, pSize, qSize, - inSize = 1; + inSize = 1; do { - p = list; - list = null; - tail = null; - numMerges = 0; - - while (p) { - numMerges++; - q = p; - pSize = 0; - for (i = 0; i0 || (qSize>0 && q)) { - - if (pSize === 0) { - e = q; - q = q.nextZ; - qSize--; - } else if (qSize === 0 || !q) { - e = p; - p = p.nextZ; - pSize--; - } else if (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 = 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) { + e = q; + q = q.nextZ; + qSize--; + } else if (qSize === 0 || !q) { + e = p; + p = p.nextZ; + pSize--; + } else if (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; } - p = q; - } - - tail.nextZ = null; - inSize *= 2; + tail.nextZ = null; + inSize *= 2; - } while (numMerges>1); + } while (numMerges > 1); return list; - } +} // z-order of a point given coords and size of the data bounding box - function zOrder(x, y, minX, minY, size) { +function zOrder(x, y, minX, minY, size) { // coords are transformed into non-negative 15-bit integer range - x = 32767*(x - minX)/size; - y = 32767*(y - minY)/size; + x = 32767 * (x - minX) / size; + y = 32767 * (y - minY) / size; - x = (x | (x<<8)) & 0x00FF00FF; - x = (x | (x<<4)) & 0x0F0F0F0F; - x = (x | (x<<2)) & 0x33333333; - x = (x | (x<<1)) & 0x55555555; + 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; + y = (y | (y << 8)) & 0x00FF00FF; + y = (y | (y << 4)) & 0x0F0F0F0F; + y = (y | (y << 2)) & 0x33333333; + y = (y | (y << 1)) & 0x55555555; - return x | (y<<1); - } + return x | (y << 1); +} // find the leftmost node of a polygon ring - function getLeftmost(start) { +function getLeftmost(start) { var p = start, - leftmost = start; + leftmost = start; do { - if (p.x=0 && - (ax - px)*(by - py) - (bx - px)*(ay - py)>=0 && - (bx - px)*(cy - py) - (cx - px)*(by - py)>=0; - } +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 equals(a, b) || a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && - locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b); - } +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); - } +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) { +function equals(p1, p2) { return p1.x === p2.x && p1.y === p2.y; - } +} // check if two segments intersect - function intersects(p1, q1, p2, q2) { - return area(p1, q1, p2)>0 !== area(p1, q1, q2)>0 && - area(p2, q2, p1)>0 !== area(p2, q2, q1)>0; - } +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) { +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; + 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; - } +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) { +function middleInside(a, b) { var p = a, - inside = false, - px = (a.x + b.x)/2, - py = (a.y + b.y)/2; + inside = false, + px = (a.x + b.x) / 2, + py = (a.y + b.y) / 2; do { - if (((p.y>py) !== (p.next.y>py)) && (px<(p.next.x - p.x)*(py - p.y)/(p.next.y - p.y) + p.x)) - inside = !inside; - p = p.next; + if (((p.y > py) !== (p.next.y > py)) && (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) { +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; + b2 = new Node(b.i, b.x, b.y), + an = a.next, + bp = b.prev; a.next = b; b.prev = a; @@ -532,34 +538,34 @@ var earcut = (function() { 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) { +function insertNode(i, x, y, last) { var p = new Node(i, x, y); if (!last) { - p.prev = p; - p.next = p; + p.prev = p; + p.next = p; } else { - p.next = last.next; - p.prev = last; - last.next.prev = p; - last.next = p; + p.next = last.next; + p.prev = last; + last.next.prev = p; + last.next = p; } return p; - } +} - function removeNode(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) { +function Node(i, x, y) { // vertice index in coordinates array this.i = i; @@ -580,11 +586,63 @@ var earcut = (function() { // indicates whether this is a steiner point this.steiner = false; - } +} - return earcut; +// return a percentage difference between the polygon area and its triangulation area; +// used to verify correctness of triangulation +earcut.deviation = function (data, holeIndices, dim, triangles) { + var hasHoles = holeIndices && holeIndices.length; + var outerLen = hasHoles ? holeIndices[0] * dim : data.length; + + var polygonArea = Math.abs(signedArea(data, 0, outerLen, dim)); + if (hasHoles) { + for (var i = 0, len = holeIndices.length; i < len; i++) { + var start = holeIndices[i] * dim; + var end = i < len - 1 ? holeIndices[i + 1] * dim : data.length; + polygonArea -= Math.abs(signedArea(data, start, end, dim)); + } + } -}()); + var trianglesArea = 0; + for (i = 0; i < triangles.length; i += 3) { + var a = triangles[i] * dim; + var b = triangles[i + 1] * dim; + var c = triangles[i + 2] * dim; + trianglesArea += Math.abs( + (data[a] - data[c]) * (data[b + 1] - data[a + 1]) - + (data[a] - data[b]) * (data[c + 1] - data[a + 1])); + } + + return polygonArea === 0 && trianglesArea === 0 ? 0 : + Math.abs((trianglesArea - polygonArea) / polygonArea); +}; + +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; +} + +// turn a polygon in a multi-dimensional array form (e.g. as in GeoJSON) into a form Earcut accepts +earcut.flatten = function (data) { + var dim = data[0][0].length, + result = {vertices: [], holes: [], dimensions: dim}, + holeIndex = 0; + + for (var i = 0; i < data.length; i++) { + for (var j = 0; j < data[i].length; j++) { + for (var d = 0; d < dim; d++) result.vertices.push(data[i][j][d]); + } + if (i > 0) { + holeIndex += data[i - 1].length; + result.holes.push(holeIndex); + } + } + return result; +}; var Color = (function(window) { @@ -1167,5 +1225,4 @@ global.module.exports = Triangulate; } else { global.Triangulate = Triangulate; } - }(this)); diff --git a/dist/Triangulate.js b/dist/Triangulate.js index 10dc6ba..d64f717 100644 --- a/dist/Triangulate.js +++ b/dist/Triangulate.js @@ -1 +1 @@ -!function(a){var b=function(){function a(a,c,e){e=e||2;var f=c&&c.length,g=f?c[0]*e:a.length,h=b(a,0,g,e,!0),j=[];if(!h)return j;var k,l,m,n,o,p,q;if(f&&(h=i(a,c,h,e)),a.length>80*e){k=m=a[0],l=n=a[1];for(var r=e;g>r;r+=e)o=a[r],p=a[r+1],k>o&&(k=o),l>p&&(l=p),o>m&&(m=o),p>n&&(n=p);q=Math.max(m-k,n-l)}return d(h,j,e,k,l,q),j}function b(a,b,c,d,e){var f,g,h,i=0;for(f=b,g=c-d;c>f;f+=d)i+=(a[g]-a[f])*(a[f+1]+a[g+1]),g=f;if(e===i>0)for(f=b;c>f;f+=d)h=z(f,a[f],a[f+1],h);else for(f=c-d;f>=b;f-=d)h=z(f,a[f],a[f+1],h);return h}function c(a,b){if(!a)return a;b||(b=a);var c,d=a;do if(c=!1,d.steiner||!t(d,d.next)&&0!==s(d.prev,d,d.next))d=d.next;else{if(A(d),d=b=d.prev,d===d.next)return null;c=!0}while(c||d!==b);return b}function d(a,b,i,j,k,l,n){if(a){!n&&l&&m(a,j,k,l);for(var o,p,q=a;a.prev!==a.next;)if(o=a.prev,p=a.next,l?f(a,j,k,l):e(a))b.push(o.i/i),b.push(a.i/i),b.push(p.i/i),A(a),a=p.next,q=p.next;else if(a=p,a===q){n?1===n?(a=g(a,b,i),d(a,b,i,j,k,l,2)):2===n&&h(a,b,i,j,k,l):d(c(a),b,i,j,k,l,1);break}}}function e(a){var b=a.prev,c=a,d=a.next;if(s(b,c,d)>=0)return!1;for(var e=a.next.next;e!==a.prev;){if(q(b.x,b.y,c.x,c.y,d.x,d.y,e.x,e.y)&&s(e.prev,e,e.next)>=0)return!1;e=e.next}return!0}function f(a,b,c,d){var e=a.prev,f=a,g=a.next;if(s(e,f,g)>=0)return!1;for(var h=e.xf.x?e.x>g.x?e.x:g.x:f.x>g.x?f.x:g.x,k=e.y>f.y?e.y>g.y?e.y:g.y:f.y>g.y?f.y:g.y,l=o(h,i,b,c,d),m=o(j,k,b,c,d),n=a.nextZ;n&&n.z<=m;){if(n!==a.prev&&n!==a.next&&q(e.x,e.y,f.x,f.y,g.x,g.y,n.x,n.y)&&s(n.prev,n,n.next)>=0)return!1;n=n.nextZ}for(n=a.prevZ;n&&n.z>=l;){if(n!==a.prev&&n!==a.next&&q(e.x,e.y,f.x,f.y,g.x,g.y,n.x,n.y)&&s(n.prev,n,n.next)>=0)return!1;n=n.prevZ}return!0}function g(a,b,c){var d=a;do{var e=d.prev,f=d.next.next;u(e,d,d.next,f)&&w(e,f)&&w(f,e)&&(b.push(e.i/c),b.push(d.i/c),b.push(f.i/c),A(d),A(d.next),d=a=f),d=d.next}while(d!==a);return d}function h(a,b,e,f,g,h){var i=a;do{for(var j=i.next.next;j!==i.prev;){if(i.i!==j.i&&r(i,j)){var k=y(i,j);return i=c(i,i.next),k=c(k,k.next),d(i,b,e,f,g,h),void d(k,b,e,f,g,h)}j=j.next}i=i.next}while(i!==a)}function i(a,d,e,f){var g,h,i,l,m,n=[];for(g=0,h=d.length;h>g;g++)i=d[g]*f,l=h-1>g?d[g+1]*f:a.length,m=b(a,i,l,f,!1),m===m.next&&(m.steiner=!0),n.push(p(m));for(n.sort(j),g=0;g=d.next.y){var h=d.x+(f-d.y)*(d.next.x-d.x)/(d.next.y-d.y);e>=h&&h>g&&(g=h,c=d.x=d.x&&d.x>=c.x&&q(fi||i===k&&d.x>c.x)&&w(d,a)&&(c=d,k=i)),d=d.next;return c}function m(a,b,c,d){var e=a;do null===e.z&&(e.z=o(e.x,e.y,b,c,d)),e.prevZ=e.prev,e.nextZ=e.next,e=e.next;while(e!==a);e.prevZ.nextZ=null,e.prevZ=null,n(e)}function n(a){var b,c,d,e,f,g,h,i,j=1;do{for(c=a,a=null,f=null,g=0;c;){for(g++,d=c,h=0,b=0;j>b&&(h++,d=d.nextZ,d);b++);for(i=j;h>0||i>0&&d;)0===h?(e=d,d=d.nextZ,i--):0!==i&&d?c.z<=d.z?(e=c,c=c.nextZ,h--):(e=d,d=d.nextZ,i--):(e=c,c=c.nextZ,h--),f?f.nextZ=e:a=e,e.prevZ=f,f=e;c=d}f.nextZ=null,j*=2}while(g>1);return a}function o(a,b,c,d,e){return a=32767*(a-c)/e,b=32767*(b-d)/e,a=16711935&(a|a<<8),a=252645135&(a|a<<4),a=858993459&(a|a<<2),a=1431655765&(a|a<<1),b=16711935&(b|b<<8),b=252645135&(b|b<<4),b=858993459&(b|b<<2),b=1431655765&(b|b<<1),a|b<<1}function p(a){var b=a,c=a;do b.x=0&&(a-g)*(d-h)-(c-g)*(b-h)>=0&&(c-g)*(f-h)-(e-g)*(d-h)>=0}function r(a,b){return t(a,b)||a.next.i!==b.i&&a.prev.i!==b.i&&!v(a,b)&&w(a,b)&&w(b,a)&&x(a,b)}function s(a,b,c){return(b.y-a.y)*(c.x-b.x)-(b.x-a.x)*(c.y-b.y)}function t(a,b){return a.x===b.x&&a.y===b.y}function u(a,b,c,d){return s(a,b,c)>0!=s(a,b,d)>0&&s(c,d,a)>0!=s(c,d,b)>0}function v(a,b){var c=a;do{if(c.i!==a.i&&c.next.i!==a.i&&c.i!==b.i&&c.next.i!==b.i&&u(c,c.next,a,b))return!0;c=c.next}while(c!==a);return!1}function w(a,b){return s(a.prev,a,a.next)<0?s(a,b,a.next)>=0&&s(a,a.prev,b)>=0:s(a,b,a.prev)<0||s(a,a.next,b)<0}function x(a,b){var c=a,d=!1,e=(a.x+b.x)/2,f=(a.y+b.y)/2;do c.y>f!=c.next.y>f&&e<(c.next.x-c.x)*(f-c.y)/(c.next.y-c.y)+c.x&&(d=!d),c=c.next;while(c!==a);return d}function y(a,b){var c=new B(a.i,a.x,a.y),d=new B(b.i,b.x,b.y),e=a.next,f=b.prev;return a.next=b,b.prev=a,c.next=e,e.prev=c,d.next=c,c.prev=d,f.next=d,d.prev=f,d}function z(a,b,c,d){var e=new B(a,b,c);return d?(e.next=d.next,e.prev=d,d.next.prev=e,d.next=e):(e.prev=e,e.next=e),e}function A(a){a.next.prev=a.prev,a.prev.next=a.next,a.prevZ&&(a.prevZ.nextZ=a.nextZ),a.nextZ&&(a.nextZ.prevZ=a.prevZ)}function B(a,b,c){this.i=a,this.x=b,this.y=c,this.prev=null,this.next=null,this.z=null,this.prevZ=null,this.nextZ=null,this.steiner=!1}return a}(),c=(function(a){function b(a,b,c){return 0>c&&(c+=1),c>1&&(c-=1),1/6>c?a+6*(b-a)*c:.5>c?b:2/3>c?a+(b-a)*(2/3-c)*6:a}function c(a,b){return Math.min(b,Math.max(0,a||0))}var d={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgrey:"#a9a9a9",darkgreen:"#006400",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",grey:"#808080",green:"#008000",greenyellow:"#adff2f",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgrey:"#d3d3d3",lightgreen:"#90ee90",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",rebeccapurple:"#663399",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"},e=function(a){if(a=a||"","object"==typeof a){var b=a;this.R=c(b.r,max),this.G=c(b.g,max),this.B=c(b.b,max),this.A=void 0!==b.a?c(b.a,1):1,this.isValid=!0}else if("string"==typeof a){a=a.toLowerCase(),a=d[a]||a;var e;(e=a.match(/^#?(\w{2})(\w{2})(\w{2})$/))?(this.R=parseInt(e[1],16)/255,this.G=parseInt(e[2],16)/255,this.B=parseInt(e[3],16)/255,this.A=1,this.isValid=!0):(e=a.match(/rgba?\((\d+)\D+(\d+)\D+(\d+)(\D+([\d.]+))?\)/))&&(this.R=parseInt(e[1],10)/255,this.G=parseInt(e[2],10)/255,this.B=parseInt(e[3],10)/255,this.A=e[4]?parseFloat(e[5]):1,this.isValid=!0)}};return e.prototype={toHSL:function(){var a,b,c=Math.max(this.R,this.G,this.B),d=Math.min(this.R,this.G,this.B),e=(c+d)/2,f=c-d;if(f){switch(b=e>.5?f/(2-c-d):f/(c+d),c){case this.R:a=(this.G-this.B)/f+(this.Gf;f++){for(var k=0;kf;f+=3)this.triangle(a,[h[2*l[f]],h[2*l[f]+1],d],[h[2*l[f+1]],h[2*l[f+1]+1],d],[h[2*l[f+2]],h[2*l[f+2]+1],d],e)},cube:function(a,b,c,d,e,f,g,h){e=e||0,f=f||0,g=g||0;var i=[e,f,g],j=[e+b,f,g],k=[e+b,f+c,g],l=[e,f+c,g],m=[e,f,g+d],n=[e+b,f,g+d],o=[e+b,f+c,g+d],p=[e,f+c,g+d];this.quad(a,j,i,l,k,h),this.quad(a,m,n,o,p,h),this.quad(a,i,j,n,m,h),this.quad(a,j,k,o,n,h),this.quad(a,k,l,p,o,h),this.quad(a,l,i,m,p,h)},cylinder:function(a,b,c,d,e,f,g){f=f||0;for(var h,i,j,k,l,m,n=this.NUM_X_SEGMENTS,o=2*Math.PI,p=0;n>p;p++)h=p/n*o,i=(p+1)/n*o,j=Math.sin(h),k=Math.cos(h),l=Math.sin(i),m=Math.cos(i),this.triangle(a,[b[0]+c*j,b[1]+c*k,f],[b[0]+d*l,b[1]+d*m,f+e],[b[0]+c*l,b[1]+c*m,f],g),0!==d&&this.triangle(a,[b[0]+d*j,b[1]+d*k,f+e],[b[0]+d*l,b[1]+d*m,f+e],[b[0]+c*j,b[1]+c*k,f],g)},dome:function(a,b,c,d,e,f){e=e||0;for(var g,h,i,j,k,l,m,n,o,p,q=this.NUM_Y_SEGMENTS/2,r=Math.PI/2,s=0;q>s;s++)g=s/q*r-r,h=(s+1)/q*r-r,i=Math.sin(g),j=Math.cos(g),k=Math.sin(h),l=Math.cos(h),m=j*c,n=l*c,o=(k-i)*d,p=e-k*d,this.cylinder(a,b,n,m,o,p,f)},sphere:function(a,b,c,d,e,f){return e=e||0,this.cylinder(a,b,c,c,d,e,f)},pyramid:function(a,b,c,d,e,f){e=e||0,b=b[0];for(var g=0,h=b.length-1;h>g;g++)this.triangle(a,[b[g][0],b[g][1],e],[b[g+1][0],b[g+1][1],e],[c[0],c[1],e+d],f)},extrusion:function(a,b,c,d,e,f){d=d||0;for(var g,h,i,j,k,l,m,n,o,p,q,r,s=f[2]*c,t=f[3]*c,u=0,v=b.length;v>u;u++){g=b[u],h=g.length-1,g[0][0]===g[h][0]&&g[0][1]===g[h][1]||(g.push(g[0]),h++);for(var w=0;h>w;w++)i=g[w],j=g[w+1],k=len2(sub2(i,j)),q=f[0]*k<<0,r=f[1]*k<<0,l=[i[0],i[1],d],m=[j[0],j[1],d],n=[j[0],j[1],d+c],o=[i[0],i[1],d+c],p=normal(l,m,n),[].push.apply(a.vertices,[].concat(l,n,m,l,o,n)),[].push.apply(a.normals,[].concat(p,p,p,p,p,p)),[].push.apply(a.colors,[].concat(e,e,e,e,e,e)),a.texCoords.push(q,t,r,s,r,t,q,t,q,s,r,s)}}});"function"==typeof a.define?a.define([],c):"object"==typeof a.exports?a.module.exports=c:a.Triangulate=c}(this); \ No newline at end of file +!function(a){"use strict";function b(a,b,d){d=d||2;var f=b&&b.length,g=f?b[0]*d:a.length,h=c(a,0,g,d,!0),i=[];if(!h)return i;var k,l,m,n,o,p,q;if(f&&(h=j(a,b,h,d)),a.length>80*d){k=m=a[0],l=n=a[1];for(var r=d;g>r;r+=d)o=a[r],p=a[r+1],k>o&&(k=o),l>p&&(l=p),o>m&&(m=o),p>n&&(n=p);q=Math.max(m-k,n-l)}return e(h,i,d,k,l,q),i}function c(a,b,c,d,e){var f,g;if(e===D(a,b,c,d)>0)for(f=b;c>f;f+=d)g=A(f,a[f],a[f+1],g);else for(f=c-d;f>=b;f-=d)g=A(f,a[f],a[f+1],g);return g&&u(g,g.next)&&(B(g),g=g.next),g}function d(a,b){if(!a)return a;b||(b=a);var c,d=a;do if(c=!1,d.steiner||!u(d,d.next)&&0!==t(d.prev,d,d.next))d=d.next;else{if(B(d),d=b=d.prev,d===d.next)return null;c=!0}while(c||d!==b);return b}function e(a,b,c,j,k,l,m){if(a){!m&&l&&n(a,j,k,l);for(var o,p,q=a;a.prev!==a.next;)if(o=a.prev,p=a.next,l?g(a,j,k,l):f(a))b.push(o.i/c),b.push(a.i/c),b.push(p.i/c),B(a),a=p.next,q=p.next;else if(a=p,a===q){m?1===m?(a=h(a,b,c),e(a,b,c,j,k,l,2)):2===m&&i(a,b,c,j,k,l):e(d(a),b,c,j,k,l,1);break}}}function f(a){var b=a.prev,c=a,d=a.next;if(t(b,c,d)>=0)return!1;for(var e=a.next.next;e!==a.prev;){if(r(b.x,b.y,c.x,c.y,d.x,d.y,e.x,e.y)&&t(e.prev,e,e.next)>=0)return!1;e=e.next}return!0}function g(a,b,c,d){var e=a.prev,f=a,g=a.next;if(t(e,f,g)>=0)return!1;for(var h=e.xf.x?e.x>g.x?e.x:g.x:f.x>g.x?f.x:g.x,k=e.y>f.y?e.y>g.y?e.y:g.y:f.y>g.y?f.y:g.y,l=p(h,i,b,c,d),m=p(j,k,b,c,d),n=a.nextZ;n&&n.z<=m;){if(n!==a.prev&&n!==a.next&&r(e.x,e.y,f.x,f.y,g.x,g.y,n.x,n.y)&&t(n.prev,n,n.next)>=0)return!1;n=n.nextZ}for(n=a.prevZ;n&&n.z>=l;){if(n!==a.prev&&n!==a.next&&r(e.x,e.y,f.x,f.y,g.x,g.y,n.x,n.y)&&t(n.prev,n,n.next)>=0)return!1;n=n.prevZ}return!0}function h(a,b,c){var d=a;do{var e=d.prev,f=d.next.next;!u(e,f)&&v(e,d,d.next,f)&&x(e,f)&&x(f,e)&&(b.push(e.i/c),b.push(d.i/c),b.push(f.i/c),B(d),B(d.next),d=a=f),d=d.next}while(d!==a);return d}function i(a,b,c,f,g,h){var i=a;do{for(var j=i.next.next;j!==i.prev;){if(i.i!==j.i&&s(i,j)){var k=z(i,j);return i=d(i,i.next),k=d(k,k.next),e(i,b,c,f,g,h),void e(k,b,c,f,g,h)}j=j.next}i=i.next}while(i!==a)}function j(a,b,e,f){var g,h,i,j,m,n=[];for(g=0,h=b.length;h>g;g++)i=b[g]*f,j=h-1>g?b[g+1]*f:a.length,m=c(a,i,j,f,!1),m===m.next&&(m.steiner=!0),n.push(q(m));for(n.sort(k),g=0;g=d.next.y){var h=d.x+(f-d.y)*(d.next.x-d.x)/(d.next.y-d.y);if(e>=h&&h>g){if(g=h,h===e){if(f===d.y)return d;if(f===d.next.y)return d.next}c=d.x=d.x&&d.x>=k&&r(l>f?e:g,f,k,l,l>f?g:e,f,d.x,d.y)&&(i=Math.abs(f-d.y)/(e-d.x),(m>i||i===m&&d.x>c.x)&&x(d,a)&&(c=d,m=i)),d=d.next;return c}function n(a,b,c,d){var e=a;do null===e.z&&(e.z=p(e.x,e.y,b,c,d)),e.prevZ=e.prev,e.nextZ=e.next,e=e.next;while(e!==a);e.prevZ.nextZ=null,e.prevZ=null,o(e)}function o(a){var b,c,d,e,f,g,h,i,j=1;do{for(c=a,a=null,f=null,g=0;c;){for(g++,d=c,h=0,b=0;j>b&&(h++,d=d.nextZ,d);b++);for(i=j;h>0||i>0&&d;)0===h?(e=d,d=d.nextZ,i--):0!==i&&d?c.z<=d.z?(e=c,c=c.nextZ,h--):(e=d,d=d.nextZ,i--):(e=c,c=c.nextZ,h--),f?f.nextZ=e:a=e,e.prevZ=f,f=e;c=d}f.nextZ=null,j*=2}while(g>1);return a}function p(a,b,c,d,e){return a=32767*(a-c)/e,b=32767*(b-d)/e,a=16711935&(a|a<<8),a=252645135&(a|a<<4),a=858993459&(a|a<<2),a=1431655765&(a|a<<1),b=16711935&(b|b<<8),b=252645135&(b|b<<4),b=858993459&(b|b<<2),b=1431655765&(b|b<<1),a|b<<1}function q(a){var b=a,c=a;do b.x=0&&(a-g)*(d-h)-(c-g)*(b-h)>=0&&(c-g)*(f-h)-(e-g)*(d-h)>=0}function s(a,b){return a.next.i!==b.i&&a.prev.i!==b.i&&!w(a,b)&&x(a,b)&&x(b,a)&&y(a,b)}function t(a,b,c){return(b.y-a.y)*(c.x-b.x)-(b.x-a.x)*(c.y-b.y)}function u(a,b){return a.x===b.x&&a.y===b.y}function v(a,b,c,d){return u(a,b)&&u(c,d)||u(a,d)&&u(c,b)?!0:t(a,b,c)>0!=t(a,b,d)>0&&t(c,d,a)>0!=t(c,d,b)>0}function w(a,b){var c=a;do{if(c.i!==a.i&&c.next.i!==a.i&&c.i!==b.i&&c.next.i!==b.i&&v(c,c.next,a,b))return!0;c=c.next}while(c!==a);return!1}function x(a,b){return t(a.prev,a,a.next)<0?t(a,b,a.next)>=0&&t(a,a.prev,b)>=0:t(a,b,a.prev)<0||t(a,a.next,b)<0}function y(a,b){var c=a,d=!1,e=(a.x+b.x)/2,f=(a.y+b.y)/2;do c.y>f!=c.next.y>f&&e<(c.next.x-c.x)*(f-c.y)/(c.next.y-c.y)+c.x&&(d=!d),c=c.next;while(c!==a);return d}function z(a,b){var c=new C(a.i,a.x,a.y),d=new C(b.i,b.x,b.y),e=a.next,f=b.prev;return a.next=b,b.prev=a,c.next=e,e.prev=c,d.next=c,c.prev=d,f.next=d,d.prev=f,d}function A(a,b,c,d){var e=new C(a,b,c);return d?(e.next=d.next,e.prev=d,d.next.prev=e,d.next=e):(e.prev=e,e.next=e),e}function B(a){a.next.prev=a.prev,a.prev.next=a.next,a.prevZ&&(a.prevZ.nextZ=a.nextZ),a.nextZ&&(a.nextZ.prevZ=a.prevZ)}function C(a,b,c){this.i=a,this.x=b,this.y=c,this.prev=null,this.next=null,this.z=null,this.prevZ=null,this.nextZ=null,this.steiner=!1}function D(a,b,c,d){for(var e=0,f=b,g=c-d;c>f;f+=d)e+=(a[g]-a[f])*(a[f+1]+a[g+1]),g=f;return e}module.exports=b,b.deviation=function(a,b,c,d){var e=b&&b.length,f=e?b[0]*c:a.length,g=Math.abs(D(a,0,f,c));if(e)for(var h=0,i=b.length;i>h;h++){var j=b[h]*c,k=i-1>h?b[h+1]*c:a.length;g-=Math.abs(D(a,j,k,c))}var l=0;for(h=0;hg;g++)c.vertices.push(a[e][f][g]);e>0&&(d+=a[e-1].length,c.holes.push(d))}return c};var E=(function(a){function b(a,b,c){return 0>c&&(c+=1),c>1&&(c-=1),1/6>c?a+6*(b-a)*c:.5>c?b:2/3>c?a+(b-a)*(2/3-c)*6:a}function c(a,b){return Math.min(b,Math.max(0,a||0))}var d={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgrey:"#a9a9a9",darkgreen:"#006400",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",grey:"#808080",green:"#008000",greenyellow:"#adff2f",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgrey:"#d3d3d3",lightgreen:"#90ee90",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",rebeccapurple:"#663399",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"},e=function(a){if(a=a||"","object"==typeof a){var b=a;this.R=c(b.r,max),this.G=c(b.g,max),this.B=c(b.b,max),this.A=void 0!==b.a?c(b.a,1):1,this.isValid=!0}else if("string"==typeof a){a=a.toLowerCase(),a=d[a]||a;var e;(e=a.match(/^#?(\w{2})(\w{2})(\w{2})$/))?(this.R=parseInt(e[1],16)/255,this.G=parseInt(e[2],16)/255,this.B=parseInt(e[3],16)/255,this.A=1,this.isValid=!0):(e=a.match(/rgba?\((\d+)\D+(\d+)\D+(\d+)(\D+([\d.]+))?\)/))&&(this.R=parseInt(e[1],10)/255,this.G=parseInt(e[2],10)/255,this.B=parseInt(e[3],10)/255,this.A=e[4]?parseFloat(e[5]):1,this.isValid=!0)}};return e.prototype={toHSL:function(){var a,b,c=Math.max(this.R,this.G,this.B),d=Math.min(this.R,this.G,this.B),e=(c+d)/2,f=c-d;if(f){switch(b=e>.5?f/(2-c-d):f/(c+d),c){case this.R:a=(this.G-this.B)/f+(this.Gf;f++){for(var k=0;kf;f+=3)this.triangle(a,[h[2*l[f]],h[2*l[f]+1],d],[h[2*l[f+1]],h[2*l[f+1]+1],d],[h[2*l[f+2]],h[2*l[f+2]+1],d],e)},cube:function(a,b,c,d,e,f,g,h){e=e||0,f=f||0,g=g||0;var i=[e,f,g],j=[e+b,f,g],k=[e+b,f+c,g],l=[e,f+c,g],m=[e,f,g+d],n=[e+b,f,g+d],o=[e+b,f+c,g+d],p=[e,f+c,g+d];this.quad(a,j,i,l,k,h),this.quad(a,m,n,o,p,h),this.quad(a,i,j,n,m,h),this.quad(a,j,k,o,n,h),this.quad(a,k,l,p,o,h),this.quad(a,l,i,m,p,h)},cylinder:function(a,b,c,d,e,f,g){f=f||0;for(var h,i,j,k,l,m,n=this.NUM_X_SEGMENTS,o=2*Math.PI,p=0;n>p;p++)h=p/n*o,i=(p+1)/n*o,j=Math.sin(h),k=Math.cos(h),l=Math.sin(i),m=Math.cos(i),this.triangle(a,[b[0]+c*j,b[1]+c*k,f],[b[0]+d*l,b[1]+d*m,f+e],[b[0]+c*l,b[1]+c*m,f],g),0!==d&&this.triangle(a,[b[0]+d*j,b[1]+d*k,f+e],[b[0]+d*l,b[1]+d*m,f+e],[b[0]+c*j,b[1]+c*k,f],g)},dome:function(a,b,c,d,e,f){e=e||0;for(var g,h,i,j,k,l,m,n,o,p,q=this.NUM_Y_SEGMENTS/2,r=Math.PI/2,s=0;q>s;s++)g=s/q*r-r,h=(s+1)/q*r-r,i=Math.sin(g),j=Math.cos(g),k=Math.sin(h),l=Math.cos(h),m=j*c,n=l*c,o=(k-i)*d,p=e-k*d,this.cylinder(a,b,n,m,o,p,f)},sphere:function(a,b,c,d,e,f){return e=e||0,this.cylinder(a,b,c,c,d,e,f)},pyramid:function(a,b,c,d,e,f){e=e||0,b=b[0];for(var g=0,h=b.length-1;h>g;g++)this.triangle(a,[b[g][0],b[g][1],e],[b[g+1][0],b[g+1][1],e],[c[0],c[1],e+d],f)},extrusion:function(a,b,c,d,e,f){d=d||0;for(var g,h,i,j,k,l,m,n,o,p,q,r,s=f[2]*c,t=f[3]*c,u=0,v=b.length;v>u;u++){g=b[u],h=g.length-1,g[0][0]===g[h][0]&&g[0][1]===g[h][1]||(g.push(g[0]),h++);for(var w=0;h>w;w++)i=g[w],j=g[w+1],k=len2(sub2(i,j)),q=f[0]*k<<0,r=f[1]*k<<0,l=[i[0],i[1],d],m=[j[0],j[1],d],n=[j[0],j[1],d+c],o=[i[0],i[1],d+c],p=normal(l,m,n),[].push.apply(a.vertices,[].concat(l,n,m,l,o,n)),[].push.apply(a.normals,[].concat(p,p,p,p,p,p)),[].push.apply(a.colors,[].concat(e,e,e,e,e,e)),a.texCoords.push(q,t,r,s,r,t,q,t,q,s,r,s)}}});"function"==typeof a.define?a.define([],E):"object"==typeof a.exports?a.module.exports=E:a.Triangulate=E}(this); \ No newline at end of file diff --git a/package.json b/package.json index 5cf919e..86edc1a 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ }, "author": "@kekscom", "dependencies": { - "Color": "git://github.com/kekscom/Color.git" + "Color": "git://github.com/kekscom/Color.git", + "earcut": "^2.1.1" }, "devDependencies": { "grunt": "^0.4.4", diff --git a/src/earcut.dev.js b/src/earcut.dev.js deleted file mode 100644 index 2997df5..0000000 --- a/src/earcut.dev.js +++ /dev/null @@ -1,586 +0,0 @@ - - -var earcut = (function() { - - 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, size; - - 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; imaxX) maxX = x; - if (y>maxY) maxY = y; - } - - // minX, minY and size are later used to transform coords into integers for z-order calculation - size = Math.max(maxX - minX, maxY - minY); - } - - earcutLinked(outerNode, triangles, dim, minX, minY, size); - - return triangles; - } - -// create a circular doubly linked list from polygon points in the specified winding order - function linkedList(data, start, end, dim, clockwise) { - var sum = 0, - i, j, last; - - // calculate original winding order of a polygon ring - for (i = start, j = end - dim; i0)) { - for (i = start; i=start; i -= dim) last = insertNode(i, data[i], data[i + 1], last); - } - - 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) return null; - 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, size, pass) { - if (!ear) return; - - // interlink polygon nodes in z-order - if (!pass && size) indexCurve(ear, minX, minY, size); - - 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 (size ? isEarHashed(ear, minX, minY, size) : 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, size, 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, size, 2); - - // as a last resort, try splitting the remaining polygon into two - } else if (pass === 2) { - splitEarcut(ear, triangles, dim, minX, minY, size); - } - - 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, size) { - 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.xb.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, size), - maxZ = zOrder(maxTX, maxTY, minX, minY, size); - - // 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; - - // a self-intersection where edge (v[i-1],v[i]) intersects (v[i+1],v[i+2]) - if (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, size) { - // 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, size); - earcutLinked(c, triangles, dim, minX, minY, size); - 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=p.next.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; - m = p.x=p.x && p.x>=m.x && - pointInTriangle(hym.x)) && locallyInside(p, hole)) { - m = p; - tanMin = tan; - } - } - - p = p.next; - } - - return m; - } - -// interlink polygon nodes in z-order - function indexCurve(start, minX, minY, size) { - var p = start; - do { - if (p.z === null) p.z = zOrder(p.x, p.y, minX, minY, size); - 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; i0 || (qSize>0 && q)) { - - if (pSize === 0) { - e = q; - q = q.nextZ; - qSize--; - } else if (qSize === 0 || !q) { - e = p; - p = p.nextZ; - pSize--; - } else if (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 size of the data bounding box - function zOrder(x, y, minX, minY, size) { - // coords are transformed into non-negative 15-bit integer range - x = 32767*(x - minX)/size; - y = 32767*(y - minY)/size; - - 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=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 equals(a, b) || 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) { - 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)) && (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; - } - - return earcut; - -}());