UNPKG

polygon-tools

Version:
365 lines (315 loc) 10.3 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.WINDING_CW = exports.WINDING_CCW = exports.WINDING_UNKNOWN = undefined; var _slicedToArray = function () { function sliceIterator(arr, i) { 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"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); /** * @module polygon */ exports.ccw = ccw; exports.normal = normal; exports.area = area; exports.centroid = centroid; exports.is_ccw = is_ccw; exports.is_cw = is_cw; exports.winding = winding; exports.bounds = bounds; exports.ensure_cw = ensure_cw; exports.ensure_ccw = ensure_ccw; exports.triangulate = triangulate; exports.subtract = subtract; exports.union = union; exports.intersection = intersection; var _tesselator = require('./tesselator'); var tess = _interopRequireWildcard(_tesselator); var _vec = require('./vec'); var vec = _interopRequireWildcard(_vec); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } var WINDING_UNKNOWN = exports.WINDING_UNKNOWN = 0; var WINDING_CCW = exports.WINDING_CCW = 1; var WINDING_CW = exports.WINDING_CW = 2; function ccw(a, b, c) { return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]); } /** * Polygon normal (2d / 3d) * * @param {Array} pts Points of the polygon * @param {Boolean} [forceNewell=false] Whether to force Newell's method * * @return {Array} Polygon normal or null if the polygon is degenerate */ function normal(pts) { var forceNewell = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; if (pts.length < 3) return null; var vs = pts.map(function (p) { return p.length >= 3 ? p : [p[0], p[1], 0]; }); if (!forceNewell) { var _vs = _slicedToArray(vs, 3), a = _vs[0], b = _vs[1], c = _vs[2], ba = vec.subtract(b, a), ca = vec.subtract(c, a), cr = vec.normalize(vec.cross(ba, ca)); if (cr.some(function (v) { return isNaN(v); })) { if (pts.length === 3) return null; } else { return cr; } } // fallback to Newell's method var n = [0, 0, 0]; vs.forEach(function (v, i) { var w = vs[(i + 1) % pts.length]; n[0] = n[0] + (v[1] - w[1]) * (v[2] + w[2]); n[1] = n[1] + (v[2] - w[2]) * (v[0] + w[0]); n[2] = n[2] + (v[0] - w[0]) * (v[1] + w[1]); }); n = vec.normalize(n); return n.some(function (v) { return isNaN(v); }) ? null : n; } /** * Signed area of a polygon. * For 3d polygons a signed area can only be computed when the optional * polygon normal ```n``` is passed in. * @see http://stackoverflow.com/questions/12642256/python-find-area-of-polygon-from-xyz-coordinates * * @param {Array} pts Polygon points * @param {Array} [n=null] Optional polygon normal, needed to compute the signed area for 3d polygons * * @return {Number} */ function area(pts) { var n = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; if (pts.length < 3) return 0; if (pts[0].length < 3) { return pts.reduce(function (a, p, i) { var pn = pts[i + 1] || pts[0]; return a + p[0] * pn[1] - pn[0] * p[1]; }, 0) / 2; } else { var num = pts.length, nrm = n || normal(pts), total = [0, 0, 0]; if (!nrm) return 0; for (var i = 0; i < num; ++i) { var v = pts[i], w = pts[(i + 1) % num]; total = vec.add(total, vec.cross(v, w)); } return vec.dot(total, nrm) / 2; } } /** * Polygon centroid (2d) * * @param {Array} pts * * @return {Array} */ function centroid(pts) { var _pts$reduce = pts.reduce(function (_ref, p, i) { var _ref2 = _slicedToArray(_ref, 2), x = _ref2[0], y = _ref2[1]; var pn = pts[i + 1] || pts[0], c = p[0] * pn[1] - pn[0] * p[1]; return [x + (p[0] + pn[0]) * c, y + (p[1] + pn[1]) * c]; }, [0, 0]), _pts$reduce2 = _slicedToArray(_pts$reduce, 2), x = _pts$reduce2[0], y = _pts$reduce2[1]; var ar = area(pts); if (x !== 0) { x = x / (Math.abs(ar) * 6); } if (y !== 0) { y = y / (Math.abs(ar) * 6); } if (ar < 0) { x = -x; y = -y; } return [x, y]; } /** * Tests wether the polygon winding is counter clockwise * * @param {Array} pts * @param {Array} [n=null] Optional polygon normal, needed for 3d polygons * * @return {Boolean} */ function is_ccw(pts) { var n = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; return area(pts, n) > 0; } /** * Tests wether the polygon winding is clockwise * * @param {Array} pts * @param {Array} [n=null] Optional polygon normal, needed for 3d polygons * * @return {Boolean} */ function is_cw(pts) { var n = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; return area(pts, n) < 0; } /** * Polygon winding (2d only) * * @param {Array} pts * @param {Array} [n=null] Optional polygon normal, needed for 3d polygons * * @return {Number} */ function winding(pts) { var n = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; var a = area(pts, n); if (a < 0) return WINDING_CW; if (a > 0) return WINDING_CCW; return WINDING_UNKNOWN; } /** * Polygon bounds. * @typedef {Object} PolygonBounds * @property {Number} xMin * @property {Number} yMin * @property {Number} xMax * @property {Number} yMax */ /** * Polygon bounds * * @param {Array} pts * * @return {PolygonBounds} */ function bounds(pts) { var min = [Number.MAX_VALUE, Number.MAX_VALUE], max = [-Number.MAX_VALUE, -Number.MAX_VALUE]; pts.forEach(function (p) { for (var i = 0; i < Math.min(3, p.length); ++i) { min[i] = Math.min(min[i], p[i]); max[i] = Math.max(max[i], p[i]); } }); return { xMin: min[0], yMin: min[1], xMax: max[0], yMax: max[1] }; } /** * Ensures CW winding * * @param {Array} pts * @param {Array} [n=null] Optional polygon normal, needed for 3d polygons * * @return {Array} */ function ensure_cw(pts) { var n = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; if (is_ccw(pts, n)) pts.reverse(); return pts; } /** * Ensures CCW winding * * @param {Array} pts * @param {Array} [n=null] Optional polygon normal, needed for 3d polygons * * @return {Array} */ function ensure_ccw(pts) { var n = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; if (is_cw(pts, n)) pts.reverse(); return pts; } /** * Triangulates a polygon * * @param {Array} polygon * @param {Array.<Array>} holes * * @return triangles */ function triangulate(polygon, holes) { if (!polygon || polygon.length < 3) return polygon; var bp = bounds(polygon); holes = holes.filter(function (hole) { var b = bounds(hole), out = b.xMin > bp.xMax || b.yMin > bp.yMax || b.xMax < bp.xMin || b.yMax < bp.yMin; return !out; }); var options = { polygons: [polygon], holes: holes }; return tess.run(options).reduce(function (p, v) { return p.concat(v); }, []); } /** * Subtract polygons * * @param {Array} polygons * * @return {Array} */ function subtract() { for (var _len = arguments.length, polygons = Array(_len), _key = 0; _key < _len; _key++) { polygons[_key] = arguments[_key]; } var options = { polygons: [ensure_ccw(polygons[0])], holes: polygons.slice(1).map(function (p) { return ensure_cw(p); }), boundaryOnly: true, autoWinding: false }; return tess.run(options); } /** * Union of a set of polygons * * @param {Array} polygons * * @return {Array} */ function union() { for (var _len2 = arguments.length, polygons = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { polygons[_key2] = arguments[_key2]; } var options = { polygons: polygons.map(function (p) { return ensure_ccw(p); }), boundaryOnly: true, autoWinding: false }; return tess.run(options); } /** * Intersection of a set of polygons * * @param {Array} a First polygon * @param {Array} b Second polygon * * @return {Array} */ function intersection(a, b) { var options = { polygons: [ensure_ccw(a), ensure_ccw(b)], boundaryOnly: true, windingRule: tess.GLU_TESS_WINDING_ABS_GEQ_TWO, autoWinding: false }; return tess.run(options); }