/** * martinez v0.7.4 * Martinez polygon clipping algorithm, does boolean operation on polygons (multipolygons, polygons with holes etc): intersection, union, difference, xor * * @author Alex Milevski * @license MIT * @preserve */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = global || self, factory(global.martinez = {})); }(this, (function (exports) { 'use strict'; function DEFAULT_COMPARE (a, b) { return a > b ? 1 : a < b ? -1 : 0; } var SplayTree = function SplayTree(compare, noDuplicates) { if ( compare === void 0 ) compare = DEFAULT_COMPARE; if ( noDuplicates === void 0 ) noDuplicates = false; this._compare = compare; this._root = null; this._size = 0; this._noDuplicates = !!noDuplicates; }; var prototypeAccessors = { size: { configurable: true } }; SplayTree.prototype.rotateLeft = function rotateLeft (x) { var y = x.right; if (y) { x.right = y.left; if (y.left) { y.left.parent = x; } y.parent = x.parent; } if (!x.parent) { this._root = y; } else if (x === x.parent.left) { x.parent.left = y; } else { x.parent.right = y; } if (y) { y.left = x; } x.parent = y; }; SplayTree.prototype.rotateRight = function rotateRight (x) { var y = x.left; if (y) { x.left = y.right; if (y.right) { y.right.parent = x; } y.parent = x.parent; } if (!x.parent) { this._root = y; } else if(x === x.parent.left) { x.parent.left = y; } else { x.parent.right = y; } if (y) { y.right = x; } x.parent = y; }; SplayTree.prototype._splay = function _splay (x) { while (x.parent) { var p = x.parent; if (!p.parent) { if (p.left === x) { this.rotateRight(p); } else { this.rotateLeft(p); } } else if (p.left === x && p.parent.left === p) { this.rotateRight(p.parent); this.rotateRight(p); } else if (p.right === x && p.parent.right === p) { this.rotateLeft(p.parent); this.rotateLeft(p); } else if (p.left === x && p.parent.right === p) { this.rotateRight(p); this.rotateLeft(p); } else { this.rotateLeft(p); this.rotateRight(p); } } }; SplayTree.prototype.splay = function splay (x) { var p, gp, ggp, l, r; while (x.parent) { p = x.parent; gp = p.parent; if (gp && gp.parent) { ggp = gp.parent; if (ggp.left === gp) { ggp.left= x; } else { ggp.right = x; } x.parent = ggp; } else { x.parent = null; this._root = x; } l = x.left; r = x.right; if (x === p.left) { // left if (gp) { if (gp.left === p) { /* zig-zig */ if (p.right) { gp.left = p.right; gp.left.parent = gp; } else { gp.left = null; } p.right = gp; gp.parent = p; } else { /* zig-zag */ if (l) { gp.right = l; l.parent = gp; } else { gp.right = null; } x.left = gp; gp.parent = x; } } if (r) { p.left = r; r.parent = p; } else { p.left = null; } x.right= p; p.parent = x; } else { // right if (gp) { if (gp.right === p) { /* zig-zig */ if (p.left) { gp.right = p.left; gp.right.parent = gp; } else { gp.right = null; } p.left = gp; gp.parent = p; } else { /* zig-zag */ if (r) { gp.left = r; r.parent = gp; } else { gp.left = null; } x.right = gp; gp.parent = x; } } if (l) { p.right = l; l.parent = p; } else { p.right = null; } x.left = p; p.parent = x; } } }; SplayTree.prototype.replace = function replace (u, v) { if (!u.parent) { this._root = v; } else if (u === u.parent.left) { u.parent.left = v; } else { u.parent.right = v; } if (v) { v.parent = u.parent; } }; SplayTree.prototype.minNode = function minNode (u) { if ( u === void 0 ) u = this._root; if (u) { while (u.left) { u = u.left; } } return u; }; SplayTree.prototype.maxNode = function maxNode (u) { if ( u === void 0 ) u = this._root; if (u) { while (u.right) { u = u.right; } } return u; }; SplayTree.prototype.insert = function insert (key, data) { var z = this._root; var p = null; var comp = this._compare; var cmp; if (this._noDuplicates) { while (z) { p = z; cmp = comp(z.key, key); if (cmp === 0) { return; } else if (comp(z.key, key) < 0) { z = z.right; } else { z = z.left; } } } else { while (z) { p = z; if (comp(z.key, key) < 0) { z = z.right; } else { z = z.left; } } } z = { key: key, data: data, left: null, right: null, parent: p }; if (!p) { this._root = z; } else if (comp(p.key, z.key) < 0) { p.right = z; } else { p.left= z; } this.splay(z); this._size++; return z; }; SplayTree.prototype.find = function find (key) { var z = this._root; var comp = this._compare; while (z) { var cmp = comp(z.key, key); if (cmp < 0) { z = z.right; } else if (cmp > 0) { z = z.left; } else { return z; } } return null; }; /** * Whether the tree contains a node with the given key * @param{Key} key * @return {boolean} true/false */ SplayTree.prototype.contains = function contains (key) { var node = this._root; var comparator = this._compare; while (node){ var cmp = comparator(key, node.key); if (cmp === 0) { return true; } else if (cmp < 0) { node = node.left; } else { node = node.right; } } return false; }; SplayTree.prototype.remove = function remove (key) { var z = this.find(key); if (!z) { return false; } this.splay(z); if (!z.left) { this.replace(z, z.right); } else if (!z.right) { this.replace(z, z.left); } else { var y = this.minNode(z.right); if (y.parent !== z) { this.replace(y, y.right); y.right = z.right; y.right.parent = y; } this.replace(z, y); y.left = z.left; y.left.parent = y; } this._size--; return true; }; SplayTree.prototype.removeNode = function removeNode (z) { if (!z) { return false; } this.splay(z); if (!z.left) { this.replace(z, z.right); } else if (!z.right) { this.replace(z, z.left); } else { var y = this.minNode(z.right); if (y.parent !== z) { this.replace(y, y.right); y.right = z.right; y.right.parent = y; } this.replace(z, y); y.left = z.left; y.left.parent = y; } this._size--; return true; }; SplayTree.prototype.erase = function erase (key) { var z = this.find(key); if (!z) { return; } this.splay(z); var s = z.left; var t = z.right; var sMax = null; if (s) { s.parent = null; sMax = this.maxNode(s); this.splay(sMax); this._root = sMax; } if (t) { if (s) { sMax.right = t; } else { this._root = t; } t.parent = sMax; } this._size--; }; /** * Removes and returns the node with smallest key * @return {?Node} */ SplayTree.prototype.pop = function pop () { var node = this._root, returnValue = null; if (node) { while (node.left) { node = node.left; } returnValue = { key: node.key, data: node.data }; this.remove(node.key); } return returnValue; }; /* eslint-disable class-methods-use-this */ /** * Successor node * @param{Node} node * @return {?Node} */ SplayTree.prototype.next = function next (node) { var successor = node; if (successor) { if (successor.right) { successor = successor.right; while (successor && successor.left) { successor = successor.left; } } else { successor = node.parent; while (successor && successor.right === node) { node = successor; successor = successor.parent; } } } return successor; }; /** * Predecessor node * @param{Node} node * @return {?Node} */ SplayTree.prototype.prev = function prev (node) { var predecessor = node; if (predecessor) { if (predecessor.left) { predecessor = predecessor.left; while (predecessor && predecessor.right) { predecessor = predecessor.right; } } else { predecessor = node.parent; while (predecessor && predecessor.left === node) { node = predecessor; predecessor = predecessor.parent; } } } return predecessor; }; /* eslint-enable class-methods-use-this */ /** * @param{forEachCallback} callback * @return {SplayTree} */ SplayTree.prototype.forEach = function forEach (callback) { var current = this._root; var s = [], done = false, i = 0; while (!done) { // Reach the left most Node of the current Node if (current) { // Place pointer to a tree node on the stack // before traversing the node's left subtree s.push(current); current = current.left; } else { // BackTrack from the empty subtree and visit the Node // at the top of the stack; however, if the stack is // empty you are done if (s.length > 0) { current = s.pop(); callback(current, i++); // We have visited the node and its left // subtree. Now, it's right subtree's turn current = current.right; } else { done = true; } } } return this; }; /** * Walk key range from `low` to `high`. Stops if `fn` returns a value. * @param{Key} low * @param{Key} high * @param{Function} fn * @param{*?} ctx * @return {SplayTree} */ SplayTree.prototype.range = function range (low, high, fn, ctx) { var Q = []; var compare = this._compare; var node = this._root, cmp; while (Q.length !== 0 || node) { if (node) { Q.push(node); node = node.left; } else { node = Q.pop(); cmp = compare(node.key, high); if (cmp > 0) { break; } else if (compare(node.key, low) >= 0) { if (fn.call(ctx, node)) { return this; } // stop if smth is returned } node = node.right; } } return this; }; /** * Returns all keys in order * @return {Array} */ SplayTree.prototype.keys = function keys () { var current = this._root; var s = [], r = [], done = false; while (!done) { if (current) { s.push(current); current = current.left; } else { if (s.length > 0) { current = s.pop(); r.push(current.key); current = current.right; } else { done = true; } } } return r; }; /** * Returns `data` fields of all nodes in order. * @return {Array} */ SplayTree.prototype.values = function values () { var current = this._root; var s = [], r = [], done = false; while (!done) { if (current) { s.push(current); current = current.left; } else { if (s.length > 0) { current = s.pop(); r.push(current.data); current = current.right; } else { done = true; } } } return r; }; /** * Returns node at given index * @param{number} index * @return {?Node} */ SplayTree.prototype.at = function at (index) { // removed after a consideration, more misleading than useful // index = index % this.size; // if (index < 0) index = this.size - index; var current = this._root; var s = [], done = false, i = 0; while (!done) { if (current) { s.push(current); current = current.left; } else { if (s.length > 0) { current = s.pop(); if (i === index) { return current; } i++; current = current.right; } else { done = true; } } } return null; }; /** * Bulk-load items. Both array have to be same size * @param{Array} keys * @param{Array}[values] * @param{Boolean} [presort=false] Pre-sort keys and values, using * tree's comparator. Sorting is done * in-place * @return {AVLTree} */ SplayTree.prototype.load = function load (keys, values, presort) { if ( keys === void 0 ) keys = []; if ( values === void 0 ) values = []; if ( presort === void 0 ) presort = false; if (this._size !== 0) { throw new Error('bulk-load: tree is not empty'); } var size = keys.length; if (presort) { sort(keys, values, 0, size - 1, this._compare); } this._root = loadRecursive(null, keys, values, 0, size); this._size = size; return this; }; SplayTree.prototype.min = function min () { var node = this.minNode(this._root); if (node) { return node.key; } else { return null; } }; SplayTree.prototype.max = function max () { var node = this.maxNode(this._root); if (node) { return node.key; } else { return null; } }; SplayTree.prototype.isEmpty = function isEmpty () { return this._root === null; }; prototypeAccessors.size.get = function () { return this._size; }; /** * Create a tree and load it with items * @param{Array} keys * @param{Array?} [values] * @param{Function?} [comparator] * @param{Boolean?} [presort=false] Pre-sort keys and values, using * tree's comparator. Sorting is done * in-place * @param{Boolean?} [noDuplicates=false] Allow duplicates * @return {SplayTree} */ SplayTree.createTree = function createTree (keys, values, comparator, presort, noDuplicates) { return new SplayTree(comparator, noDuplicates).load(keys, values, presort); }; Object.defineProperties( SplayTree.prototype, prototypeAccessors ); function loadRecursive (parent, keys, values, start, end) { var size = end - start; if (size > 0) { var middle = start + Math.floor(size / 2); var key = keys[middle]; var data = values[middle]; var node = { key: key, data: data, parent: parent }; node.left = loadRecursive(node, keys, values, start, middle); node.right = loadRecursive(node, keys, values, middle + 1, end); return node; } return null; } function sort(keys, values, left, right, compare) { if (left >= right) { return; } var pivot = keys[(left + right) >> 1]; var i = left - 1; var j = right + 1; while (true) { do { i++; } while (compare(keys[i], pivot) < 0); do { j--; } while (compare(keys[j], pivot) > 0); if (i >= j) { break; } var tmp = keys[i]; keys[i] = keys[j]; keys[j] = tmp; tmp = values[i]; values[i] = values[j]; values[j] = tmp; } sort(keys, values, left, j, compare); sort(keys, values, j + 1, right, compare); } var NORMAL = 0; var NON_CONTRIBUTING = 1; var SAME_TRANSITION = 2; var DIFFERENT_TRANSITION = 3; var INTERSECTION = 0; var UNION = 1; var DIFFERENCE = 2; var XOR = 3; /** * @param {SweepEvent} event * @param {SweepEvent} prev * @param {Operation} operation */ function computeFields (event, prev, operation) { // compute inOut and otherInOut fields if (prev === null) { event.inOut = false; event.otherInOut = true; // previous line segment in sweepline belongs to the same polygon } else { if (event.isSubject === prev.isSubject) { event.inOut = !prev.inOut; event.otherInOut = prev.otherInOut; // previous line segment in sweepline belongs to the clipping polygon } else { event.inOut = !prev.otherInOut; event.otherInOut = prev.isVertical() ? !prev.inOut : prev.inOut; } // compute prevInResult field if (prev) { event.prevInResult = (!inResult(prev, operation) || prev.isVertical()) ? prev.prevInResult : prev; } } // check if the line segment belongs to the Boolean operation var isInResult = inResult(event, operation); if (isInResult) { event.resultTransition = determineResultTransition(event, operation); } else { event.resultTransition = 0; } } /* eslint-disable indent */ function inResult(event, operation) { switch (event.type) { case NORMAL: switch (operation) { case INTERSECTION: return !event.otherInOut; case UNION: return event.otherInOut; case DIFFERENCE: // return (event.isSubject && !event.otherInOut) || // (!event.isSubject && event.otherInOut); return (event.isSubject && event.otherInOut) || (!event.isSubject && !event.otherInOut); case XOR: return true; } break; case SAME_TRANSITION: return operation === INTERSECTION || operation === UNION; case DIFFERENT_TRANSITION: return operation === DIFFERENCE; case NON_CONTRIBUTING: return false; } return false; } /* eslint-enable indent */ function determineResultTransition(event, operation) { var thisIn = !event.inOut; var thatIn = !event.otherInOut; var isIn; switch (operation) { case INTERSECTION: isIn = thisIn && thatIn; break; case UNION: isIn = thisIn || thatIn; break; case XOR: isIn = thisIn ^ thatIn; break; case DIFFERENCE: if (event.isSubject) { isIn = thisIn && !thatIn; } else { isIn = thatIn && !thisIn; } break; } return isIn ? +1 : -1; } var SweepEvent = function SweepEvent (point, left, otherEvent, isSubject, edgeType) { /** * Is left endpoint? * @type {Boolean} */ this.left = left; /** * @type {Array.} */ this.point = point; /** * Other edge reference * @type {SweepEvent} */ this.otherEvent = otherEvent; /** * Belongs to source or clipping polygon * @type {Boolean} */ this.isSubject = isSubject; /** * Edge contribution type * @type {Number} */ this.type = edgeType || NORMAL; /** * In-out transition for the sweepline crossing polygon * @type {Boolean} */ this.inOut = false; /** * @type {Boolean} */ this.otherInOut = false; /** * Previous event in result? * @type {SweepEvent} */ this.prevInResult = null; /** * Type of result transition (0 = not in result, +1 = out-in, -1, in-out) * @type {Number} */ this.resultTransition = 0; // connection step /** * @type {Number} */ this.otherPos = -1; /** * @type {Number} */ this.outputContourId = -1; this.isExteriorRing = true; // TODO: Looks unused, remove? }; var prototypeAccessors$1 = { inResult: { configurable: true } }; /** * @param{Array.}p * @return {Boolean} */ SweepEvent.prototype.isBelow = function isBelow (p) { var p0 = this.point, p1 = this.otherEvent.point; return this.left ? (p0[0] - p[0]) * (p1[1] - p[1]) - (p1[0] - p[0]) * (p0[1] - p[1]) > 0 // signedArea(this.point, this.otherEvent.point, p) > 0 : : (p1[0] - p[0]) * (p0[1] - p[1]) - (p0[0] - p[0]) * (p1[1] - p[1]) > 0; //signedArea(this.otherEvent.point, this.point, p) > 0; }; /** * @param{Array.}p * @return {Boolean} */ SweepEvent.prototype.isAbove = function isAbove (p) { return !this.isBelow(p); }; /** * @return {Boolean} */ SweepEvent.prototype.isVertical = function isVertical () { return this.point[0] === this.otherEvent.point[0]; }; /** * Does event belong to result? * @return {Boolean} */ prototypeAccessors$1.inResult.get = function () { return this.resultTransition !== 0; }; SweepEvent.prototype.clone = function clone () { var copy = new SweepEvent( this.point, this.left, this.otherEvent, this.isSubject, this.type); copy.contourId = this.contourId; copy.resultTransition = this.resultTransition; copy.prevInResult = this.prevInResult; copy.isExteriorRing = this.isExteriorRing; copy.inOut = this.inOut; copy.otherInOut = this.otherInOut; return copy; }; Object.defineProperties( SweepEvent.prototype, prototypeAccessors$1 ); function equals(p1, p2) { if (p1[0] === p2[0]) { if (p1[1] === p2[1]) { return true; } else { return false; } } return false; } // const EPSILON = 1e-9; // const abs = Math.abs; // TODO https://github.com/w8r/martinez/issues/6#issuecomment-262847164 // Precision problem. // // module.exports = function equals(p1, p2) { // return abs(p1[0] - p2[0]) <= EPSILON && abs(p1[1] - p2[1]) <= EPSILON; // }; var epsilon = 1.1102230246251565e-16; var splitter = 134217729; var resulterrbound = (3 + 8 * epsilon) * epsilon; // fast_expansion_sum_zeroelim routine from oritinal code function sum(elen, e, flen, f, h) { var Q, Qnew, hh, bvirt; var enow = e[0]; var fnow = f[0]; var eindex = 0; var findex = 0; if ((fnow > enow) === (fnow > -enow)) { Q = enow; enow = e[++eindex]; } else { Q = fnow; fnow = f[++findex]; } var hindex = 0; if (eindex < elen && findex < flen) { if ((fnow > enow) === (fnow > -enow)) { Qnew = enow + Q; hh = Q - (Qnew - enow); enow = e[++eindex]; } else { Qnew = fnow + Q; hh = Q - (Qnew - fnow); fnow = f[++findex]; } Q = Qnew; if (hh !== 0) { h[hindex++] = hh; } while (eindex < elen && findex < flen) { if ((fnow > enow) === (fnow > -enow)) { Qnew = Q + enow; bvirt = Qnew - Q; hh = Q - (Qnew - bvirt) + (enow - bvirt); enow = e[++eindex]; } else { Qnew = Q + fnow; bvirt = Qnew - Q; hh = Q - (Qnew - bvirt) + (fnow - bvirt); fnow = f[++findex]; } Q = Qnew; if (hh !== 0) { h[hindex++] = hh; } } } while (eindex < elen) { Qnew = Q + enow; bvirt = Qnew - Q; hh = Q - (Qnew - bvirt) + (enow - bvirt); enow = e[++eindex]; Q = Qnew; if (hh !== 0) { h[hindex++] = hh; } } while (findex < flen) { Qnew = Q + fnow; bvirt = Qnew - Q; hh = Q - (Qnew - bvirt) + (fnow - bvirt); fnow = f[++findex]; Q = Qnew; if (hh !== 0) { h[hindex++] = hh; } } if (Q !== 0 || hindex === 0) { h[hindex++] = Q; } return hindex; } function estimate(elen, e) { var Q = e[0]; for (var i = 1; i < elen; i++) { Q += e[i]; } return Q; } function vec(n) { return new Float64Array(n); } var ccwerrboundA = (3 + 16 * epsilon) * epsilon; var ccwerrboundB = (2 + 12 * epsilon) * epsilon; var ccwerrboundC = (9 + 64 * epsilon) * epsilon * epsilon; var B = vec(4); var C1 = vec(8); var C2 = vec(12); var D = vec(16); var u = vec(4); function orient2dadapt(ax, ay, bx, by, cx, cy, detsum) { var acxtail, acytail, bcxtail, bcytail; var bvirt, c, ahi, alo, bhi, blo, _i, _j, _0, s1, s0, t1, t0, u3; var acx = ax - cx; var bcx = bx - cx; var acy = ay - cy; var bcy = by - cy; s1 = acx * bcy; c = splitter * acx; ahi = c - (c - acx); alo = acx - ahi; c = splitter * bcy; bhi = c - (c - bcy); blo = bcy - bhi; s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo); t1 = acy * bcx; c = splitter * acy; ahi = c - (c - acy); alo = acy - ahi; c = splitter * bcx; bhi = c - (c - bcx); blo = bcx - bhi; t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo); _i = s0 - t0; bvirt = s0 - _i; B[0] = s0 - (_i + bvirt) + (bvirt - t0); _j = s1 + _i; bvirt = _j - s1; _0 = s1 - (_j - bvirt) + (_i - bvirt); _i = _0 - t1; bvirt = _0 - _i; B[1] = _0 - (_i + bvirt) + (bvirt - t1); u3 = _j + _i; bvirt = u3 - _j; B[2] = _j - (u3 - bvirt) + (_i - bvirt); B[3] = u3; var det = estimate(4, B); var errbound = ccwerrboundB * detsum; if (det >= errbound || -det >= errbound) { return det; } bvirt = ax - acx; acxtail = ax - (acx + bvirt) + (bvirt - cx); bvirt = bx - bcx; bcxtail = bx - (bcx + bvirt) + (bvirt - cx); bvirt = ay - acy; acytail = ay - (acy + bvirt) + (bvirt - cy); bvirt = by - bcy; bcytail = by - (bcy + bvirt) + (bvirt - cy); if (acxtail === 0 && acytail === 0 && bcxtail === 0 && bcytail === 0) { return det; } errbound = ccwerrboundC * detsum + resulterrbound * Math.abs(det); det += (acx * bcytail + bcy * acxtail) - (acy * bcxtail + bcx * acytail); if (det >= errbound || -det >= errbound) { return det; } s1 = acxtail * bcy; c = splitter * acxtail; ahi = c - (c - acxtail); alo = acxtail - ahi; c = splitter * bcy; bhi = c - (c - bcy); blo = bcy - bhi; s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo); t1 = acytail * bcx; c = splitter * acytail; ahi = c - (c - acytail); alo = acytail - ahi; c = splitter * bcx; bhi = c - (c - bcx); blo = bcx - bhi; t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo); _i = s0 - t0; bvirt = s0 - _i; u[0] = s0 - (_i + bvirt) + (bvirt - t0); _j = s1 + _i; bvirt = _j - s1; _0 = s1 - (_j - bvirt) + (_i - bvirt); _i = _0 - t1; bvirt = _0 - _i; u[1] = _0 - (_i + bvirt) + (bvirt - t1); u3 = _j + _i; bvirt = u3 - _j; u[2] = _j - (u3 - bvirt) + (_i - bvirt); u[3] = u3; var C1len = sum(4, B, 4, u, C1); s1 = acx * bcytail; c = splitter * acx; ahi = c - (c - acx); alo = acx - ahi; c = splitter * bcytail; bhi = c - (c - bcytail); blo = bcytail - bhi; s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo); t1 = acy * bcxtail; c = splitter * acy; ahi = c - (c - acy); alo = acy - ahi; c = splitter * bcxtail; bhi = c - (c - bcxtail); blo = bcxtail - bhi; t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo); _i = s0 - t0; bvirt = s0 - _i; u[0] = s0 - (_i + bvirt) + (bvirt - t0); _j = s1 + _i; bvirt = _j - s1; _0 = s1 - (_j - bvirt) + (_i - bvirt); _i = _0 - t1; bvirt = _0 - _i; u[1] = _0 - (_i + bvirt) + (bvirt - t1); u3 = _j + _i; bvirt = u3 - _j; u[2] = _j - (u3 - bvirt) + (_i - bvirt); u[3] = u3; var C2len = sum(C1len, C1, 4, u, C2); s1 = acxtail * bcytail; c = splitter * acxtail; ahi = c - (c - acxtail); alo = acxtail - ahi; c = splitter * bcytail; bhi = c - (c - bcytail); blo = bcytail - bhi; s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo); t1 = acytail * bcxtail; c = splitter * acytail; ahi = c - (c - acytail); alo = acytail - ahi; c = splitter * bcxtail; bhi = c - (c - bcxtail); blo = bcxtail - bhi; t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo); _i = s0 - t0; bvirt = s0 - _i; u[0] = s0 - (_i + bvirt) + (bvirt - t0); _j = s1 + _i; bvirt = _j - s1; _0 = s1 - (_j - bvirt) + (_i - bvirt); _i = _0 - t1; bvirt = _0 - _i; u[1] = _0 - (_i + bvirt) + (bvirt - t1); u3 = _j + _i; bvirt = u3 - _j; u[2] = _j - (u3 - bvirt) + (_i - bvirt); u[3] = u3; var Dlen = sum(C2len, C2, 4, u, D); return D[Dlen - 1]; } function orient2d(ax, ay, bx, by, cx, cy) { var detleft = (ay - cy) * (bx - cx); var detright = (ax - cx) * (by - cy); var det = detleft - detright; if (detleft === 0 || detright === 0 || (detleft > 0) !== (detright > 0)) { return det; } var detsum = Math.abs(detleft + detright); if (Math.abs(det) >= ccwerrboundA * detsum) { return det; } return -orient2dadapt(ax, ay, bx, by, cx, cy, detsum); } /** * Signed area of the triangle (p0, p1, p2) * @param {Array.} p0 * @param {Array.} p1 * @param {Array.} p2 * @return {Number} */ function signedArea(p0, p1, p2) { var res = orient2d(p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]); if (res > 0) { return -1; } if (res < 0) { return 1; } return 0; } /** * @param {SweepEvent} e1 * @param {SweepEvent} e2 * @return {Number} */ function compareEvents(e1, e2) { var p1 = e1.point; var p2 = e2.point; // Different x-coordinate if (p1[0] > p2[0]) { return 1; } if (p1[0] < p2[0]) { return -1; } // Different points, but same x-coordinate // Event with lower y-coordinate is processed first if (p1[1] !== p2[1]) { return p1[1] > p2[1] ? 1 : -1; } return specialCases(e1, e2, p1); } /* eslint-disable no-unused-vars */ function specialCases(e1, e2, p1, p2) { // Same coordinates, but one is a left endpoint and the other is // a right endpoint. The right endpoint is processed first if (e1.left !== e2.left) { return e1.left ? 1 : -1; } // const p2 = e1.otherEvent.point, p3 = e2.otherEvent.point; // const sa = (p1[0] - p3[0]) * (p2[1] - p3[1]) - (p2[0] - p3[0]) * (p1[1] - p3[1]) // Same coordinates, both events // are left endpoints or right endpoints. // not collinear if (signedArea(p1, e1.otherEvent.point, e2.otherEvent.point) !== 0) { // the event associate to the bottom segment is processed first return (!e1.isBelow(e2.otherEvent.point)) ? 1 : -1; } return (!e1.isSubject && e2.isSubject) ? 1 : -1; } /* eslint-enable no-unused-vars */ /** * @param {SweepEvent} se * @param {Array.} p * @param {Queue} queue * @return {Queue} */ function divideSegment(se, p, queue) { var r = new SweepEvent(p, false, se, se.isSubject); var l = new SweepEvent(p, true, se.otherEvent, se.isSubject); /* eslint-disable no-console */ if (equals(se.point, se.otherEvent.point)) { console.warn('what is that, a collapsed segment?', se); } /* eslint-enable no-console */ r.contourId = l.contourId = se.contourId; // avoid a rounding error. The left event would be processed after the right event if (compareEvents(l, se.otherEvent) > 0) { se.otherEvent.left = true; l.left = false; } // avoid a rounding error. The left event would be processed after the right event // if (compareEvents(se, r) > 0) {} se.otherEvent.otherEvent = l; se.otherEvent = r; queue.push(l); queue.push(r); return queue; } //const EPS = 1e-9; /** * Finds the magnitude of the cross product of two vectors (if we pretend * they're in three dimensions) * * @param {Object} a First vector * @param {Object} b Second vector * @private * @returns {Number} The magnitude of the cross product */ function crossProduct(a, b) { return (a[0] * b[1]) - (a[1] * b[0]); } /** * Finds the dot product of two vectors. * * @param {Object} a First vector * @param {Object} b Second vector * @private * @returns {Number} The dot product */ function dotProduct(a, b) { return (a[0] * b[0]) + (a[1] * b[1]); } /** * Finds the intersection (if any) between two line segments a and b, given the * line segments' end points a1, a2 and b1, b2. * * This algorithm is based on Schneider and Eberly. * http://www.cimec.org.ar/~ncalvo/Schneider_Eberly.pdf * Page 244. * * @param {Array.} a1 point of first line * @param {Array.} a2 point of first line * @param {Array.} b1 point of second line * @param {Array.} b2 point of second line * @param {Boolean=} noEndpointTouch whether to skip single touchpoints * (meaning connected segments) as * intersections * @returns {Array.>|Null} If the lines intersect, the point of * intersection. If they overlap, the two end points of the overlapping segment. * Otherwise, null. */ function intersection (a1, a2, b1, b2, noEndpointTouch) { // The algorithm expects our lines in the form P + sd, where P is a point, // s is on the interval [0, 1], and d is a vector. // We are passed two points. P can be the first point of each pair. The // vector, then, could be thought of as the distance (in x and y components) // from the first point to the second point. // So first, let's make our vectors: var va = [a2[0] - a1[0], a2[1] - a1[1]]; var vb = [b2[0] - b1[0], b2[1] - b1[1]]; // We also define a function to convert back to regular point form: /* eslint-disable arrow-body-style */ function toPoint(p, s, d) { return [ p[0] + s * d[0], p[1] + s * d[1] ]; } /* eslint-enable arrow-body-style */ // The rest is pretty much a straight port of the algorithm. var e = [b1[0] - a1[0], b1[1] - a1[1]]; var kross = crossProduct(va, vb); var sqrKross = kross * kross; var sqrLenA = dotProduct(va, va); //const sqrLenB = dotProduct(vb, vb); // Check for line intersection. This works because of the properties of the // cross product -- specifically, two vectors are parallel if and only if the // cross product is the 0 vector. The full calculation involves relative error // to account for possible very small line segments. See Schneider & Eberly // for details. if (sqrKross > 0/* EPS * sqrLenB * sqLenA */) { // If they're not parallel, then (because these are line segments) they // still might not actually intersect. This code checks that the // intersection point of the lines is actually on both line segments. var s = crossProduct(e, vb) / kross; if (s < 0 || s > 1) { // not on line segment a return null; } var t = crossProduct(e, va) / kross; if (t < 0 || t > 1) { // not on line segment b return null; } if (s === 0 || s === 1) { // on an endpoint of line segment a return noEndpointTouch ? null : [toPoint(a1, s, va)]; } if (t === 0 || t === 1) { // on an endpoint of line segment b return noEndpointTouch ? null : [toPoint(b1, t, vb)]; } return [toPoint(a1, s, va)]; } // If we've reached this point, then the lines are either parallel or the // same, but the segments could overlap partially or fully, or not at all. // So we need to find the overlap, if any. To do that, we can use e, which is // the (vector) difference between the two initial points. If this is parallel // with the line itself, then the two lines are the same line, and there will // be overlap. //const sqrLenE = dotProduct(e, e); kross = crossProduct(e, va); sqrKross = kross * kross; if (sqrKross > 0 /* EPS * sqLenB * sqLenE */) { // Lines are just parallel, not the same. No overlap. return null; } var sa = dotProduct(va, e) / sqrLenA; var sb = sa + dotProduct(va, vb) / sqrLenA; var smin = Math.min(sa, sb); var smax = Math.max(sa, sb); // this is, essentially, the FindIntersection acting on floats from // Schneider & Eberly, just inlined into this function. if (smin <= 1 && smax >= 0) { // overlap on an end point if (smin === 1) { return noEndpointTouch ? null : [toPoint(a1, smin > 0 ? smin : 0, va)]; } if (smax === 0) { return noEndpointTouch ? null : [toPoint(a1, smax < 1 ? smax : 1, va)]; } if (noEndpointTouch && smin === 0 && smax === 1) { return null; } // There's overlap on a segment -- two points of intersection. Return both. return [ toPoint(a1, smin > 0 ? smin : 0, va), toPoint(a1, smax < 1 ? smax : 1, va) ]; } return null; } /** * @param {SweepEvent} se1 * @param {SweepEvent} se2 * @param {Queue} queue * @return {Number} */ function possibleIntersection (se1, se2, queue) { // that disallows self-intersecting polygons, // did cost us half a day, so I'll leave it // out of respect // if (se1.isSubject === se2.isSubject) return; var inter = intersection( se1.point, se1.otherEvent.point, se2.point, se2.otherEvent.point ); var nintersections = inter ? inter.length : 0; if (nintersections === 0) { return 0; } // no intersection // the line segments intersect at an endpoint of both line segments if ((nintersections === 1) && (equals(se1.point, se2.point) || equals(se1.otherEvent.point, se2.otherEvent.point))) { return 0; } if (nintersections === 2 && se1.isSubject === se2.isSubject) { // if(se1.contourId === se2.contourId){ // console.warn('Edges of the same polygon overlap', // se1.point, se1.otherEvent.point, se2.point, se2.otherEvent.point); // } //throw new Error('Edges of the same polygon overlap'); return 0; } // The line segments associated to se1 and se2 intersect if (nintersections === 1) { // if the intersection point is not an endpoint of se1 if (!equals(se1.point, inter[0]) && !equals(se1.otherEvent.point, inter[0])) { divideSegment(se1, inter[0], queue); } // if the intersection point is not an endpoint of se2 if (!equals(se2.point, inter[0]) && !equals(se2.otherEvent.point, inter[0])) { divideSegment(se2, inter[0], queue); } return 1; } // The line segments associated to se1 and se2 overlap var events = []; var leftCoincide = false; var rightCoincide = false; if (equals(se1.point, se2.point)) { leftCoincide = true; // linked } else if (compareEvents(se1, se2) === 1) { events.push(se2, se1); } else { events.push(se1, se2); } if (equals(se1.otherEvent.point, se2.otherEvent.point)) { rightCoincide = true; } else if (compareEvents(se1.otherEvent, se2.otherEvent) === 1) { events.push(se2.otherEvent, se1.otherEvent); } else { events.push(se1.otherEvent, se2.otherEvent); } if ((leftCoincide && rightCoincide) || leftCoincide) { // both line segments are equal or share the left endpoint se2.type = NON_CONTRIBUTING; se1.type = (se2.inOut === se1.inOut) ? SAME_TRANSITION : DIFFERENT_TRANSITION; if (leftCoincide && !rightCoincide) { // honestly no idea, but changing events selection from [2, 1] // to [0, 1] fixes the overlapping self-intersecting polygons issue divideSegment(events[1].otherEvent, events[0].point, queue); } return 2; } // the line segments share the right endpoint if (rightCoincide) { divideSegment(events[0], events[1].point, queue); return 3; } // no line segment includes totally the other one if (events[0] !== events[3].otherEvent) { divideSegment(events[0], events[1].point, queue); divideSegment(events[1], events[2].point, queue); return 3; } // one line segment includes the other one divideSegment(events[0], events[1].point, queue); divideSegment(events[3].otherEvent, events[2].point, queue); return 3; } /** * @param {SweepEvent} le1 * @param {SweepEvent} le2 * @return {Number} */ function compareSegments(le1, le2) { if (le1 === le2) { return 0; } // Segments are not collinear if (signedArea(le1.point, le1.otherEvent.point, le2.point) !== 0 || signedArea(le1.point, le1.otherEvent.point, le2.otherEvent.point) !== 0) { // If they share their left endpoint use the right endpoint to sort if (equals(le1.point, le2.point)) { return le1.isBelow(le2.otherEvent.point) ? -1 : 1; } // Different left endpoint: use the left endpoint to sort if (le1.point[0] === le2.point[0]) { return le1.point[1] < le2.point[1] ? -1 : 1; } // has the line segment associated to e1 been inserted // into S after the line segment associated to e2 ? if (compareEvents(le1, le2) === 1) { return le2.isAbove(le1.point) ? -1 : 1; } // The line segment associated to e2 has been inserted // into S after the line segment associated to e1 return le1.isBelow(le2.point) ? -1 : 1; } if (le1.isSubject === le2.isSubject) { // same polygon var p1 = le1.point, p2 = le2.point; if (p1[0] === p2[0] && p1[1] === p2[1]/*equals(le1.point, le2.point)*/) { p1 = le1.otherEvent.point; p2 = le2.otherEvent.point; if (p1[0] === p2[0] && p1[1] === p2[1]) { return 0; } else { return le1.contourId > le2.contourId ? 1 : -1; } } } else { // Segments are collinear, but belong to separate polygons return le1.isSubject ? -1 : 1; } return compareEvents(le1, le2) === 1 ? 1 : -1; } function subdivide(eventQueue, subject, clipping, sbbox, cbbox, operation) { var sweepLine = new SplayTree(compareSegments); var sortedEvents = []; var rightbound = Math.min(sbbox[2], cbbox[2]); var prev, next, begin; while (eventQueue.length !== 0) { var event = eventQueue.pop(); sortedEvents.push(event); // optimization by bboxes for intersection and difference goes here if ((operation === INTERSECTION && event.point[0] > rightbound) || (operation === DIFFERENCE && event.point[0] > sbbox[2])) { break; } if (event.left) { next = prev = sweepLine.insert(event); begin = sweepLine.minNode(); if (prev !== begin) { prev = sweepLine.prev(prev); } else { prev = null; } next = sweepLine.next(next); var prevEvent = prev ? prev.key : null; var prevprevEvent = (void 0); computeFields(event, prevEvent, operation); if (next) { if (possibleIntersection(event, next.key, eventQueue) === 2) { computeFields(event, prevEvent, operation); computeFields(next.key, event, operation); } } if (prev) { if (possibleIntersection(prev.key, event, eventQueue) === 2) { var prevprev = prev; if (prevprev !== begin) { prevprev = sweepLine.prev(prevprev); } else { prevprev = null; } prevprevEvent = prevprev ? prevprev.key : null; computeFields(prevEvent, prevprevEvent, operation); computeFields(event, prevEvent, operation); } } } else { event = event.otherEvent; next = prev = sweepLine.find(event); if (prev && next) { if (prev !== begin) { prev = sweepLine.prev(prev); } else { prev = null; } next = sweepLine.next(next); sweepLine.remove(event); if (next && prev) { possibleIntersection(prev.key, next.key, eventQueue); } } } } return sortedEvents; } var Contour = function Contour() { this.points = []; this.holeIds = []; this.holeOf = null; this.depth = null; }; Contour.prototype.isExterior = function isExterior () { return this.holeOf == null; }; /** * @param {Array.} sortedEvents * @return {Array.} */ function orderEvents(sortedEvents) { var event, i, len, tmp; var resultEvents = []; for (i = 0, len = sortedEvents.length; i < len; i++) { event = sortedEvents[i]; if ((event.left && event.inResult) || (!event.left && event.otherEvent.inResult)) { resultEvents.push(event); } } // Due to overlapping edges the resultEvents array can be not wholly sorted var sorted = false; while (!sorted) { sorted = true; for (i = 0, len = resultEvents.length; i < len; i++) { if ((i + 1) < len && compareEvents(resultEvents[i], resultEvents[i + 1]) === 1) { tmp = resultEvents[i]; resultEvents[i] = resultEvents[i + 1]; resultEvents[i + 1] = tmp; sorted = false; } } } for (i = 0, len = resultEvents.length; i < len; i++) { event = resultEvents[i]; event.otherPos = i; } // imagine, the right event is found in the beginning of the queue, // when his left counterpart is not marked yet for (i = 0, len = resultEvents.length; i < len; i++) { event = resultEvents[i]; if (!event.left) { tmp = event.otherPos; event.otherPos = event.otherEvent.otherPos; event.otherEvent.otherPos = tmp; } } return resultEvents; } /** * @param {Number} pos * @param {Array.} resultEvents * @param {Object>} processed * @return {Number} */ function nextPos(pos, resultEvents, processed, origPos) { var newPos = pos + 1, p = resultEvents[pos].point, p1; var length = resultEvents.length; if (newPos < length) { p1 = resultEvents[newPos].point; } while (newPos < length && p1[0] === p[0] && p1[1] === p[1]) { if (!processed[newPos]) { return newPos; } else { newPos++; } if (newPos < length) { p1 = resultEvents[newPos].point; } } newPos = pos - 1; while (processed[newPos] && newPos > origPos) { newPos--; } return newPos; } function initializeContourFromContext(event, contours, contourId) { var contour = new Contour(); if (event.prevInResult != null) { var prevInResult = event.prevInResult; // Note that it is valid to query the "previous in result" for its output contour id, // because we must have already processed it (i.e., assigned an output contour id) // in an earlier iteration, otherwise it wouldn't be possible that it is "previous in // result". var lowerContourId = prevInResult.outputContourId; var lowerResultTransition = prevInResult.resultTransition; if (lowerResultTransition > 0) { // We are inside. Now we have to check if the thing below us is another hole or // an exterior contour. var lowerContour = contours[lowerContourId]; if (lowerContour.holeOf != null) { // The lower contour is a hole => Connect the new contour as a hole to its parent, // and use same depth. var parentContourId = lowerContour.holeOf; contours[parentContourId].holeIds.push(contourId); contour.holeOf = parentContourId; contour.depth = contours[lowerContourId].depth; } else { // The lower contour is an exterior contour => Connect the new contour as a hole, // and increment depth. contours[lowerContourId].holeIds.push(contourId); contour.holeOf = lowerContourId; contour.depth = contours[lowerContourId].depth + 1; } } else { // We are outside => this contour is an exterior contour of same depth. contour.holeOf = null; contour.depth = contours[lowerContourId].depth; } } else { // There is no lower/previous contour => this contour is an exterior contour of depth 0. contour.holeOf = null; contour.depth = 0; } return contour; } /** * @param {Array.} sortedEvents * @return {Array.<*>} polygons */ function connectEdges(sortedEvents) { var i, len; var resultEvents = orderEvents(sortedEvents); // "false"-filled array var processed = {}; var contours = []; var loop = function ( ) { if (processed[i]) { return; } var contourId = contours.length; var contour = initializeContourFromContext(resultEvents[i], contours, contourId); // Helper function that combines marking an event as processed with assigning its output contour ID var markAsProcessed = function (pos) { processed[pos] = true; if (pos < resultEvents.length && resultEvents[pos]) { resultEvents[pos].outputContourId = contourId; } }; var pos = i; var origPos = i; var initial = resultEvents[i].point; contour.points.push(initial); /* eslint no-constant-condition: "off" */ while (true) { markAsProcessed(pos); pos = resultEvents[pos].otherPos; markAsProcessed(pos); contour.points.push(resultEvents[pos].point); pos = nextPos(pos, resultEvents, processed, origPos); if (pos == origPos || pos >= resultEvents.length || !resultEvents[pos]) { break; } } contours.push(contour); }; for (i = 0, len = resultEvents.length; i < len; i++) loop( ); return contours; } var tinyqueue = TinyQueue; var default_1 = TinyQueue; function TinyQueue(data, compare) { if (!(this instanceof TinyQueue)) { return new TinyQueue(data, compare); } this.data = data || []; this.length = this.data.length; this.compare = compare || defaultCompare; if (this.length > 0) { for (var i = (this.length >> 1) - 1; i >= 0; i--) { this._down(i); } } } function defaultCompare(a, b) { return a < b ? -1 : a > b ? 1 : 0; } TinyQueue.prototype = { push: function (item) { this.data.push(item); this.length++; this._up(this.length - 1); }, pop: function () { if (this.length === 0) { return undefined; } var top = this.data[0]; this.length--; if (this.length > 0) { this.data[0] = this.data[this.length]; this._down(0); } this.data.pop(); return top; }, peek: function () { return this.data[0]; }, _up: function (pos) { var data = this.data; var compare = this.compare; var item = data[pos]; while (pos > 0) { var parent = (pos - 1) >> 1; var current = data[parent]; if (compare(item, current) >= 0) { break; } data[pos] = current; pos = parent; } data[pos] = item; }, _down: function (pos) { var data = this.data; var compare = this.compare; var halfLength = this.length >> 1; var item = data[pos]; while (pos < halfLength) { var left = (pos << 1) + 1; var right = left + 1; var best = data[left]; if (right < this.length && compare(data[right], best) < 0) { left = right; best = data[right]; } if (compare(best, item) >= 0) { break; } data[pos] = best; pos = left; } data[pos] = item; } }; tinyqueue.default = default_1; var max = Math.max; var min = Math.min; var contourId = 0; function processPolygon(contourOrHole, isSubject, depth, Q, bbox, isExteriorRing) { var i, len, s1, s2, e1, e2; for (i = 0, len = contourOrHole.length - 1; i < len; i++) { s1 = contourOrHole[i]; s2 = contourOrHole[i + 1]; e1 = new SweepEvent(s1, false, undefined, isSubject); e2 = new SweepEvent(s2, false, e1, isSubject); e1.otherEvent = e2; if (s1[0] === s2[0] && s1[1] === s2[1]) { continue; // skip collapsed edges, or it breaks } e1.contourId = e2.contourId = depth; if (!isExteriorRing) { e1.isExteriorRing = false; e2.isExteriorRing = false; } if (compareEvents(e1, e2) > 0) { e2.left = true; } else { e1.left = true; } var x = s1[0], y = s1[1]; bbox[0] = min(bbox[0], x); bbox[1] = min(bbox[1], y); bbox[2] = max(bbox[2], x); bbox[3] = max(bbox[3], y); // Pushing it so the queue is sorted from left to right, // with object on the left having the highest priority. Q.push(e1); Q.push(e2); } } function fillQueue(subject, clipping, sbbox, cbbox, operation) { var eventQueue = new tinyqueue(null, compareEvents); var polygonSet, isExteriorRing, i, ii, j, jj; //, k, kk; for (i = 0, ii = subject.length; i < ii; i++) { polygonSet = subject[i]; for (j = 0, jj = polygonSet.length; j < jj; j++) { isExteriorRing = j === 0; if (isExteriorRing) { contourId++; } processPolygon(polygonSet[j], true, contourId, eventQueue, sbbox, isExteriorRing); } } for (i = 0, ii = clipping.length; i < ii; i++) { polygonSet = clipping[i]; for (j = 0, jj = polygonSet.length; j < jj; j++) { isExteriorRing = j === 0; if (operation === DIFFERENCE) { isExteriorRing = false; } if (isExteriorRing) { contourId++; } processPolygon(polygonSet[j], false, contourId, eventQueue, cbbox, isExteriorRing); } } return eventQueue; } var EMPTY = []; function trivialOperation(subject, clipping, operation) { var result = null; if (subject.length * clipping.length === 0) { if (operation === INTERSECTION) { result = EMPTY; } else if (operation === DIFFERENCE) { result = subject; } else if (operation === UNION || operation === XOR) { result = (subject.length === 0) ? clipping : subject; } } return result; } function compareBBoxes(subject, clipping, sbbox, cbbox, operation) { var result = null; if (sbbox[0] > cbbox[2] || cbbox[0] > sbbox[2] || sbbox[1] > cbbox[3] || cbbox[1] > sbbox[3]) { if (operation === INTERSECTION) { result = EMPTY; } else if (operation === DIFFERENCE) { result = subject; } else if (operation === UNION || operation === XOR) { result = subject.concat(clipping); } } return result; } function boolean(subject, clipping, operation) { if (typeof subject[0][0][0] === 'number') { subject = [subject]; } if (typeof clipping[0][0][0] === 'number') { clipping = [clipping]; } var trivial = trivialOperation(subject, clipping, operation); if (trivial) { return trivial === EMPTY ? null : trivial; } var sbbox = [Infinity, Infinity, -Infinity, -Infinity]; var cbbox = [Infinity, Infinity, -Infinity, -Infinity]; // console.time('fill queue'); var eventQueue = fillQueue(subject, clipping, sbbox, cbbox, operation); //console.timeEnd('fill queue'); trivial = compareBBoxes(subject, clipping, sbbox, cbbox, operation); if (trivial) { return trivial === EMPTY ? null : trivial; } // console.time('subdivide edges'); var sortedEvents = subdivide(eventQueue, subject, clipping, sbbox, cbbox, operation); //console.timeEnd('subdivide edges'); // console.time('connect vertices'); var contours = connectEdges(sortedEvents); //console.timeEnd('connect vertices'); // Convert contours to polygons var polygons = []; for (var i = 0; i < contours.length; i++) { var contour = contours[i]; if (contour.isExterior()) { // The exterior ring goes first var rings = [contour.points]; // Followed by holes if any for (var j = 0; j < contour.holeIds.length; j++) { var holeId = contour.holeIds[j]; rings.push(contours[holeId].points); } polygons.push(rings); } } return polygons; } function union (subject, clipping) { return boolean(subject, clipping, UNION); } function diff (subject, clipping) { return boolean(subject, clipping, DIFFERENCE); } function xor (subject, clipping) { return boolean(subject, clipping, XOR); } function intersection$1 (subject, clipping) { return boolean(subject, clipping, INTERSECTION); } /** * @enum {Number} */ var operations = { UNION: UNION, DIFFERENCE: DIFFERENCE, INTERSECTION: INTERSECTION, XOR: XOR }; exports.diff = diff; exports.intersection = intersection$1; exports.operations = operations; exports.union = union; exports.xor = xor; Object.defineProperty(exports, '__esModule', { value: true }); }))); //# sourceMappingURL=martinez.umd.js.map