polygon-tools
Version:
365 lines (315 loc) • 10.3 kB
JavaScript
;
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);
}