three-conic-polygon-geometry
Version:
ThreeJS geometry for drawing polygons on a sphere
1,531 lines (1,313 loc) • 211 kB
JavaScript
// Version 2.1.2 three-conic-polygon-geometry - https://github.com/vasturiano/three-conic-polygon-geometry
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('three')) :
typeof define === 'function' && define.amd ? define(['three'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ConicPolygonGeometry = factory(global.THREE));
})(this, (function (three) { 'use strict';
function _arrayLikeToArray(r, a) {
(null == a || a > r.length) && (a = r.length);
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
return n;
}
function _arrayWithHoles(r) {
if (Array.isArray(r)) return r;
}
function _arrayWithoutHoles(r) {
if (Array.isArray(r)) return _arrayLikeToArray(r);
}
function _assertThisInitialized(e) {
if (undefined === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
return e;
}
function _callSuper(t, o, e) {
return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, [], _getPrototypeOf(t).constructor) : o.apply(t, e));
}
function _classCallCheck(a, n) {
if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function");
}
function _createClass(e, r, t) {
return Object.defineProperty(e, "prototype", {
writable: false
}), e;
}
function _getPrototypeOf(t) {
return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) {
return t.__proto__ || Object.getPrototypeOf(t);
}, _getPrototypeOf(t);
}
function _inherits(t, e) {
if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function");
t.prototype = Object.create(e && e.prototype, {
constructor: {
value: t,
writable: true,
configurable: true
}
}), Object.defineProperty(t, "prototype", {
writable: false
}), e && _setPrototypeOf(t, e);
}
function _isNativeReflectConstruct() {
try {
var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
} catch (t) {}
return (_isNativeReflectConstruct = function () {
return !!t;
})();
}
function _iterableToArray(r) {
if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r);
}
function _iterableToArrayLimit(r, l) {
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
if (null != t) {
var e,
n,
i,
u,
a = [],
f = true,
o = false;
try {
if (i = (t = t.call(r)).next, 0 === l) ; else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
} catch (r) {
o = true, n = r;
} finally {
try {
if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
} finally {
if (o) throw n;
}
}
return a;
}
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _possibleConstructorReturn(t, e) {
if (e && ("object" == typeof e || "function" == typeof e)) return e;
if (undefined !== e) throw new TypeError("Derived constructors may only return object or undefined");
return _assertThisInitialized(t);
}
function _setPrototypeOf(t, e) {
return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) {
return t.__proto__ = e, t;
}, _setPrototypeOf(t, e);
}
function _slicedToArray(r, e) {
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
}
function _toConsumableArray(r) {
return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread();
}
function _unsupportedIterableToArray(r, a) {
if (r) {
if ("string" == typeof r) return _arrayLikeToArray(r, a);
var t = {}.toString.call(r).slice(8, -1);
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : undefined;
}
}
function ascending(a, b) {
return a == null || b == null ? NaN : a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
}
function descending(a, b) {
return a == null || b == null ? NaN
: b < a ? -1
: b > a ? 1
: b >= a ? 0
: NaN;
}
function bisector(f) {
let compare1, compare2, delta;
// If an accessor is specified, promote it to a comparator. In this case we
// can test whether the search value is (self-) comparable. We can’t do this
// for a comparator (except for specific, known comparators) because we can’t
// tell if the comparator is symmetric, and an asymmetric comparator can’t be
// used to test whether a single value is comparable.
if (f.length !== 2) {
compare1 = ascending;
compare2 = (d, x) => ascending(f(d), x);
delta = (d, x) => f(d) - x;
} else {
compare1 = f === ascending || f === descending ? f : zero$1;
compare2 = f;
delta = f;
}
function left(a, x, lo = 0, hi = a.length) {
if (lo < hi) {
if (compare1(x, x) !== 0) return hi;
do {
const mid = (lo + hi) >>> 1;
if (compare2(a[mid], x) < 0) lo = mid + 1;
else hi = mid;
} while (lo < hi);
}
return lo;
}
function right(a, x, lo = 0, hi = a.length) {
if (lo < hi) {
if (compare1(x, x) !== 0) return hi;
do {
const mid = (lo + hi) >>> 1;
if (compare2(a[mid], x) <= 0) lo = mid + 1;
else hi = mid;
} while (lo < hi);
}
return lo;
}
function center(a, x, lo = 0, hi = a.length) {
const i = left(a, x, lo, hi - 1);
return i > lo && delta(a[i - 1], x) > -delta(a[i], x) ? i - 1 : i;
}
return {left, center, right};
}
function zero$1() {
return 0;
}
function number$1(x) {
return x === null ? NaN : +x;
}
const ascendingBisect = bisector(ascending);
const bisectRight = ascendingBisect.right;
bisector(number$1).center;
function extent(values, valueof) {
let min;
let max;
if (valueof === undefined) {
for (const value of values) {
if (value != null) {
if (min === undefined) {
if (value >= value) min = max = value;
} else {
if (min > value) min = value;
if (max < value) max = value;
}
}
}
} else {
let index = -1;
for (let value of values) {
if ((value = valueof(value, ++index, values)) != null) {
if (min === undefined) {
if (value >= value) min = max = value;
} else {
if (min > value) min = value;
if (max < value) max = value;
}
}
}
}
return [min, max];
}
// https://github.com/python/cpython/blob/a74eea238f5baba15797e2e8b570d153bc8690a7/Modules/mathmodule.c#L1423
class Adder {
constructor() {
this._partials = new Float64Array(32);
this._n = 0;
}
add(x) {
const p = this._partials;
let i = 0;
for (let j = 0; j < this._n && j < 32; j++) {
const y = p[j],
hi = x + y,
lo = Math.abs(x) < Math.abs(y) ? x - (hi - y) : y - (hi - x);
if (lo) p[i++] = lo;
x = hi;
}
p[i] = x;
this._n = i + 1;
return this;
}
valueOf() {
const p = this._partials;
let n = this._n, x, y, lo, hi = 0;
if (n > 0) {
hi = p[--n];
while (n > 0) {
x = hi;
y = p[--n];
hi = x + y;
lo = y - (hi - x);
if (lo) break;
}
if (n > 0 && ((lo < 0 && p[n - 1] < 0) || (lo > 0 && p[n - 1] > 0))) {
y = lo * 2;
x = hi + y;
if (y == x - hi) hi = x;
}
}
return hi;
}
}
const e10 = Math.sqrt(50),
e5 = Math.sqrt(10),
e2 = Math.sqrt(2);
function tickSpec(start, stop, count) {
const step = (stop - start) / Math.max(0, count),
power = Math.floor(Math.log10(step)),
error = step / Math.pow(10, power),
factor = error >= e10 ? 10 : error >= e5 ? 5 : error >= e2 ? 2 : 1;
let i1, i2, inc;
if (power < 0) {
inc = Math.pow(10, -power) / factor;
i1 = Math.round(start * inc);
i2 = Math.round(stop * inc);
if (i1 / inc < start) ++i1;
if (i2 / inc > stop) --i2;
inc = -inc;
} else {
inc = Math.pow(10, power) * factor;
i1 = Math.round(start / inc);
i2 = Math.round(stop / inc);
if (i1 * inc < start) ++i1;
if (i2 * inc > stop) --i2;
}
if (i2 < i1 && 0.5 <= count && count < 2) return tickSpec(start, stop, count * 2);
return [i1, i2, inc];
}
function ticks(start, stop, count) {
stop = +stop, start = +start, count = +count;
if (!(count > 0)) return [];
if (start === stop) return [start];
const reverse = stop < start, [i1, i2, inc] = reverse ? tickSpec(stop, start, count) : tickSpec(start, stop, count);
if (!(i2 >= i1)) return [];
const n = i2 - i1 + 1, ticks = new Array(n);
if (reverse) {
if (inc < 0) for (let i = 0; i < n; ++i) ticks[i] = (i2 - i) / -inc;
else for (let i = 0; i < n; ++i) ticks[i] = (i2 - i) * inc;
} else {
if (inc < 0) for (let i = 0; i < n; ++i) ticks[i] = (i1 + i) / -inc;
else for (let i = 0; i < n; ++i) ticks[i] = (i1 + i) * inc;
}
return ticks;
}
function tickIncrement(start, stop, count) {
stop = +stop, start = +start, count = +count;
return tickSpec(start, stop, count)[2];
}
function tickStep(start, stop, count) {
stop = +stop, start = +start, count = +count;
const reverse = stop < start, inc = reverse ? tickIncrement(stop, start, count) : tickIncrement(start, stop, count);
return (reverse ? -1 : 1) * (inc < 0 ? 1 / -inc : inc);
}
function mean(values, valueof) {
let count = 0;
let sum = 0;
if (valueof === undefined) {
for (let value of values) {
if (value != null && (value = +value) >= value) {
++count, sum += value;
}
}
} else {
let index = -1;
for (let value of values) {
if ((value = valueof(value, ++index, values)) != null && (value = +value) >= value) {
++count, sum += value;
}
}
}
if (count) return sum / count;
}
function* flatten$1(arrays) {
for (const array of arrays) {
yield* array;
}
}
function merge(arrays) {
return Array.from(flatten$1(arrays));
}
function earcut(data, holeIndices, dim = 2) {
const hasHoles = holeIndices && holeIndices.length;
const outerLen = hasHoles ? holeIndices[0] * dim : data.length;
let outerNode = linkedList(data, 0, outerLen, dim, true);
const triangles = [];
if (!outerNode || outerNode.next === outerNode.prev) return triangles;
let minX, minY, invSize;
if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim);
// if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
if (data.length > 80 * dim) {
minX = Infinity;
minY = Infinity;
let maxX = -Infinity;
let maxY = -Infinity;
for (let i = dim; i < outerLen; i += dim) {
const x = data[i];
const y = data[i + 1];
if (x < minX) minX = x;
if (y < minY) minY = y;
if (x > maxX) maxX = x;
if (y > maxY) maxY = y;
}
// minX, minY and invSize are later used to transform coords into integers for z-order calculation
invSize = Math.max(maxX - minX, maxY - minY);
invSize = invSize !== 0 ? 32767 / invSize : 0;
}
earcutLinked(outerNode, triangles, dim, minX, minY, invSize, 0);
return triangles;
}
// create a circular doubly linked list from polygon points in the specified winding order
function linkedList(data, start, end, dim, clockwise) {
let last;
if (clockwise === (signedArea(data, start, end, dim) > 0)) {
for (let i = start; i < end; i += dim) last = insertNode(i / dim | 0, data[i], data[i + 1], last);
} else {
for (let i = end - dim; i >= start; i -= dim) last = insertNode(i / dim | 0, data[i], data[i + 1], last);
}
if (last && equals(last, last.next)) {
removeNode(last);
last = last.next;
}
return last;
}
// eliminate colinear or duplicate points
function filterPoints(start, end) {
if (!start) return start;
if (!end) end = start;
let p = start,
again;
do {
again = false;
if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) {
removeNode(p);
p = end = p.prev;
if (p === p.next) break;
again = true;
} else {
p = p.next;
}
} while (again || p !== end);
return end;
}
// main ear slicing loop which triangulates a polygon (given as a linked list)
function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) {
if (!ear) return;
// interlink polygon nodes in z-order
if (!pass && invSize) indexCurve(ear, minX, minY, invSize);
let stop = ear;
// iterate through ears, slicing them one by one
while (ear.prev !== ear.next) {
const prev = ear.prev;
const next = ear.next;
if (invSize ? isEarHashed(ear, minX, minY, invSize) : isEar(ear)) {
triangles.push(prev.i, ear.i, next.i); // cut off the triangle
removeNode(ear);
// skipping the next vertex leads to less sliver triangles
ear = next.next;
stop = next.next;
continue;
}
ear = next;
// if we looped through the whole remaining polygon and can't find any more ears
if (ear === stop) {
// try filtering points and slicing again
if (!pass) {
earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1);
// if this didn't work, try curing all small self-intersections locally
} else if (pass === 1) {
ear = cureLocalIntersections(filterPoints(ear), triangles);
earcutLinked(ear, triangles, dim, minX, minY, invSize, 2);
// as a last resort, try splitting the remaining polygon into two
} else if (pass === 2) {
splitEarcut(ear, triangles, dim, minX, minY, invSize);
}
break;
}
}
}
// check whether a polygon node forms a valid ear with adjacent nodes
function isEar(ear) {
const a = ear.prev,
b = ear,
c = ear.next;
if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
// now make sure we don't have other points inside the potential ear
const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y;
// triangle bbox
const x0 = Math.min(ax, bx, cx),
y0 = Math.min(ay, by, cy),
x1 = Math.max(ax, bx, cx),
y1 = Math.max(ay, by, cy);
let p = c.next;
while (p !== a) {
if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 &&
pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) &&
area(p.prev, p, p.next) >= 0) return false;
p = p.next;
}
return true;
}
function isEarHashed(ear, minX, minY, invSize) {
const a = ear.prev,
b = ear,
c = ear.next;
if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y;
// triangle bbox
const x0 = Math.min(ax, bx, cx),
y0 = Math.min(ay, by, cy),
x1 = Math.max(ax, bx, cx),
y1 = Math.max(ay, by, cy);
// z-order range for the current triangle bbox;
const minZ = zOrder(x0, y0, minX, minY, invSize),
maxZ = zOrder(x1, y1, minX, minY, invSize);
let p = ear.prevZ,
n = ear.nextZ;
// look for points inside the triangle in both directions
while (p && p.z >= minZ && n && n.z <= maxZ) {
if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c &&
pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false;
p = p.prevZ;
if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c &&
pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false;
n = n.nextZ;
}
// look for remaining points in decreasing z-order
while (p && p.z >= minZ) {
if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c &&
pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false;
p = p.prevZ;
}
// look for remaining points in increasing z-order
while (n && n.z <= maxZ) {
if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c &&
pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false;
n = n.nextZ;
}
return true;
}
// go through all polygon nodes and cure small local self-intersections
function cureLocalIntersections(start, triangles) {
let p = start;
do {
const a = p.prev,
b = p.next.next;
if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) {
triangles.push(a.i, p.i, b.i);
// remove two nodes involved
removeNode(p);
removeNode(p.next);
p = start = b;
}
p = p.next;
} while (p !== start);
return filterPoints(p);
}
// try splitting polygon into two and triangulate them independently
function splitEarcut(start, triangles, dim, minX, minY, invSize) {
// look for a valid diagonal that divides the polygon into two
let a = start;
do {
let b = a.next.next;
while (b !== a.prev) {
if (a.i !== b.i && isValidDiagonal(a, b)) {
// split the polygon in two by the diagonal
let c = splitPolygon(a, b);
// filter colinear points around the cuts
a = filterPoints(a, a.next);
c = filterPoints(c, c.next);
// run earcut on each half
earcutLinked(a, triangles, dim, minX, minY, invSize, 0);
earcutLinked(c, triangles, dim, minX, minY, invSize, 0);
return;
}
b = b.next;
}
a = a.next;
} while (a !== start);
}
// link every hole into the outer loop, producing a single-ring polygon without holes
function eliminateHoles(data, holeIndices, outerNode, dim) {
const queue = [];
for (let i = 0, len = holeIndices.length; i < len; i++) {
const start = holeIndices[i] * dim;
const end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
const list = linkedList(data, start, end, dim, false);
if (list === list.next) list.steiner = true;
queue.push(getLeftmost(list));
}
queue.sort(compareXYSlope);
// process holes from left to right
for (let i = 0; i < queue.length; i++) {
outerNode = eliminateHole(queue[i], outerNode);
}
return outerNode;
}
function compareXYSlope(a, b) {
let result = a.x - b.x;
// when the left-most point of 2 holes meet at a vertex, sort the holes counterclockwise so that when we find
// the bridge to the outer shell is always the point that they meet at.
if (result === 0) {
result = a.y - b.y;
if (result === 0) {
const aSlope = (a.next.y - a.y) / (a.next.x - a.x);
const bSlope = (b.next.y - b.y) / (b.next.x - b.x);
result = aSlope - bSlope;
}
}
return result;
}
// find a bridge between vertices that connects hole with an outer ring and and link it
function eliminateHole(hole, outerNode) {
const bridge = findHoleBridge(hole, outerNode);
if (!bridge) {
return outerNode;
}
const bridgeReverse = splitPolygon(bridge, hole);
// filter collinear points around the cuts
filterPoints(bridgeReverse, bridgeReverse.next);
return filterPoints(bridge, bridge.next);
}
// David Eberly's algorithm for finding a bridge between hole and outer polygon
function findHoleBridge(hole, outerNode) {
let p = outerNode;
const hx = hole.x;
const hy = hole.y;
let qx = -Infinity;
let m;
// find a segment intersected by a ray from the hole's leftmost point to the left;
// segment's endpoint with lesser x will be potential connection point
// unless they intersect at a vertex, then choose the vertex
if (equals(hole, p)) return p;
do {
if (equals(hole, p.next)) return p.next;
else if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) {
const x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y);
if (x <= hx && x > qx) {
qx = x;
m = p.x < p.next.x ? p : p.next;
if (x === hx) return m; // hole touches outer segment; pick leftmost endpoint
}
}
p = p.next;
} while (p !== outerNode);
if (!m) return null;
// look for points inside the triangle of hole point, segment intersection and endpoint;
// if there are no points found, we have a valid connection;
// otherwise choose the point of the minimum angle with the ray as connection point
const stop = m;
const mx = m.x;
const my = m.y;
let tanMin = Infinity;
p = m;
do {
if (hx >= p.x && p.x >= mx && hx !== p.x &&
pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) {
const tan = Math.abs(hy - p.y) / (hx - p.x); // tangential
if (locallyInside(p, hole) &&
(tan < tanMin || (tan === tanMin && (p.x > m.x || (p.x === m.x && sectorContainsSector(m, p)))))) {
m = p;
tanMin = tan;
}
}
p = p.next;
} while (p !== stop);
return m;
}
// whether sector in vertex m contains sector in vertex p in the same coordinates
function sectorContainsSector(m, p) {
return area(m.prev, m, p.prev) < 0 && area(p.next, m, m.next) < 0;
}
// interlink polygon nodes in z-order
function indexCurve(start, minX, minY, invSize) {
let p = start;
do {
if (p.z === 0) p.z = zOrder(p.x, p.y, minX, minY, invSize);
p.prevZ = p.prev;
p.nextZ = p.next;
p = p.next;
} while (p !== start);
p.prevZ.nextZ = null;
p.prevZ = null;
sortLinked(p);
}
// Simon Tatham's linked list merge sort algorithm
// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
function sortLinked(list) {
let numMerges;
let inSize = 1;
do {
let p = list;
let e;
list = null;
let tail = null;
numMerges = 0;
while (p) {
numMerges++;
let q = p;
let pSize = 0;
for (let i = 0; i < inSize; i++) {
pSize++;
q = q.nextZ;
if (!q) break;
}
let qSize = inSize;
while (pSize > 0 || (qSize > 0 && q)) {
if (pSize !== 0 && (qSize === 0 || !q || p.z <= q.z)) {
e = p;
p = p.nextZ;
pSize--;
} else {
e = q;
q = q.nextZ;
qSize--;
}
if (tail) tail.nextZ = e;
else list = e;
e.prevZ = tail;
tail = e;
}
p = q;
}
tail.nextZ = null;
inSize *= 2;
} while (numMerges > 1);
return list;
}
// z-order of a point given coords and inverse of the longer side of data bbox
function zOrder(x, y, minX, minY, invSize) {
// coords are transformed into non-negative 15-bit integer range
x = (x - minX) * invSize | 0;
y = (y - minY) * invSize | 0;
x = (x | (x << 8)) & 0x00FF00FF;
x = (x | (x << 4)) & 0x0F0F0F0F;
x = (x | (x << 2)) & 0x33333333;
x = (x | (x << 1)) & 0x55555555;
y = (y | (y << 8)) & 0x00FF00FF;
y = (y | (y << 4)) & 0x0F0F0F0F;
y = (y | (y << 2)) & 0x33333333;
y = (y | (y << 1)) & 0x55555555;
return x | (y << 1);
}
// find the leftmost node of a polygon ring
function getLeftmost(start) {
let p = start,
leftmost = start;
do {
if (p.x < leftmost.x || (p.x === leftmost.x && p.y < leftmost.y)) leftmost = p;
p = p.next;
} while (p !== start);
return leftmost;
}
// check if a point lies within a convex triangle
function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) {
return (cx - px) * (ay - py) >= (ax - px) * (cy - py) &&
(ax - px) * (by - py) >= (bx - px) * (ay - py) &&
(bx - px) * (cy - py) >= (cx - px) * (by - py);
}
// check if a point lies within a convex triangle but false if its equal to the first point of the triangle
function pointInTriangleExceptFirst(ax, ay, bx, by, cx, cy, px, py) {
return !(ax === px && ay === py) && pointInTriangle(ax, ay, bx, by, cx, cy, px, py);
}
// check if a diagonal between two polygon nodes is valid (lies in polygon interior)
function isValidDiagonal(a, b) {
return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && // dones't intersect other edges
(locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible
(area(a.prev, a, b.prev) || area(a, b.prev, b)) || // does not create opposite-facing sectors
equals(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0); // special zero-length case
}
// signed area of a triangle
function area(p, q, r) {
return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
}
// check if two points are equal
function equals(p1, p2) {
return p1.x === p2.x && p1.y === p2.y;
}
// check if two segments intersect
function intersects(p1, q1, p2, q2) {
const o1 = sign$2(area(p1, q1, p2));
const o2 = sign$2(area(p1, q1, q2));
const o3 = sign$2(area(p2, q2, p1));
const o4 = sign$2(area(p2, q2, q1));
if (o1 !== o2 && o3 !== o4) return true; // general case
if (o1 === 0 && onSegment(p1, p2, q1)) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1
if (o2 === 0 && onSegment(p1, q2, q1)) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1
if (o3 === 0 && onSegment(p2, p1, q2)) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2
if (o4 === 0 && onSegment(p2, q1, q2)) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2
return false;
}
// for collinear points p, q, r, check if point q lies on segment pr
function onSegment(p, q, r) {
return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y);
}
function sign$2(num) {
return num > 0 ? 1 : num < 0 ? -1 : 0;
}
// check if a polygon diagonal intersects any polygon segments
function intersectsPolygon(a, b) {
let p = a;
do {
if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i &&
intersects(p, p.next, a, b)) return true;
p = p.next;
} while (p !== a);
return false;
}
// check if a polygon diagonal is locally inside the polygon
function locallyInside(a, b) {
return area(a.prev, a, a.next) < 0 ?
area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0 :
area(a, b, a.prev) < 0 || area(a, a.next, b) < 0;
}
// check if the middle point of a polygon diagonal is inside the polygon
function middleInside(a, b) {
let p = a;
let inside = false;
const px = (a.x + b.x) / 2;
const py = (a.y + b.y) / 2;
do {
if (((p.y > py) !== (p.next.y > py)) && p.next.y !== p.y &&
(px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x))
inside = !inside;
p = p.next;
} while (p !== a);
return inside;
}
// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;
// if one belongs to the outer ring and another to a hole, it merges it into a single ring
function splitPolygon(a, b) {
const a2 = createNode(a.i, a.x, a.y),
b2 = createNode(b.i, b.x, b.y),
an = a.next,
bp = b.prev;
a.next = b;
b.prev = a;
a2.next = an;
an.prev = a2;
b2.next = a2;
a2.prev = b2;
bp.next = b2;
b2.prev = bp;
return b2;
}
// create a node and optionally link it with previous one (in a circular doubly linked list)
function insertNode(i, x, y, last) {
const p = createNode(i, x, y);
if (!last) {
p.prev = p;
p.next = p;
} else {
p.next = last.next;
p.prev = last;
last.next.prev = p;
last.next = p;
}
return p;
}
function removeNode(p) {
p.next.prev = p.prev;
p.prev.next = p.next;
if (p.prevZ) p.prevZ.nextZ = p.nextZ;
if (p.nextZ) p.nextZ.prevZ = p.prevZ;
}
function createNode(i, x, y) {
return {
i, // vertex index in coordinates array
x, y, // vertex coordinates
prev: null, // previous and next vertex nodes in a polygon ring
next: null,
z: 0, // z-order curve value
prevZ: null, // previous and next nodes in z-order
nextZ: null,
steiner: false // indicates whether this is a steiner point
};
}
function signedArea(data, start, end, dim) {
let sum = 0;
for (let i = start, j = end - dim; i < end; i += dim) {
sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]);
j = i;
}
return sum;
}
// turn a polygon in a multi-dimensional array form (e.g. as in GeoJSON) into a form Earcut accepts
function flatten(data) {
const vertices = [];
const holes = [];
const dimensions = data[0][0].length;
let holeIndex = 0;
let prevLen = 0;
for (const ring of data) {
for (const p of ring) {
for (let d = 0; d < dimensions; d++) vertices.push(p[d]);
}
if (prevLen) {
holeIndex += prevLen;
holes.push(holeIndex);
}
prevLen = ring.length;
}
return {vertices, holes, dimensions};
}
const epsilon$2 = 1.1102230246251565e-16;
const splitter = 134217729;
const resulterrbound = (3 + 8 * epsilon$2) * epsilon$2;
// fast_expansion_sum_zeroelim routine from oritinal code
function sum(elen, e, flen, f, h) {
let Q, Qnew, hh, bvirt;
let enow = e[0];
let fnow = f[0];
let eindex = 0;
let findex = 0;
if ((fnow > enow) === (fnow > -enow)) {
Q = enow;
enow = e[++eindex];
} else {
Q = fnow;
fnow = f[++findex];
}
let 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) {
let Q = e[0];
for (let i = 1; i < elen; i++) Q += e[i];
return Q;
}
function vec(n) {
return new Float64Array(n);
}
const ccwerrboundA = (3 + 16 * epsilon$2) * epsilon$2;
const ccwerrboundB = (2 + 12 * epsilon$2) * epsilon$2;
const ccwerrboundC = (9 + 64 * epsilon$2) * epsilon$2 * epsilon$2;
const B = vec(4);
const C1 = vec(8);
const C2 = vec(12);
const D = vec(16);
const u = vec(4);
function orient2dadapt(ax, ay, bx, by, cx, cy, detsum) {
let acxtail, acytail, bcxtail, bcytail;
let bvirt, c, ahi, alo, bhi, blo, _i, _j, _0, s1, s0, t1, t0, u3;
const acx = ax - cx;
const bcx = bx - cx;
const acy = ay - cy;
const 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;
let det = estimate(4, B);
let 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;
const 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;
const 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;
const Dlen = sum(C2len, C2, 4, u, D);
return D[Dlen - 1];
}
function orient2d(ax, ay, bx, by, cx, cy) {
const detleft = (ay - cy) * (bx - cx);
const detright = (ax - cx) * (by - cy);
const det = detleft - detright;
const detsum = Math.abs(detleft + detright);
if (Math.abs(det) >= ccwerrboundA * detsum) return det;
return -orient2dadapt(ax, ay, bx, by, cx, cy, detsum);
}
const EPSILON = Math.pow(2, -52);
const EDGE_STACK = new Uint32Array(512);
class Delaunator {
static from(points, getX = defaultGetX, getY = defaultGetY) {
const n = points.length;
const coords = new Float64Array(n * 2);
for (let i = 0; i < n; i++) {
const p = points[i];
coords[2 * i] = getX(p);
coords[2 * i + 1] = getY(p);
}
return new Delaunator(coords);
}
constructor(coords) {
const n = coords.length >> 1;
if (n > 0 && typeof coords[0] !== 'number') throw new Error('Expected coords to contain numbers.');
this.coords = coords;
// arrays that will store the triangulation graph
const maxTriangles = Math.max(2 * n - 5, 0);
this._triangles = new Uint32Array(maxTriangles * 3);
this._halfedges = new Int32Array(maxTriangles * 3);
// temporary arrays for tracking the edges of the advancing convex hull
this._hashSize = Math.ceil(Math.sqrt(n));
this._hullPrev = new Uint32Array(n); // edge to prev edge
this._hullNext = new Uint32Array(n); // edge to next edge
this._hullTri = new Uint32Array(n); // edge to adjacent triangle
this._hullHash = new Int32Array(this._hashSize); // angular edge hash
// temporary arrays for sorting points
this._ids = new Uint32Array(n);
this._dists = new Float64Array(n);
this.update();
}
update() {
const {coords, _hullPrev: hullPrev, _hullNext: hullNext, _hullTri: hullTri, _hullHash: hullHash} = this;
const n = coords.length >> 1;
// populate an array of point indices; calculate input data bbox
let minX = Infinity;
let minY = Infinity;
let maxX = -Infinity;
let maxY = -Infinity;
for (let i = 0; i < n; i++) {
const x = coords[2 * i];
const y = coords[2 * i + 1];
if (x < minX) minX = x;
if (y < minY) minY = y;
if (x > maxX) maxX = x;
if (y > maxY) maxY = y;
this._ids[i] = i;
}
const cx = (minX + maxX) / 2;
const cy = (minY + maxY) / 2;
let i0, i1, i2;
// pick a seed point close to the center
for (let i = 0, minDist = Infinity; i < n; i++) {
const d = dist(cx, cy, coords[2 * i], coords[2 * i + 1]);
if (d < minDist) {
i0 = i;
minDist = d;
}
}
const i0x = coords[2 * i0];
const i0y = coords[2 * i0 + 1];
// find the point closest to the seed
for (let i = 0, minDist = Infinity; i < n; i++) {
if (i === i0) continue;
const d = dist(i0x, i0y, coords[2 * i], coords[2 * i + 1]);
if (d < minDist && d > 0) {
i1 = i;
minDist = d;
}
}
let i1x = coords[2 * i1];
let i1y = coords[2 * i1 + 1];
let minRadius = Infinity;
// find the third point which forms the smallest circumcircle with the first two
for (let i = 0; i < n; i++) {
if (i === i0 || i === i1) continue;
const r = circumradius(i0x, i0y, i1x, i1y, coords[2 * i], coords[2 * i + 1]);
if (r < minRadius) {
i2 = i;
minRadius = r;
}
}
let i2x = coords[2 * i2];
let i2y = coords[2 * i2 + 1];
if (minRadius === Infinity) {
// order collinear points by dx (or dy if all x are identical)
// and return the list as a hull
for (let i = 0; i < n; i++) {
this._dists[i] = (coords[2 * i] - coords[0]) || (coords[2 * i + 1] - coords[1]);
}
quicksort(this._ids, this._dists, 0, n - 1);
const hull = new Uint32Array(n);
let j = 0;
for (let i = 0, d0 = -Infinity; i < n; i++) {
const id = this._ids[i];
const d = this._dists[id];
if (d > d0) {
hull[j++] = id;
d0 = d;
}
}
this.hull = hull.subarray(0, j);
this.triangles = new Uint32Array(0);
this.halfedges = new Uint32Array(0);
return;
}
// swap the order of the seed points for counter-clockwise orientation
if (orient2d(i0x, i0y, i1x, i1y, i2x, i2y) < 0) {
const i = i1;
const x = i1x;
const y = i1y;
i1 = i2;
i1x = i2x;
i1y = i2y;
i2 = i;
i2x = x;
i2y = y;
}
const center = circumcenter(i0x, i0y, i1x, i1y, i2x, i2y);
this._cx = center.x;
this._cy = center.y;
for (let i = 0; i < n; i++) {
this._dists[i] = dist(coords[2 * i], coords[2 * i + 1], center.x, center.y);
}
// sort the points by distance from the seed triangle circumcenter
quicksort(this._ids, this._dists, 0, n - 1);
// set up the seed triangle as the starting hull
this._hullStart = i0;
let hullSize = 3;
hullNext[i0] = hullPrev[i2] = i1;
hullNext[i1] = hullPrev[i0] = i2;
hullNext[i2] = hullPrev[i1] = i0;
hullTri[i0] = 0;
hullTri[i1] = 1;
hullTri[i2] = 2;
hullHash.fill(-1);
hullHash[this._hashKey(i0x, i0y)] = i0;
hullHash[this._hashKey(i1x, i1y)] = i1;
hullHash[this._hashKey(i2x, i2y)] = i2;
this.trianglesLen = 0;
this._addTriangle(i0, i1, i2, -1, -1, -1);
for (let k = 0, xp, yp; k < this._ids.length; k++) {
const i = this._ids[k];
const x = coords[2 * i];
const y = coords[2 * i + 1];
// skip near-duplicate points
if (k > 0 && Math.abs(x - xp) <= EPSILON && Math.abs(y - yp) <= EPSILON) continue;
xp = x;
yp = y;
// skip seed triangle points
if (i === i0 || i === i1 || i === i2) continue;
// find a visible edge on the convex hull using edge hash
let start = 0;
for (let j = 0, key = this._hashKey(x, y); j < this._hashSize; j++) {
start = hullHash[(key + j) % this._hashSize];
if (start !== -1 && start !== hullNext[start]) break;
}
start = hullPrev[start];
let e = start, q;
while (q = hullNext[e], orient2d(x, y, coords[2 * e], coords[2 * e + 1], coords[2 * q], coords[2 * q + 1]) >= 0) {
e = q;
if (e === start) {
e = -1;
break;
}
}
if (e === -1) continue; // likely a near-duplicate point; skip it
// add the first triangle from the point
let t = this._addTriangle(e, i, hullNext[e], -1, -1, hullTri[e]);
// recursively flip triangles from the point until they satisfy the Delaunay condition
hullTri[i] = this._legalize(t + 2);
hullTri[e] = t; // keep track of boundary triangles on the hull
hullSize++;
// walk forward through the hull, adding more triangles and flipping recursively
let n = hullNext[e];
while (q = hullNext[n], orient2d(x, y, coords[2 * n], coords[2 * n + 1], coords[2 * q], coords[2 * q + 1]) < 0) {
t = this._addTriangle(n, i, q, hullTri[i], -1, hullTri[n]);
hullTri[i] = this._legalize(t + 2);
hullNext[n] = n; // mark as removed
hullSize--;
n = q;
}
// walk backward from the other side, adding more triangles and flipping
if (e === start) {
while (q = hullPrev[e], orient2d(x, y, coords[2 * q], coords[2 * q + 1], coords[2 * e], coords[2 * e + 1]) < 0) {
t = this._addTriangle(q, i, e, -1, hullTri[e], hullTri[q]);
this._legalize(t + 2);
hullTri[q] = t;
hullNext[e] = e; // mark as removed
hullSize--;
e = q;
}
}
// update the hull indices
this._hullStart = hullPrev[i] = e;
hullNext[e] = hullPrev[n] = i;
hullNext[i] = n;
// save the two new edges in the hash table
hullHash[this._hashKey(x, y)] = i;
hullHash[this._hashKey(coords[2 * e], coords[2 * e + 1])] = e;
}
this.hull = new Uint32Array(hullSize);
for (let i = 0, e = this._hullStart; i < hullSize; i++) {
this.hull[i] = e;
e = hullNext[e];
}
// trim typed triangle mesh arrays
this.triangles = this._triangles.subarray(0, this.trianglesLen);
this.halfedges = this._halfedges.subarray(0, this.trianglesLen);
}
_hashKey(x, y) {
return Math.floor(pseudoAngle(x - this._cx, y - this._cy) * this._hashSize) % this._hashSize;
}
_legalize(a) {
const {_triangles: triangles, _halfedges: halfedges, coords} = this;
let i = 0;
let ar = 0;
// recursion eliminated with a fixed-size stack
w