/** * * Earcut https://github.com/mapbox/earcut * * Copyright (c) 2015, 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 * and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF * THIS SOFTWARE. */ 'use strict'; //module.exports = earcut; function earcut(data, holeIndices, dim) { dim = dim || 2; var hasHoles = holeIndices && holeIndices.length, outerLen = hasHoles ? holeIndices[0] * dim : data.length, outerNode = filterPoints(data, 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; 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); } earcutLinked(data, 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; i < end; i += dim) { sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]); j = i; } // 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); } return last; } // eliminate colinear or duplicate points function filterPoints(data, start, end) { if (!start) return start; if (!end) end = start; var node = 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; again = true; } else { node = node.next; } } while (again || node !== 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) { if (!ear) return; // interlink polygon nodes in z-order if (!pass && minX !== undefined) indexCurve(data, 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 (isEar(data, ear, minX, minY, size)) { // 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(data, filterPoints(data, 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); // as a last resort, try splitting the remaining polygon into two } else if (pass === 2) { splitEarcut(data, ear, triangles, dim, minX, minY, size); } break; } } } // 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; } } } // then look for points in decreasing z-order node = ear.prevZ; while (node && node.z >= minZ) { i = node.i; node = node.prevZ; 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; } } } // if we don't use z-order curve hash, simply iterate through all other points } else { node = ear.next.next; while (node !== ear.prev) { i = node.i; node = node.next; 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; } } } } return true; } // go through all polygon nodes and cure small local self-intersections function cureLocalIntersections(data, start, triangles, dim) { var node = start; do { var a = node.prev, b = node.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)) { triangles.push(a.i / dim); triangles.push(node.i / dim); triangles.push(b.i / dim); // remove two nodes involved removeNode(node); removeNode(node.next); node = start = b; } node = node.next; } while (node !== start); return node; } // try splitting polygon into two and triangulate them independently function splitEarcut(data, 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)) { // 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); // run earcut on each half earcutLinked(data, a, triangles, dim, minX, minY, size); earcutLinked(data, 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 < 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; list = filterPoints(data, list); if (list) queue.push(getLeftmost(data, list)); } queue.sort(function (a, b) { return data[a.i] - data[b.i]; }); // process holes from left to right for (i = 0; i < queue.length; i++) { eliminateHole(data, queue[i], outerNode); outerNode = filterPoints(data, outerNode, outerNode.next); } return outerNode; } // 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); if (outerNode) { var b = splitPolygon(outerNode, holeNode); filterPoints(data, 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; // 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; } } node = node.next; } while (node !== outerNode); if (!mNode) return null; // look for points strictly 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, tanMin = Infinity, mx, my, amx, s, t, tan; node = mNode.next; while (node !== stop) { mx = data[node.i]; my = data[node.i + 1]; amx = px - mx; 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; } } } } node = node.next; } return mNode; } // interlink polygon nodes in z-order function indexCurve(data, start, minX, minY, size) { var node = 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); node.prevZ.nextZ = null; node.prevZ = null; sortLinked(node); } // 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) { 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(data, start) { var node = start, leftmost = start; do { if (data[node.i] < data[leftmost.i]) leftmost = node; node = node.next; } while (node !== start); return leftmost; } // 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); } // 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; } // check if two points are equal function equals(data, p1, p2) { return data[p1] === data[p2] && data[p1 + 1] === data[p2 + 1]; } // 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); } // check if a polygon diagonal intersects any polygon segments function intersectsPolygon(data, start, a, b) { var node = start; 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); 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; } // check if the middle point of a polygon diagonal is inside the polygon function middleInside(data, start, a, b) { var node = start, inside = false, px = (data[a] + data[b]) / 2, py = (data[a + 1] + data[b + 1]) / 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])) inside = !inside; node = node.next; } while (node !== start); 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), b2 = new Node(b.i), 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, last) { var node = new Node(i); if (!last) { node.prev = node; node.next = node; } else { node.next = last.next; node.prev = last; last.next.prev = node; last.next = node; } return node; } function removeNode(node) { node.next.prev = node.prev; node.prev.next = node.next; if (node.prevZ) node.prevZ.nextZ = node.nextZ; if (node.nextZ) node.nextZ.prevZ = node.prevZ; } function Node(i) { // vertex coordinates this.i = i; // 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; }