From d704dde2880162d4b22fa0923c76bbd0cf49cc10 Mon Sep 17 00:00:00 2001 From: "Mr.doob" Date: Mon, 12 Jun 2017 12:50:34 -0700 Subject: [PATCH] Updated to latest earcut. --- examples/js/libs/earcut.js | 558 ++++++++++++++++++------------------- 1 file changed, 269 insertions(+), 289 deletions(-) diff --git a/examples/js/libs/earcut.js b/examples/js/libs/earcut.js index 8c2b8fe9685ab6..855dfd31b43b5c 100644 --- a/examples/js/libs/earcut.js +++ b/examples/js/libs/earcut.js @@ -2,7 +2,7 @@ * * Earcut https://github.com/mapbox/earcut * - * Copyright (c) 2015, Mapbox + * Copyright (c) 2016, Mapbox * * Permission to use, copy, modify, and/or distribute this software for any purpose * with or without fee is hereby granted, provided that the above copyright notice @@ -26,7 +26,7 @@ function earcut(data, holeIndices, dim) { var hasHoles = holeIndices && holeIndices.length, outerLen = hasHoles ? holeIndices[0] * dim : data.length, - outerNode = filterPoints(data, linkedList(data, 0, outerLen, dim, true)), + outerNode = linkedList(data, 0, outerLen, dim, true), triangles = []; if (!outerNode) return triangles; @@ -53,62 +53,59 @@ function earcut(data, holeIndices, dim) { size = Math.max(maxX - minX, maxY - minY); } - earcutLinked(data, outerNode, triangles, dim, minX, minY, size); + 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; + var i, last; - // calculate original winding order of a polygon ring - for (i = start, j = end - dim; i < end; i += dim) { - sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]); - j = i; + 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); } - // link points into circular doubly-linked list in the specified winding order - if (clockwise === (sum > 0)) { - for (i = start; i < end; i += dim) last = insertNode(i, last); - } else { - for (i = end - dim; i >= start; i -= dim) last = insertNode(i, last); + if (last && equals(last, last.next)) { + removeNode(last); + last = last.next; } return last; } // eliminate colinear or duplicate points -function filterPoints(data, start, end) { +function filterPoints(start, end) { if (!start) return start; if (!end) end = start; - var node = start, + var p = start, again; do { again = false; - if (!node.steiner && (equals(data, node.i, node.next.i) || orient(data, node.prev.i, node.i, node.next.i) === 0)) { - removeNode(node); - node = end = node.prev; - if (node === node.next) return null; + 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 { - node = node.next; + p = p.next; } - } while (again || node !== end); + } while (again || p !== end); return end; } // main ear slicing loop which triangulates a polygon (given as a linked list) -function earcutLinked(data, 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 && minX !== undefined) indexCurve(data, ear, minX, minY, size); + if (!pass && size) indexCurve(ear, minX, minY, size); var stop = ear, prev, next; @@ -118,7 +115,7 @@ function earcutLinked(data, ear, triangles, dim, minX, minY, size, pass) { prev = ear.prev; next = ear.next; - if (isEar(data, ear, minX, minY, size)) { + if (size ? isEarHashed(ear, minX, minY, size) : isEar(ear)) { // cut off the triangle triangles.push(prev.i / dim); triangles.push(ear.i / dim); @@ -139,16 +136,16 @@ function earcutLinked(data, ear, triangles, dim, minX, minY, size, pass) { if (ear === stop) { // try filtering points and slicing again if (!pass) { - earcutLinked(data, filterPoints(data, ear), triangles, dim, minX, minY, size, 1); + 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(data, ear, triangles, dim); - earcutLinked(data, ear, triangles, dim, minX, minY, size, 2); + 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(data, ear, triangles, dim, minX, minY, size); + splitEarcut(ear, triangles, dim, minX, minY, size); } break; @@ -157,158 +154,108 @@ function earcutLinked(data, ear, triangles, dim, minX, minY, size, pass) { } // check whether a polygon node forms a valid ear with adjacent nodes -function isEar(data, ear, minX, minY, size) { - - var a = ear.prev.i, - b = ear.i, - c = ear.next.i, - - ax = data[a], ay = data[a + 1], - bx = data[b], by = data[b + 1], - cx = data[c], cy = data[c + 1], - - abd = ax * by - ay * bx, - acd = ax * cy - ay * cx, - cbd = cx * by - cy * bx, - A = abd - acd - cbd; - - if (A <= 0) return false; // reflex, can't be an ear - - // now make sure we don't have other points inside the potential ear; - // the code below is a bit verbose and repetitive but this is done for performance - - var cay = cy - ay, - acx = ax - cx, - aby = ay - by, - bax = bx - ax, - i, px, py, s, t, k, node; - - // if we use z-order curve hashing, iterate through the curve - if (minX !== undefined) { - - // triangle bbox; min & max are calculated like this for speed - var minTX = ax < bx ? (ax < cx ? ax : cx) : (bx < cx ? bx : cx), - minTY = ay < by ? (ay < cy ? ay : cy) : (by < cy ? by : cy), - maxTX = ax > bx ? (ax > cx ? ax : cx) : (bx > cx ? bx : cx), - maxTY = ay > by ? (ay > cy ? ay : cy) : (by > cy ? by : cy), - - // z-order range for the current triangle bbox; - 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 - node = ear.nextZ; - - while (node && node.z <= maxZ) { - i = node.i; - node = node.nextZ; - if (i === a || i === c) continue; - - px = data[i]; - py = data[i + 1]; - - s = cay * px + acx * py - acd; - if (s >= 0) { - t = aby * px + bax * py + abd; - if (t >= 0) { - k = A - s - t; - if ((k >= 0) && ((s && t) || (s && k) || (t && k))) return false; - } - } - } +function isEar(ear) { + var a = ear.prev, + b = ear, + c = ear.next; - // then look for points in decreasing z-order - node = ear.prevZ; + if (area(a, b, c) >= 0) return false; // reflex, can't be an ear - while (node && node.z >= minZ) { - i = node.i; - node = node.prevZ; - if (i === a || i === c) continue; + // now make sure we don't have other points inside the potential ear + var p = ear.next.next; - px = data[i]; - py = data[i + 1]; + 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; + } - s = cay * px + acx * py - acd; - if (s >= 0) { - t = aby * px + bax * py + abd; - if (t >= 0) { - k = A - s - t; - if ((k >= 0) && ((s && t) || (s && k) || (t && k))) return false; - } - } - } + return true; +} - // if we don't use z-order curve hash, simply iterate through all other points - } else { - node = ear.next.next; +function isEarHashed(ear, minX, minY, size) { + var a = ear.prev, + b = ear, + c = ear.next; - while (node !== ear.prev) { - i = node.i; - node = node.next; + if (area(a, b, c) >= 0) return false; // reflex, can't be an ear - px = data[i]; - py = data[i + 1]; + // 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); - s = cay * px + acx * py - acd; - if (s >= 0) { - t = aby * px + bax * py + abd; - if (t >= 0) { - k = A - s - t; - if ((k >= 0) && ((s && t) || (s && k) || (t && k))) return false; - } - } - } + // 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(data, start, triangles, dim) { - var node = start; +function cureLocalIntersections(start, triangles, dim) { + var p = start; do { - var a = node.prev, - b = node.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 (a.i !== b.i && intersects(data, a.i, node.i, node.next.i, b.i) && - locallyInside(data, a, b) && locallyInside(data, b, a) && - orient(data, a.i, node.i, b.i) && orient(data, a.i, node.next.i, b.i)) { + if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) { triangles.push(a.i / dim); - triangles.push(node.i / dim); + triangles.push(p.i / dim); triangles.push(b.i / dim); // remove two nodes involved - removeNode(node); - removeNode(node.next); + removeNode(p); + removeNode(p.next); - node = start = b; + p = start = b; } - node = node.next; - } while (node !== start); + p = p.next; + } while (p !== start); - return node; + return p; } // try splitting polygon into two and triangulate them independently -function splitEarcut(data, 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(data, a, b)) { + 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(data, a, a.next); - c = filterPoints(data, c, c.next); + a = filterPoints(a, a.next); + c = filterPoints(c, c.next); // run earcut on each half - earcutLinked(data, a, triangles, dim, minX, minY, size); - earcutLinked(data, c, triangles, dim, minX, minY, size); + earcutLinked(a, triangles, dim, minX, minY, size); + earcutLinked(c, triangles, dim, minX, minY, size); return; } b = b.next; @@ -327,122 +274,106 @@ function eliminateHoles(data, holeIndices, outerNode, 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; - list = filterPoints(data, list); - if (list) queue.push(getLeftmost(data, list)); + queue.push(getLeftmost(list)); } - queue.sort(function (a, b) { - return data[a.i] - data[b.i]; - }); + queue.sort(compareX); // process holes from left to right for (i = 0; i < queue.length; i++) { - eliminateHole(data, queue[i], outerNode); - outerNode = filterPoints(data, outerNode, outerNode.next); + 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(data, holeNode, outerNode) { - outerNode = findHoleBridge(data, holeNode, outerNode); +function eliminateHole(hole, outerNode) { + outerNode = findHoleBridge(hole, outerNode); if (outerNode) { - var b = splitPolygon(outerNode, holeNode); - filterPoints(data, b, b.next); + var b = splitPolygon(outerNode, hole); + filterPoints(b, b.next); } } // David Eberly's algorithm for finding a bridge between hole and outer polygon -function findHoleBridge(data, holeNode, outerNode) { - var node = outerNode, - i = holeNode.i, - px = data[i], - py = data[i + 1], - qMax = -Infinity, - mNode, a, b; +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 { - a = node.i; - b = node.next.i; - - if (py <= data[a + 1] && py >= data[b + 1]) { - var qx = data[a] + (py - data[a + 1]) * (data[b] - data[a]) / (data[b + 1] - data[a + 1]); - if (qx <= px && qx > qMax) { - qMax = qx; - mNode = data[a] < data[b] ? node : node.next; + 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; } } - node = node.next; - } while (node !== outerNode); + p = p.next; + } while (p !== outerNode); + + if (!m) return null; - if (!mNode) return null; + if (hx === qx) return m.prev; // hole touches outer segment; pick lower endpoint - // look for points strictly inside the triangle of hole point, segment intersection and 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 bx = data[mNode.i], - by = data[mNode.i + 1], - pbd = px * by - py * bx, - pcd = px * py - py * qMax, - cpy = py - py, - pcx = px - qMax, - pby = py - by, - bpx = bx - px, - A = pbd - pcd - (qMax * by - py * bx), - sign = A <= 0 ? -1 : 1, - stop = mNode, + var stop = m, + mx = m.x, + my = m.y, tanMin = Infinity, - mx, my, amx, s, t, tan; + tan; - node = mNode.next; + p = m.next; - while (node !== stop) { + 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)) { - mx = data[node.i]; - my = data[node.i + 1]; - amx = px - mx; + tan = Math.abs(hy - p.y) / (hx - p.x); // tangential - if (amx >= 0 && mx >= bx) { - s = (cpy * mx + pcx * my - pcd) * sign; - if (s >= 0) { - t = (pby * mx + bpx * my + pbd) * sign; - - if (t >= 0 && A * sign - s - t >= 0) { - tan = Math.abs(py - my) / amx; // tangential - if ((tan < tanMin || (tan === tanMin && mx > bx)) && - locallyInside(data, node, holeNode)) { - mNode = node; - tanMin = tan; - } - } + if ((tan < tanMin || (tan === tanMin && p.x > m.x)) && locallyInside(p, hole)) { + m = p; + tanMin = tan; } } - node = node.next; + p = p.next; } - return mNode; + return m; } // interlink polygon nodes in z-order -function indexCurve(data, start, minX, minY, size) { - var node = start; - +function indexCurve(start, minX, minY, size) { + var p = start; do { - if (node.z === null) node.z = zOrder(data[node.i], data[node.i + 1], minX, minY, size); - node.prevZ = node.prev; - node.nextZ = node.next; - node = node.next; - } while (node !== start); + 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); - node.prevZ.nextZ = null; - node.prevZ = null; + p.prevZ.nextZ = null; + p.prevZ = null; - sortLinked(node); + sortLinked(p); } // Simon Tatham's linked list merge sort algorithm @@ -466,20 +397,11 @@ function sortLinked(list) { 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) { + if (pSize !== 0 && (qSize === 0 || !q || p.z <= q.z)) { e = p; p = p.nextZ; pSize--; @@ -527,81 +449,79 @@ function zOrder(x, y, minX, minY, size) { } // find the leftmost node of a polygon ring -function getLeftmost(data, start) { - var node = start, +function getLeftmost(start) { + var p = start, leftmost = start; do { - if (data[node.i] < data[leftmost.i]) leftmost = node; - node = node.next; - } while (node !== start); + 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(data, a, b) { - return a.next.i !== b.i && a.prev.i !== b.i && - !intersectsPolygon(data, a, a.i, b.i) && - locallyInside(data, a, b) && locallyInside(data, b, a) && - middleInside(data, a, a.i, b.i); +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); } -// winding order of triangle formed by 3 given points -function orient(data, p, q, r) { - var o = (data[q + 1] - data[p + 1]) * (data[r] - data[q]) - (data[q] - data[p]) * (data[r + 1] - data[q + 1]); - return o > 0 ? 1 : - o < 0 ? -1 : 0; +// 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(data, p1, p2) { - return data[p1] === data[p2] && data[p1 + 1] === data[p2 + 1]; +function equals(p1, p2) { + return p1.x === p2.x && p1.y === p2.y; } // check if two segments intersect -function intersects(data, p1, q1, p2, q2) { - return orient(data, p1, q1, p2) !== orient(data, p1, q1, q2) && - orient(data, p2, q2, p1) !== orient(data, p2, q2, q1); +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(data, start, a, b) { - var node = start; +function intersectsPolygon(a, b) { + var p = a; do { - var p1 = node.i, - p2 = node.next.i; - - if (p1 !== a && p2 !== a && p1 !== b && p2 !== b && intersects(data, p1, p2, a, b)) return true; - - node = node.next; - } while (node !== start); + 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(data, a, b) { - return orient(data, a.prev.i, a.i, a.next.i) === -1 ? - orient(data, a.i, b.i, a.next.i) !== -1 && orient(data, a.i, a.prev.i, b.i) !== -1 : - orient(data, a.i, b.i, a.prev.i) === -1 || orient(data, a.i, a.next.i, b.i) === -1; +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(data, start, a, b) { - var node = start, +function middleInside(a, b) { + var p = a, inside = false, - px = (data[a] + data[b]) / 2, - py = (data[a + 1] + data[b + 1]) / 2; + px = (a.x + b.x) / 2, + py = (a.y + b.y) / 2; do { - var p1 = node.i, - p2 = node.next.i; - - if (((data[p1 + 1] > py) !== (data[p2 + 1] > py)) && - (px < (data[p2] - data[p1]) * (py - data[p1 + 1]) / (data[p2 + 1] - data[p1 + 1]) + data[p1])) + 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; - - node = node.next; - } while (node !== start); + p = p.next; + } while (p !== a); return inside; } @@ -609,8 +529,8 @@ function middleInside(data, start, a, b) { // 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), - b2 = new Node(b.i), + 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; @@ -630,34 +550,38 @@ function splitPolygon(a, b) { } // create a node and optionally link it with previous one (in a circular doubly linked list) -function insertNode(i, last) { - var node = new Node(i); +function insertNode(i, x, y, last) { + var p = new Node(i, x, y); if (!last) { - node.prev = node; - node.next = node; + p.prev = p; + p.next = p; } else { - node.next = last.next; - node.prev = last; - last.next.prev = node; - last.next = node; + p.next = last.next; + p.prev = last; + last.next.prev = p; + last.next = p; } - return node; + return p; } -function removeNode(node) { - node.next.prev = node.prev; - node.prev.next = node.next; +function removeNode(p) { + p.next.prev = p.prev; + p.prev.next = p.next; - if (node.prevZ) node.prevZ.nextZ = node.nextZ; - if (node.nextZ) node.nextZ.prevZ = node.prevZ; + if (p.prevZ) p.prevZ.nextZ = p.nextZ; + if (p.nextZ) p.nextZ.prevZ = p.prevZ; } -function Node(i) { - // vertex coordinates +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; @@ -672,3 +596,59 @@ function Node(i) { // indicates whether this is a steiner point this.steiner = false; } + +// 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; +};