UNPKG

fernandez-polygon-decomposition

Version:

An algorithm to decompose polygons with holes from "A practical algorithm for decomposing polygonal domains into convex polygons by diagonals" by J Fernández

1,796 lines (1,582 loc) 70.6 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = global || self, factory(global.fernandezPolygonDecomposition = {})); }(this, function (exports) { 'use strict'; function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function (obj) { return typeof obj; }; } else { _typeof = function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread2(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); } function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); } function _iterableToArrayLimit(arr, i) { if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === "[object Arguments]")) { return; } var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } var robustDiff = robustSubtract; //Easy case: Add two scalars function scalarScalar(a, b) { var x = a + b; var bv = x - a; var av = x - bv; var br = b - bv; var ar = a - av; var y = ar + br; if(y) { return [y, x] } return [x] } function robustSubtract(e, f) { var ne = e.length|0; var nf = f.length|0; if(ne === 1 && nf === 1) { return scalarScalar(e[0], -f[0]) } var n = ne + nf; var g = new Array(n); var count = 0; var eptr = 0; var fptr = 0; var abs = Math.abs; var ei = e[eptr]; var ea = abs(ei); var fi = -f[fptr]; var fa = abs(fi); var a, b; if(ea < fa) { b = ei; eptr += 1; if(eptr < ne) { ei = e[eptr]; ea = abs(ei); } } else { b = fi; fptr += 1; if(fptr < nf) { fi = -f[fptr]; fa = abs(fi); } } if((eptr < ne && ea < fa) || (fptr >= nf)) { a = ei; eptr += 1; if(eptr < ne) { ei = e[eptr]; ea = abs(ei); } } else { a = fi; fptr += 1; if(fptr < nf) { fi = -f[fptr]; fa = abs(fi); } } var x = a + b; var bv = x - a; var y = b - bv; var q0 = y; var q1 = x; var _x, _bv, _av, _br, _ar; while(eptr < ne && fptr < nf) { if(ea < fa) { a = ei; eptr += 1; if(eptr < ne) { ei = e[eptr]; ea = abs(ei); } } else { a = fi; fptr += 1; if(fptr < nf) { fi = -f[fptr]; fa = abs(fi); } } b = q0; x = a + b; bv = x - a; y = b - bv; if(y) { g[count++] = y; } _x = q1 + x; _bv = _x - q1; _av = _x - _bv; _br = x - _bv; _ar = q1 - _av; q0 = _ar + _br; q1 = _x; } while(eptr < ne) { a = ei; b = q0; x = a + b; bv = x - a; y = b - bv; if(y) { g[count++] = y; } _x = q1 + x; _bv = _x - q1; _av = _x - _bv; _br = x - _bv; _ar = q1 - _av; q0 = _ar + _br; q1 = _x; eptr += 1; if(eptr < ne) { ei = e[eptr]; } } while(fptr < nf) { a = fi; b = q0; x = a + b; bv = x - a; y = b - bv; if(y) { g[count++] = y; } _x = q1 + x; _bv = _x - q1; _av = _x - _bv; _br = x - _bv; _ar = q1 - _av; q0 = _ar + _br; q1 = _x; fptr += 1; if(fptr < nf) { fi = -f[fptr]; } } if(q0) { g[count++] = q0; } if(q1) { g[count++] = q1; } if(!count) { g[count++] = 0.0; } g.length = count; return g } var cmp = robustCompare; function robustCompare(a, b) { var d = robustDiff(a, b); return d[d.length-1] } var compress = compressExpansion; function compressExpansion(e) { var m = e.length; var Q = e[e.length-1]; var bottom = m; for(var i=m-2; i>=0; --i) { var a = Q; var b = e[i]; Q = a + b; var bv = Q - a; var q = b - bv; if(q) { e[--bottom] = Q; Q = q; } } var top = 0; for(var i=bottom; i<m; ++i) { var a = e[i]; var b = Q; Q = a + b; var bv = Q - a; var q = b - bv; if(q) { e[top++] = q; } } e[top++] = Q; e.length = top; return e } function createCommonjsModule(fn, module) { return module = { exports: {} }, fn(module, module.exports), module.exports; } var twoProduct_1 = twoProduct; var SPLITTER = +(Math.pow(2, 27) + 1.0); function twoProduct(a, b, result) { var x = a * b; var c = SPLITTER * a; var abig = c - a; var ahi = c - abig; var alo = a - ahi; var d = SPLITTER * b; var bbig = d - b; var bhi = d - bbig; var blo = b - bhi; var err1 = x - (ahi * bhi); var err2 = err1 - (alo * bhi); var err3 = err2 - (ahi * blo); var y = alo * blo - err3; if(result) { result[0] = y; result[1] = x; return result } return [ y, x ] } var robustSum = linearExpansionSum; //Easy case: Add two scalars function scalarScalar$1(a, b) { var x = a + b; var bv = x - a; var av = x - bv; var br = b - bv; var ar = a - av; var y = ar + br; if(y) { return [y, x] } return [x] } function linearExpansionSum(e, f) { var ne = e.length|0; var nf = f.length|0; if(ne === 1 && nf === 1) { return scalarScalar$1(e[0], f[0]) } var n = ne + nf; var g = new Array(n); var count = 0; var eptr = 0; var fptr = 0; var abs = Math.abs; var ei = e[eptr]; var ea = abs(ei); var fi = f[fptr]; var fa = abs(fi); var a, b; if(ea < fa) { b = ei; eptr += 1; if(eptr < ne) { ei = e[eptr]; ea = abs(ei); } } else { b = fi; fptr += 1; if(fptr < nf) { fi = f[fptr]; fa = abs(fi); } } if((eptr < ne && ea < fa) || (fptr >= nf)) { a = ei; eptr += 1; if(eptr < ne) { ei = e[eptr]; ea = abs(ei); } } else { a = fi; fptr += 1; if(fptr < nf) { fi = f[fptr]; fa = abs(fi); } } var x = a + b; var bv = x - a; var y = b - bv; var q0 = y; var q1 = x; var _x, _bv, _av, _br, _ar; while(eptr < ne && fptr < nf) { if(ea < fa) { a = ei; eptr += 1; if(eptr < ne) { ei = e[eptr]; ea = abs(ei); } } else { a = fi; fptr += 1; if(fptr < nf) { fi = f[fptr]; fa = abs(fi); } } b = q0; x = a + b; bv = x - a; y = b - bv; if(y) { g[count++] = y; } _x = q1 + x; _bv = _x - q1; _av = _x - _bv; _br = x - _bv; _ar = q1 - _av; q0 = _ar + _br; q1 = _x; } while(eptr < ne) { a = ei; b = q0; x = a + b; bv = x - a; y = b - bv; if(y) { g[count++] = y; } _x = q1 + x; _bv = _x - q1; _av = _x - _bv; _br = x - _bv; _ar = q1 - _av; q0 = _ar + _br; q1 = _x; eptr += 1; if(eptr < ne) { ei = e[eptr]; } } while(fptr < nf) { a = fi; b = q0; x = a + b; bv = x - a; y = b - bv; if(y) { g[count++] = y; } _x = q1 + x; _bv = _x - q1; _av = _x - _bv; _br = x - _bv; _ar = q1 - _av; q0 = _ar + _br; q1 = _x; fptr += 1; if(fptr < nf) { fi = f[fptr]; } } if(q0) { g[count++] = q0; } if(q1) { g[count++] = q1; } if(!count) { g[count++] = 0.0; } g.length = count; return g } var twoSum = fastTwoSum; function fastTwoSum(a, b, result) { var x = a + b; var bv = x - a; var av = x - bv; var br = b - bv; var ar = a - av; if(result) { result[0] = ar + br; result[1] = x; return result } return [ar+br, x] } var robustScale = scaleLinearExpansion; function scaleLinearExpansion(e, scale) { var n = e.length; if(n === 1) { var ts = twoProduct_1(e[0], scale); if(ts[0]) { return ts } return [ ts[1] ] } var g = new Array(2 * n); var q = [0.1, 0.1]; var t = [0.1, 0.1]; var count = 0; twoProduct_1(e[0], scale, q); if(q[0]) { g[count++] = q[0]; } for(var i=1; i<n; ++i) { twoProduct_1(e[i], scale, t); var pq = q[1]; twoSum(pq, t[0], q); if(q[0]) { g[count++] = q[0]; } var a = t[1]; var b = q[1]; var x = a + b; var bv = x - a; var y = b - bv; q[1] = x; if(y) { g[count++] = y; } } if(q[1]) { g[count++] = q[1]; } if(count === 0) { g[count++] = 0.0; } g.length = count; return g } var orientation_1 = createCommonjsModule(function (module) { var NUM_EXPAND = 5; var EPSILON = 1.1102230246251565e-16; var ERRBOUND3 = (3.0 + 16.0 * EPSILON) * EPSILON; var ERRBOUND4 = (7.0 + 56.0 * EPSILON) * EPSILON; function cofactor(m, c) { var result = new Array(m.length-1); for(var i=1; i<m.length; ++i) { var r = result[i-1] = new Array(m.length-1); for(var j=0,k=0; j<m.length; ++j) { if(j === c) { continue } r[k++] = m[i][j]; } } return result } function matrix(n) { var result = new Array(n); for(var i=0; i<n; ++i) { result[i] = new Array(n); for(var j=0; j<n; ++j) { result[i][j] = ["m", j, "[", (n-i-1), "]"].join(""); } } return result } function sign(n) { if(n & 1) { return "-" } return "" } function generateSum(expr) { if(expr.length === 1) { return expr[0] } else if(expr.length === 2) { return ["sum(", expr[0], ",", expr[1], ")"].join("") } else { var m = expr.length>>1; return ["sum(", generateSum(expr.slice(0, m)), ",", generateSum(expr.slice(m)), ")"].join("") } } function determinant(m) { if(m.length === 2) { return [["sum(prod(", m[0][0], ",", m[1][1], "),prod(-", m[0][1], ",", m[1][0], "))"].join("")] } else { var expr = []; for(var i=0; i<m.length; ++i) { expr.push(["scale(", generateSum(determinant(cofactor(m, i))), ",", sign(i), m[0][i], ")"].join("")); } return expr } } function orientation(n) { var pos = []; var neg = []; var m = matrix(n); var args = []; for(var i=0; i<n; ++i) { if((i&1)===0) { pos.push.apply(pos, determinant(cofactor(m, i))); } else { neg.push.apply(neg, determinant(cofactor(m, i))); } args.push("m" + i); } var posExpr = generateSum(pos); var negExpr = generateSum(neg); var funcName = "orientation" + n + "Exact"; var code = ["function ", funcName, "(", args.join(), "){var p=", posExpr, ",n=", negExpr, ",d=sub(p,n);\ return d[d.length-1];};return ", funcName].join(""); var proc = new Function("sum", "prod", "scale", "sub", code); return proc(robustSum, twoProduct_1, robustScale, robustDiff) } var orientation3Exact = orientation(3); var orientation4Exact = orientation(4); var CACHED = [ function orientation0() { return 0 }, function orientation1() { return 0 }, function orientation2(a, b) { return b[0] - a[0] }, function orientation3(a, b, c) { var l = (a[1] - c[1]) * (b[0] - c[0]); var r = (a[0] - c[0]) * (b[1] - c[1]); var det = l - r; var s; if(l > 0) { if(r <= 0) { return det } else { s = l + r; } } else if(l < 0) { if(r >= 0) { return det } else { s = -(l + r); } } else { return det } var tol = ERRBOUND3 * s; if(det >= tol || det <= -tol) { return det } return orientation3Exact(a, b, c) }, function orientation4(a,b,c,d) { var adx = a[0] - d[0]; var bdx = b[0] - d[0]; var cdx = c[0] - d[0]; var ady = a[1] - d[1]; var bdy = b[1] - d[1]; var cdy = c[1] - d[1]; var adz = a[2] - d[2]; var bdz = b[2] - d[2]; var cdz = c[2] - d[2]; var bdxcdy = bdx * cdy; var cdxbdy = cdx * bdy; var cdxady = cdx * ady; var adxcdy = adx * cdy; var adxbdy = adx * bdy; var bdxady = bdx * ady; var det = adz * (bdxcdy - cdxbdy) + bdz * (cdxady - adxcdy) + cdz * (adxbdy - bdxady); var permanent = (Math.abs(bdxcdy) + Math.abs(cdxbdy)) * Math.abs(adz) + (Math.abs(cdxady) + Math.abs(adxcdy)) * Math.abs(bdz) + (Math.abs(adxbdy) + Math.abs(bdxady)) * Math.abs(cdz); var tol = ERRBOUND4 * permanent; if ((det > tol) || (-det > tol)) { return det } return orientation4Exact(a,b,c,d) } ]; function slowOrient(args) { var proc = CACHED[args.length]; if(!proc) { proc = CACHED[args.length] = orientation(args.length); } return proc.apply(undefined, args) } function generateOrientationProc() { while(CACHED.length <= NUM_EXPAND) { CACHED.push(orientation(CACHED.length)); } var args = []; var procArgs = ["slow"]; for(var i=0; i<=NUM_EXPAND; ++i) { args.push("a" + i); procArgs.push("o" + i); } var code = [ "function getOrientation(", args.join(), "){switch(arguments.length){case 0:case 1:return 0;" ]; for(var i=2; i<=NUM_EXPAND; ++i) { code.push("case ", i, ":return o", i, "(", args.slice(0, i).join(), ");"); } code.push("}var s=new Array(arguments.length);for(var i=0;i<arguments.length;++i){s[i]=arguments[i]};return slow(s);}return getOrientation"); procArgs.push(code.join("")); var proc = Function.apply(undefined, procArgs); module.exports = proc.apply(undefined, [slowOrient].concat(CACHED)); for(var i=0; i<=NUM_EXPAND; ++i) { module.exports[i] = CACHED[i]; } } generateOrientationProc(); }); var product = robustProduct; function robustProduct(a, b) { if(a.length === 1) { return robustScale(b, a[0]) } if(b.length === 1) { return robustScale(a, b[0]) } if(a.length === 0 || b.length === 0) { return [0] } var r = [0]; if(a.length < b.length) { for(var i=0; i<a.length; ++i) { r = robustSum(r, robustScale(b, a[i])); } } else { for(var i=0; i<b.length; ++i) { r = robustSum(r, robustScale(a, b[i])); } } return r } /** * Useful to avoid floating point problems. */ var EPSILON = 0.00000001; var robust = true; var setRobustness = function setRobustness(bool) { robust = bool; }; var getRobustness = function getRobustness() { return robust; }; /** * Compares two vertices of the same polygon. Both of the vertex must be defined by an unique id. * * @param {{ x: number, y: number, id: number }} vertex1 * @param {{ x: number, y: number, id: number }} vertex2 * @returns {boolean} */ function vertexEquality(vertex1, vertex2) { if (vertex1.id === undefined || vertex2.id === undefined) { throw new Error('A vertex must be defined by an unique id.'); } return vertex1.id === vertex2.id; } /** * Compares two vertices. Both of the vertex must be defined by an unique id and optionnally by an originalId. * This method is used to compare vertices after the absoption phase of the absHol procedure. * * @param {{ x: number, y: number, id: number, originalId: number }} vertex1 * @param {{ x: number, y: number, id: number, originalId: number }} vertex2 * @returns {boolean} */ function vertexEqualityAfterAbsorption(vertex1, vertex2) { if (vertex1.id === undefined || vertex2.id === undefined) { throw new Error('A vertex must be defined by an unique id.'); } return (vertex1.originalId || vertex1.id) === (vertex2.originalId || vertex2.id); } /** * @param {{ x: number, y: number }} point1 * @param {{ x: number, y: number }} point2 * @returns {boolean} */ function pointEquality(point1, point2) { return point1.x === point2.x && point1.y === point2.y; } /** * @param {{ x: number, y: number }} point1 * @param {{ x: number, y: number }} point2 * @returns {number} */ function squaredDistance(point1, point2) { return (point1.x - point2.x) * (point1.x - point2.x) + (point1.y - point2.y) * (point1.y - point2.y); } /** * Checks if the polygon is flat (its area is 0). * Using orientation for robustness * * @param {{{ x: number, y: number, id: number }[]}} polygon * @returns {boolean} */ function isFlat(polygon) { var polygonLength = polygon.length; return polygon.every(function (point, index) { var previousPoint = polygon[(index - 1 + polygonLength) % polygonLength]; var nextPoint = polygon[(index + 1) % polygonLength]; return orientation(previousPoint, point, nextPoint) === 0; }); } /** * Check if the polygon vertices are clockwise ordered. * Using orientation for robustness * * @param {{ x: number, y: number}[]} polygon * @returns {boolean} */ function isClockwiseOrdered(polygon) { if (!isSimple(polygon)) { throw new Error('The isClockwiseOrdered method only works with simple polygons'); } var polygonLength = polygon.length; // find bottom right vertex var bottomRightVertexIndex = 0; var bottomRightVertex = polygon[0]; for (var i = 1; i < polygonLength; i++) { var vertex = polygon[i]; if (vertex.y < bottomRightVertex.y || vertex.y === bottomRightVertex.y && vertex.x > bottomRightVertex.x) { bottomRightVertex = vertex; bottomRightVertexIndex = i; } } return orientation(polygon[(bottomRightVertexIndex - 1 + polygonLength) % polygonLength], bottomRightVertex, polygon[(bottomRightVertexIndex + 1) % polygonLength]) > 0; } /** * This method always returns a new array * * @param {{ x: number, y: number}[]} polygon * @returns {{ x: number, y: number}[]} */ function orderClockwise(polygon) { if (!isClockwiseOrdered(polygon)) { return _toConsumableArray(polygon).reverse(); } return _toConsumableArray(polygon); } /** * See https://stackoverflow.com/questions/38856588/given-three-coordinate-points-how-do-you-detect-when-the-angle-between-them-cro. * The three points are in clockwise order. * If the result if positive, then it is a clockwise turn, if it is negative, a ccw one. * If the result is 0, the points are collinear. * * @param {{ x: number, y: number}} point1 * @param {{ x: number, y: number}} point2 * @param {{ x: number, y: number}} point3 * @returns {number} */ function orientation(point1, point2, point3) { if (robust) { var o = orientation_1([point1.x, point1.y], [point2.x, point2.y], [point3.x, point3.y]); return o === 0 ? o : -o; // the y-axis is inverted } else { // return -((point1.y - point3.y) * (point2.x - point3.x) - (point1.x - point3.x) * (point2.y - point3.y)); return (point2.x - point1.x) * (point3.y - point1.y) - (point2.y - point1.y) * (point3.x - point1.x); } } /** * Checks on which side of the line (point2, point3) the point point1 is. * * @param {{ x: number, y: number}} point1 * @param {{ x: number, y: number}} point2 * @param {{ x: number, y: number}} point3 * @returns {number} */ function sideOfLine(point1, point2, point3) { return orientation(point2, point3, point1); } var VERTEX_CODE = 100; var EDGE_CODE = 101; /** * Winding number algorithm. * See https://en.wikipedia.org/wiki/Point_in_polygon?#Winding_number_algorithm * And more specifically http://www.inf.usi.ch/hormann/papers/Hormann.2001.TPI.pdf * * @param {{ x: number, y: number}} point * @param {{ x: number, y: number}[]} polygon * @returns {number} */ function windingNumber(point, polygon) { var polygonPoint = polygon[0]; if (polygonPoint.x === point.x && polygonPoint.y === point.y) { return VERTEX_CODE; } var polygonLength = polygon.length; var wn = 0; for (var i = 0; i < polygonLength; i++) { var _polygonPoint = polygon[i]; var nextPolygonPoint = polygon[(i + 1) % polygonLength]; if (nextPolygonPoint.y === point.y) { if (nextPolygonPoint.x === point.x) { return VERTEX_CODE; } else { if (_polygonPoint.y === point.y && nextPolygonPoint.x > point.x === _polygonPoint.x < point.x) { return EDGE_CODE; } } } if (_polygonPoint.y < point.y !== nextPolygonPoint.y < point.y) { // crossing if (_polygonPoint.x >= point.x) { if (nextPolygonPoint.x > point.x) { // wn += 2 * (nextPolygonPoint.y > polygonPoint.y ? 1 : -1) - 1; wn += nextPolygonPoint.y > _polygonPoint.y ? 1 : -1; } else { var det = (_polygonPoint.x - point.x) * (nextPolygonPoint.y - point.y) - (nextPolygonPoint.x - point.x) * (_polygonPoint.y - point.y); if (det === 0) { return EDGE_CODE; } else if (det > 0 === nextPolygonPoint.y > _polygonPoint.y) { // right_crossing // wn += 2 * (nextPolygonPoint.y > polygonPoint.y ? 1 : -1) - 1; wn += nextPolygonPoint.y > _polygonPoint.y ? 1 : -1; } } } else { if (nextPolygonPoint.x > point.x) { var _det = (_polygonPoint.x - point.x) * (nextPolygonPoint.y - point.y) - (nextPolygonPoint.x - point.x) * (_polygonPoint.y - point.y); if (_det === 0) { return EDGE_CODE; } else if (_det > 0 === nextPolygonPoint.y > _polygonPoint.y) { // right_crossing // wn += 2 * (nextPolygonPoint.y > polygonPoint.y ? 1 : -1) - 1; wn += nextPolygonPoint.y > _polygonPoint.y ? 1 : -1; } } } } } return wn; } /** * Winding number algorithm. * Using robust arithmetic. * * @param {{ x: number, y: number}} point * @param {{ x: number, y: number}[]} polygon * @returns {number} */ function robustWindingNumber(point, polygon) { var polygonPoint = polygon[0]; if (polygonPoint.x === point.x && polygonPoint.y === point.y) { return VERTEX_CODE; } var polygonLength = polygon.length; var wn = 0; for (var i = 0; i < polygonLength; i++) { var _polygonPoint2 = polygon[i]; var nextPolygonPoint = polygon[(i + 1) % polygonLength]; if (nextPolygonPoint.y === point.y) { if (nextPolygonPoint.x === point.x) { return VERTEX_CODE; } else { if (_polygonPoint2.y === point.y && nextPolygonPoint.x > point.x === _polygonPoint2.x < point.x) { return EDGE_CODE; } } } if (_polygonPoint2.y < point.y !== nextPolygonPoint.y < point.y) { // crossing if (_polygonPoint2.x >= point.x) { if (nextPolygonPoint.x > point.x) { // wn += 2 * ((nextPolygonPoint.y > polygonPoint.y) | 0) - 1; wn += nextPolygonPoint.y > _polygonPoint2.y ? 1 : -1; } else { var det = robustDiff(product(robustDiff([_polygonPoint2.x], [point.x]), robustDiff([nextPolygonPoint.y], [point.y])), product(robustDiff([nextPolygonPoint.x], [point.x]), robustDiff([_polygonPoint2.y], [point.y]))); var detComparison = cmp(det, [0]); if (detComparison === 0) { return EDGE_CODE; } else if (detComparison > 0 === nextPolygonPoint.y > _polygonPoint2.y) { // right_crossing // wn += 2 * ((nextPolygonPoint.y > polygonPoint.y) | 0) - 1; wn += nextPolygonPoint.y > _polygonPoint2.y ? 1 : -1; } } } else { if (nextPolygonPoint.x > point.x) { var _det2 = robustDiff(product(robustDiff([_polygonPoint2.x], [point.x]), robustDiff([nextPolygonPoint.y], [point.y])), product(robustDiff([nextPolygonPoint.x], [point.x]), robustDiff([_polygonPoint2.y], [point.y]))); var _detComparison = cmp(_det2, [0]); if (_detComparison === 0) { return EDGE_CODE; } else if (_detComparison > 0 === nextPolygonPoint.y > _polygonPoint2.y) { // right_crossing // wn += 2 * ((nextPolygonPoint.y > polygonPoint.y) | 0) - 1; wn += nextPolygonPoint.y > _polygonPoint2.y ? 1 : -1; } } } } } return wn; } /** * Checks if the point is inside (or on the edge) of the polygon. * * @param {{ x: number, y: number}} point * @param {{ x: number, y: number}[]} polygon * @returns {boolean} */ function inPolygon(point, polygon) { if (robust) { // return classifyPoint(polygon.map(({ x, y }) => [x, y]), [point.x, point.y]) <= 0; return robustWindingNumber(point, polygon) !== 0; } else { return windingNumber(point, polygon) !== 0; } } /** * Checks if the point is inside (or on the edge) of a convex polygon. * We assume that the vertices are in clockwise order. * * @param {{ x: number, y: number}} point * @param {{ x: number, y: number}[]} convexPolygon * @returns {boolean} */ function inConvexPolygon(point, convexPolygon) { var polygonLength = convexPolygon.length; return convexPolygon.every(function (previousPoint, index) { var nextPoint = convexPolygon[(index + 1) % polygonLength]; return orientation(previousPoint, point, nextPoint) <= 0; }); } /** * Check if the polygon polygon2 is (at least partially) contained by the polygon polygon1. * * @param {{ x: number, y: number, id: number }[]} polygon1 * @param {{ x: number, y: number, id: number }[]} polygon2 * @returns {boolean} */ function containsPolygon(polygon1, polygon2) { return polygon2.some(function (vertex) { return inPolygon(vertex, polygon1); }); } /** * Check if the polygon polygon2 is totally contained by the polygon polygon1. * * @param {{ x: number, y: number, id: number }[]} polygon1 * @param {{ x: number, y: number, id: number }[]} polygon2 * @returns {boolean} */ function containsEntirePolygon(polygon1, polygon2) { return polygon2.every(function (vertex) { return inPolygon(vertex, polygon1); }); } /** * Given a vertex of one polygon, returns the next vertex (in clockwise order) of this polygon. * * @param {{ x: number, y: number, id: number }} vertex * @param {{ x: number, y: number, id: number }[]} polygon * @returns {{ x: number, y: number, id: number }} */ function nextVertex(vertex, polygon) { var polygonLength = polygon.length; var vertexIndex = polygon.findIndex(function (v) { return vertexEquality(vertex, v); }); if (vertexIndex === -1) { throw new Error('could not find vertex'); } return polygon[(vertexIndex + 1) % polygonLength]; } /** * Given a vertex of one polygon, returns the previous vertex (in clockwise order) of this polygon. * * @param {{ x: number, y: number, id: number }} vertex * @param {{ x: number, y: number, id: number }[]} polygon * @returns {{ x: number, y: number, id: number }} */ function previousVertex(vertex, polygon) { var polygonLength = polygon.length; var vertexIndex = polygon.findIndex(function (v) { return vertexEquality(vertex, v); }); if (vertexIndex === -1) { throw new Error('could not find vertex'); } return polygon[(vertexIndex - 1 + polygonLength) % polygonLength]; } /** * Checks if a point is one of the vertex of a polygon. * * @param {{ x: number, y: number }} point * @param {{ x: number, y: number, id: number }[]} polygon * @returns {boolean} */ function isAVertex(point, polygon) { return polygon.some(function (v) { return pointEquality(v, point); }); } /** * Returns all the notches of a given polygon. * * @param {{ x: number, y: number, id: number }[]} polygon * @returns {{ x: number, y: number, id: number }[]} */ function getNotches(polygon) { var polygonLength = polygon.length; return polygon.filter(function (vertex, vertexIndex) { return orientation(polygon[(vertexIndex - 1 + polygonLength) % polygonLength], vertex, polygon[(vertexIndex + 1) % polygonLength]) < 0; }); } /** * Returns all the edges of a given polygon. * An edge is one segment between two consecutive vertex of the polygon. * * @param {{ x: number, y: number, id: number }[]} polygon * @returns {{ a: { x: number, y: number, id: number }, b: { x: number, y: number, id: number }}[]} */ function getEdges(polygon) { var edges = []; var polygonLength = polygon.length; for (var i = 0; i < polygonLength; i++) { edges.push({ a: polygon[i], b: polygon[(i + 1) % polygonLength] }); } return edges; } /** * Given a vertex of one polygon, returns the next notch (in clockwise order) of this polygon. * Returns null if there are no notch in the polygon. * * @param {{ x: number, y: number, id: number }} vertex * @param {{ x: number, y: number, id: number }[]} polygon * @returns {({ x: number, y: number, id: number }|null)} */ function nextNotch(vertex, polygon) { var polygonLength = polygon.length; var vertexIndex = polygon.findIndex(function (v) { return vertexEquality(vertex, v); }); var notchIndex = (vertexIndex + 1) % polygonLength; while (notchIndex !== vertexIndex) { if (orientation(polygon[(notchIndex - 1 + polygonLength) % polygonLength], polygon[notchIndex], polygon[(notchIndex + 1) % polygonLength]) < 0) { return polygon[notchIndex]; } notchIndex = (notchIndex + 1) % polygonLength; } // if we started by the only notch, it will return the notch. if (orientation(polygon[(notchIndex - 1 + polygonLength) % polygonLength], polygon[notchIndex], polygon[(notchIndex + 1) % polygonLength]) < 0) { return polygon[notchIndex]; } return null; } /** * Given a vertex of one polygon, returns the previous notch (in clockwise order) of this polygon. * Returns null if there are no notch in the polygon. * * @param {{ x: number, y: number, id: number }} vertex * @param {{ x: number, y: number, id: number }[]} polygon * @returns {({ x: number, y: number, id: number }|null)} */ function previousNotch(vertex, polygon) { var polygonLength = polygon.length; var vertexIndex = polygon.findIndex(function (v) { return vertexEquality(vertex, v); }); var notchIndex = (vertexIndex - 1 + polygonLength) % polygonLength; while (notchIndex !== vertexIndex) { if (orientation(polygon[(notchIndex - 1 + polygonLength) % polygonLength], polygon[notchIndex], polygon[(notchIndex + 1) % polygonLength]) < 0) { return polygon[notchIndex]; } notchIndex = (notchIndex - 1 + polygonLength) % polygonLength; } // if we started by the only notch, it will return the notch. if (orientation(polygon[(notchIndex - 1 + polygonLength) % polygonLength], polygon[notchIndex], polygon[(notchIndex + 1) % polygonLength]) < 0) { return polygon[notchIndex]; } return null; } /** * Removes polygon2 from polygon1. * polygon2 vertices must be a subset of polygon1 vertices * * @param {{ x: number, y: number, id: number }[]} polygon1 * @param {{ x: number, y: number, id: number }[]} polygon2 * @returns {{ x: number, y: number, id: number }[]} */ function substractPolygons(polygon1, polygon2) { // const firstIndex = polygon1.findIndex(p => pointEquality(p, polygon2[0])); // const lastIndex = polygon1.findIndex(p => pointEquality(p, polygon2[polygon2.length - 1])); var firstIndex = polygon1.findIndex(function (p) { return vertexEquality(p, polygon2[0]); }); var lastIndex = polygon1.findIndex(function (p) { return vertexEquality(p, polygon2[polygon2.length - 1]); }); if (firstIndex < lastIndex) { return [].concat(_toConsumableArray(polygon1.slice(0, firstIndex)), [polygon1[firstIndex]], _toConsumableArray(polygon1.slice(lastIndex))); } else { return _toConsumableArray(polygon1.slice(lastIndex, firstIndex + 1)); } } /** * @param {{{ x: number, y: number, id: number }[]}} polygon * @return {boolean} */ function isConvex(polygon) { var polygonLength = polygon.length; return polygon.every(function (vertex, vertexIndex) { return orientation(polygon[(vertexIndex - 1 + polygonLength) % polygonLength], vertex, polygon[(vertexIndex + 1) % polygonLength]) >= 0; }); } /** * Quick hack to convert a non-overlapping increasing sequence into a number. * * @param {number[]} sequence * @returns {number} */ function robustSequenceToNumber(sequence) { var compressedSequence = compress(_toConsumableArray(sequence)); return compressedSequence[compressedSequence.length - 1]; } /** * See http://paulbourke.net/geometry/pointlineplane/ * This method performs exact arithmetic calculations (except for the x and y values) * * @param {{ a: { x: number, y: number }, b: { x: number, y: number }}} line1 * @param {{ a: { x: number, y: number }, b: { x: number, y: number }}} line2 * @returns {{ x: number, y: number, insideSegment1: boolean, onEdgeSegment1: boolean, insideSegment2: boolean, onEdgeSegment2: boolean }} */ function robustLineIntersection(line1, line2) { var _line1$a = line1.a, x1 = _line1$a.x, y1 = _line1$a.y, _line1$b = line1.b, x2 = _line1$b.x, y2 = _line1$b.y; var _line2$a = line2.a, x3 = _line2$a.x, y3 = _line2$a.y, _line2$b = line2.b, x4 = _line2$b.x, y4 = _line2$b.y; var y4y3 = robustDiff([y4], [y3]); var x2x1 = robustDiff([x2], [x1]); var x4x3 = robustDiff([x4], [x3]); var y2y1 = robustDiff([y2], [y1]); var y1y3 = robustDiff([y1], [y3]); var x1x3 = robustDiff([x1], [x3]); var denom = robustDiff(product(y4y3, x2x1), product(x4x3, y2y1)); var robustDenomComparison = cmp(denom, [0]); if (robustDenomComparison === 0) { return null; } var ua = robustDiff(product(x4x3, y1y3), product(y4y3, x1x3)); var ub = robustDiff(product(x2x1, y1y3), product(y2y1, x1x3)); var comparisonUaMin, comparisonUaMax, comparisonUbMin, comparisonUbMax; if (robustDenomComparison > 0) { comparisonUaMin = cmp(ua, [0]); comparisonUaMax = cmp(ua, denom); comparisonUbMin = cmp(ub, [0]); comparisonUbMax = cmp(ub, denom); } else { comparisonUaMin = cmp([0], ua); comparisonUaMax = cmp(denom, ua); comparisonUbMin = cmp([0], ub); comparisonUbMax = cmp(denom, ub); } var nonRobustDenom = robustSequenceToNumber(denom); // x and y are not exact numbers, but it is enough for the algo return { x: x1 + robustSequenceToNumber(product(ua, x2x1)) / nonRobustDenom, y: y1 + robustSequenceToNumber(product(ua, y2y1)) / nonRobustDenom, insideSegment1: comparisonUaMin > 0 && comparisonUaMax < 0, onEdgeSegment1: comparisonUaMin === 0 || comparisonUaMax === 0, insideSegment2: comparisonUbMin > 0 && comparisonUbMax < 0, onEdgeSegment2: comparisonUbMin === 0 || comparisonUbMax === 0 }; } /** * See http://paulbourke.net/geometry/pointlineplane/ * * @param {{ a: { x: number, y: number }, b: { x: number, y: number }}} line1 * @param {{ a: { x: number, y: number }, b: { x: number, y: number }}} line2 * @returns {{ x: number, y: number, insideSegment1: boolean, onEdgeSegment1: boolean, insideSegment2: boolean, onEdgeSegment2: boolean }} */ function lineIntersection(line1, line2) { if (robust) { return robustLineIntersection(line1, line2); } var _line1$a2 = line1.a, x1 = _line1$a2.x, y1 = _line1$a2.y, _line1$b2 = line1.b, x2 = _line1$b2.x, y2 = _line1$b2.y; var _line2$a2 = line2.a, x3 = _line2$a2.x, y3 = _line2$a2.y, _line2$b2 = line2.b, x4 = _line2$b2.x, y4 = _line2$b2.y; var denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1); if (Math.abs(denom) < EPSILON) { return null; } var ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denom; var ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denom; return { x: x1 + ua * (x2 - x1), y: y1 + ua * (y2 - y1), insideSegment1: ua > 0 && ua < 1, onEdgeSegment1: ua === 0 || ua === 1, insideSegment2: ub > 0 && ub < 1, onEdgeSegment2: ub === 0 || ub === 1 }; } /** * Checks if the polygon is simple. * See https://en.wikipedia.org/wiki/Simple_polygon * * @param {{ x: number, y: number, id: number }[]} polygon * @returns {boolean} */ function isSimple(polygon) { if (polygon.length < 3) { return true; } var segments = []; for (var i = 0; i < polygon.length - 1; i++) { segments.push({ a: polygon[i], b: polygon[i + 1] }); } segments.push({ a: polygon[polygon.length - 1], b: polygon[0] }); return !segments.some(function (segment1) { return segments.some(function (segment2) { if (segment1 === segment2) { return false; } var intersection = lineIntersection(segment1, segment2); if (intersection === null) { return false; } var insideSegment1 = intersection.insideSegment1, insideSegment2 = intersection.insideSegment2; return insideSegment1 && insideSegment2; }); }); } /** * This is the MP1 procedure taken from "Algorithms for the decomposition of a polygon into convex polygons" by J. Fernandez et al. * The variable names (for the most part) are named according to the publication. * * @param {{ x: number, y: number, id: number }[]} P P is a polygon with clockwise ordered vertices. * @param {{ x: number, y: number, id: number }[]} initialLVertices The initial vertices for the polygon L * @returns {{ L: { x: number, y: number, id: number }[], end: boolean }} */ function MP1(P, initialLVertices) { if (P.length < 4) { return { L: _toConsumableArray(P), end: true }; } var L = _toConsumableArray(initialLVertices); if (L.length < 1) { L.push(P[0]); } if (L.length < 2) { L.push(nextVertex(L[0], P)); } var v1 = L[0]; var v2 = L[1]; var vim1 = v1; var vi = v2; var vip1 = nextVertex(vi, P); while (L.length < P.length) { if (orientation(vim1, vi, vip1) >= 0 && orientation(vi, vip1, v1) >= 0 && orientation(vip1, v1, v2) >= 0) { L.push(vip1); } else { break; } vim1 = vi; vi = vip1; vip1 = nextVertex(vi, P); } if (P.length === L.length) { return { L: L, end: true }; } else if (L.length < 2) { return { L: L, end: true }; } else { var _loop = function _loop() { var PmL = substractPolygons(P, L); // filter on L's bounding rectangle first ? var notches = getNotches(PmL).filter(function (point) { return !isAVertex(point, L); }).filter(function (point) { return inConvexPolygon(point, L); }); if (notches.length === 0) { return "break"; } var v1 = L[0]; var k = L.length; var vk = L[k - 1]; L = L.slice(0, -1); // L.filter(point => !vertexEquality(point, vk)); notches.forEach(function (notch) { var sideOfVk = Math.sign(sideOfLine(vk, v1, notch)); if (sideOfVk !== 0) { L = L.filter(function (point) { return Math.sign(sideOfLine(point, v1, notch)) !== sideOfVk; }); } }); }; while (L.length > 2) { var _ret = _loop(); if (_ret === "break") break; } return { L: L, end: false }; } } /** * This is the MP1' procedure taken from "Algorithms for the decomposition of a polygon into convex polygons" by J. Fernandez et al. * The variable names (for the most part) are named according to the publication. * * @param {{ x: number, y: number, id: number }[]} P P is a polygon with clockwise ordered vertices. * @param {{ x: number, y: number, id: number }[]} initialLVertices The initial vertices for the polygon L * @returns {{ L: { x: number, y: number, id: number }[], end: boolean }} */ function MP1Prime(P, initialLVertices) { if (P.length < 4) { return { L: _toConsumableArray(P), end: true }; } var L = _toConsumableArray(initialLVertices); if (L.length < 1) { L.unshift(P[0]); } if (L.length < 2) { L.unshift(previousVertex(L[0], P)); } var vk = L[L.length - 1]; var vkm1 = L[L.length - 2]; var vim1 = L[1]; var vi = L[0]; var vip1 = previousVertex(vi, P); while (L.length < P.length) { if (orientation(vim1, vi, vip1) <= 0 && orientation(vi, vip1, vk) <= 0 && orientation(vip1, vk, vkm1) <= 0) { L.unshift(vip1); } else { break; } vim1 = vi; vi = vip1; vip1 = previousVertex(vi, P); } if (P.length === L.length) { return { L: L, end: true }; } else if (L.length < 2) { return { L: L, end: true }; } else { var _loop2 = function _loop2() { var PmL = substractPolygons(P, L); var notches = getNotches(PmL).filter(function (point) { return !isAVertex(point, L); }).filter(function (point) { return inConvexPolygon(point, L); }); if (notches.length === 0) { return "break"; } // CCW order var v1 = L[L.length - 1]; var vk = L[0]; L = L.slice(1); // L.filter(point => !vertexEquality(point, vk)); notches.forEach(function (notch) { var sideOfVk = Math.sign(sideOfLine(vk, v1, notch)); if (sideOfVk !== 0) { L = L.filter(function (point) { return Math.sign(sideOfLine(point, v1, notch)) !== sideOfVk; }); } }); }; while (L.length > 2) { var _ret2 = _loop2(); if (_ret2 === "break") break; } return { L: L, end: false }; } } function rotateRight(arr, n) { var length = arr.length; return [].concat(_toConsumableArray(arr.slice(length - n)), _toConsumableArray(arr.slice(0, length - n))); } /** * Merges two polygons. * polygon1 and polygon2 should be convex and share an edge (= two consecutive vertices) * * @param {{ x: number, y: number, id: number }[]} polygon1 * @param {{ x: number, y: number, id: number }[]} polygon2 * @returns {{ x: number, y: number, id: number }[]} */ function mergePolygons(polygon1, polygon2) { // const sharedVertices = polygon1.map((v1, index) => [index, polygon2.findIndex(v2 => pointEquality(v1, v2))]).filter(([_, v2Index]) => v2Index > -1); // can be problematic when a point corresponds to several vertices var sharedVertices = polygon1.map(function (v1, index) { return [index, polygon2.findIndex(function (v2) { return vertexEqualityAfterAbsorption(v1, v2); })]; }).filter(function (_ref) { var _ref2 = _slicedToArray(_ref, 2), _ = _ref2[0], v2Index = _ref2[1]; return v2Index > -1; }); var polygon1Length = polygon1.length; if (sharedVertices.length !== 2) { throw new Error("sharedVertices length should be 2 : ".concat(JSON.stringify(sharedVertices))); } if ((sharedVertices[0][0] + 1) % polygon1Length === sharedVertices[1][0]) { return [].concat(_toConsumableArray(rotateRight(polygon1, polygon1Length - sharedVertices[1][0])), _toConsumableArray(rotateRight(polygon2, polygon2.length - sharedVertices[0][1]).slice(1, -1))); } else { return [].concat(_toConsumableArray(rotateRight(polygon1, polygon1Length - sharedVertices[0][0])), _toConsumableArray(rotateRight(polygon2, polygon2.length - sharedVertices[1][1]).slice(1, -1))); } } /** * Procedure to remove inessential diagonals from the partition of convex polygons. * * @params {{ x: number, y: number, id: number }[][]} polygons * @params {{ i2: { x: number, y: number, id: number }, j2: { x: number, y: number, id: number }, rightPolygon: { x: number, y: number, id: number }[][], leftPolygon: { x: number, y: number, id: number }[][] }[]} * @returns {{ x: number, y: number, id: number }[][]} */ function mergingAlgorithm(polygons, LLE) { if (polygons.length < 2) { return polygons; } // LDP[poly] = true means that the polygon `poly` is one of the definitive polygons of the partition after the merging process. var LDP = new Map(); // LUP[poly1] = poly2 means that the polygon `poly1` is part of the polygon `poly2`. var LUP = new Map(); polygons.forEach(function (poly) { LDP.set(poly, true); LUP.set(poly, poly); }); if (LLE.length + 1 !== polygons.length) { throw new Error('wtf ? LLE + 1 !== polygons.length (' + (LLE.length + 1) + ', ' + polygons.length + ')'); } var _loop = function _loop(j) { var _LLE$j =