|
|
// https://d3js.org/d3-voronoi/ Version 1.1.2. Copyright 2017 Mike Bostock.
|
|
|
(function (global, factory) {
|
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
|
|
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
|
|
(factory((global.d3 = global.d3 || {})));
|
|
|
}(this, (function (exports) { 'use strict';
|
|
|
|
|
|
var constant = function(x) {
|
|
|
return function() {
|
|
|
return x;
|
|
|
};
|
|
|
};
|
|
|
|
|
|
function x(d) {
|
|
|
return d[0];
|
|
|
}
|
|
|
|
|
|
function y(d) {
|
|
|
return d[1];
|
|
|
}
|
|
|
|
|
|
function RedBlackTree() {
|
|
|
this._ = null; // root node
|
|
|
}
|
|
|
|
|
|
function RedBlackNode(node) {
|
|
|
node.U = // parent node
|
|
|
node.C = // color - true for red, false for black
|
|
|
node.L = // left node
|
|
|
node.R = // right node
|
|
|
node.P = // previous node
|
|
|
node.N = null; // next node
|
|
|
}
|
|
|
|
|
|
RedBlackTree.prototype = {
|
|
|
constructor: RedBlackTree,
|
|
|
|
|
|
insert: function(after, node) {
|
|
|
var parent, grandpa, uncle;
|
|
|
|
|
|
if (after) {
|
|
|
node.P = after;
|
|
|
node.N = after.N;
|
|
|
if (after.N) after.N.P = node;
|
|
|
after.N = node;
|
|
|
if (after.R) {
|
|
|
after = after.R;
|
|
|
while (after.L) after = after.L;
|
|
|
after.L = node;
|
|
|
} else {
|
|
|
after.R = node;
|
|
|
}
|
|
|
parent = after;
|
|
|
} else if (this._) {
|
|
|
after = RedBlackFirst(this._);
|
|
|
node.P = null;
|
|
|
node.N = after;
|
|
|
after.P = after.L = node;
|
|
|
parent = after;
|
|
|
} else {
|
|
|
node.P = node.N = null;
|
|
|
this._ = node;
|
|
|
parent = null;
|
|
|
}
|
|
|
node.L = node.R = null;
|
|
|
node.U = parent;
|
|
|
node.C = true;
|
|
|
|
|
|
after = node;
|
|
|
while (parent && parent.C) {
|
|
|
grandpa = parent.U;
|
|
|
if (parent === grandpa.L) {
|
|
|
uncle = grandpa.R;
|
|
|
if (uncle && uncle.C) {
|
|
|
parent.C = uncle.C = false;
|
|
|
grandpa.C = true;
|
|
|
after = grandpa;
|
|
|
} else {
|
|
|
if (after === parent.R) {
|
|
|
RedBlackRotateLeft(this, parent);
|
|
|
after = parent;
|
|
|
parent = after.U;
|
|
|
}
|
|
|
parent.C = false;
|
|
|
grandpa.C = true;
|
|
|
RedBlackRotateRight(this, grandpa);
|
|
|
}
|
|
|
} else {
|
|
|
uncle = grandpa.L;
|
|
|
if (uncle && uncle.C) {
|
|
|
parent.C = uncle.C = false;
|
|
|
grandpa.C = true;
|
|
|
after = grandpa;
|
|
|
} else {
|
|
|
if (after === parent.L) {
|
|
|
RedBlackRotateRight(this, parent);
|
|
|
after = parent;
|
|
|
parent = after.U;
|
|
|
}
|
|
|
parent.C = false;
|
|
|
grandpa.C = true;
|
|
|
RedBlackRotateLeft(this, grandpa);
|
|
|
}
|
|
|
}
|
|
|
parent = after.U;
|
|
|
}
|
|
|
this._.C = false;
|
|
|
},
|
|
|
|
|
|
remove: function(node) {
|
|
|
if (node.N) node.N.P = node.P;
|
|
|
if (node.P) node.P.N = node.N;
|
|
|
node.N = node.P = null;
|
|
|
|
|
|
var parent = node.U,
|
|
|
sibling,
|
|
|
left = node.L,
|
|
|
right = node.R,
|
|
|
next,
|
|
|
red;
|
|
|
|
|
|
if (!left) next = right;
|
|
|
else if (!right) next = left;
|
|
|
else next = RedBlackFirst(right);
|
|
|
|
|
|
if (parent) {
|
|
|
if (parent.L === node) parent.L = next;
|
|
|
else parent.R = next;
|
|
|
} else {
|
|
|
this._ = next;
|
|
|
}
|
|
|
|
|
|
if (left && right) {
|
|
|
red = next.C;
|
|
|
next.C = node.C;
|
|
|
next.L = left;
|
|
|
left.U = next;
|
|
|
if (next !== right) {
|
|
|
parent = next.U;
|
|
|
next.U = node.U;
|
|
|
node = next.R;
|
|
|
parent.L = node;
|
|
|
next.R = right;
|
|
|
right.U = next;
|
|
|
} else {
|
|
|
next.U = parent;
|
|
|
parent = next;
|
|
|
node = next.R;
|
|
|
}
|
|
|
} else {
|
|
|
red = node.C;
|
|
|
node = next;
|
|
|
}
|
|
|
|
|
|
if (node) node.U = parent;
|
|
|
if (red) return;
|
|
|
if (node && node.C) { node.C = false; return; }
|
|
|
|
|
|
do {
|
|
|
if (node === this._) break;
|
|
|
if (node === parent.L) {
|
|
|
sibling = parent.R;
|
|
|
if (sibling.C) {
|
|
|
sibling.C = false;
|
|
|
parent.C = true;
|
|
|
RedBlackRotateLeft(this, parent);
|
|
|
sibling = parent.R;
|
|
|
}
|
|
|
if ((sibling.L && sibling.L.C)
|
|
|
|| (sibling.R && sibling.R.C)) {
|
|
|
if (!sibling.R || !sibling.R.C) {
|
|
|
sibling.L.C = false;
|
|
|
sibling.C = true;
|
|
|
RedBlackRotateRight(this, sibling);
|
|
|
sibling = parent.R;
|
|
|
}
|
|
|
sibling.C = parent.C;
|
|
|
parent.C = sibling.R.C = false;
|
|
|
RedBlackRotateLeft(this, parent);
|
|
|
node = this._;
|
|
|
break;
|
|
|
}
|
|
|
} else {
|
|
|
sibling = parent.L;
|
|
|
if (sibling.C) {
|
|
|
sibling.C = false;
|
|
|
parent.C = true;
|
|
|
RedBlackRotateRight(this, parent);
|
|
|
sibling = parent.L;
|
|
|
}
|
|
|
if ((sibling.L && sibling.L.C)
|
|
|
|| (sibling.R && sibling.R.C)) {
|
|
|
if (!sibling.L || !sibling.L.C) {
|
|
|
sibling.R.C = false;
|
|
|
sibling.C = true;
|
|
|
RedBlackRotateLeft(this, sibling);
|
|
|
sibling = parent.L;
|
|
|
}
|
|
|
sibling.C = parent.C;
|
|
|
parent.C = sibling.L.C = false;
|
|
|
RedBlackRotateRight(this, parent);
|
|
|
node = this._;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
sibling.C = true;
|
|
|
node = parent;
|
|
|
parent = parent.U;
|
|
|
} while (!node.C);
|
|
|
|
|
|
if (node) node.C = false;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
function RedBlackRotateLeft(tree, node) {
|
|
|
var p = node,
|
|
|
q = node.R,
|
|
|
parent = p.U;
|
|
|
|
|
|
if (parent) {
|
|
|
if (parent.L === p) parent.L = q;
|
|
|
else parent.R = q;
|
|
|
} else {
|
|
|
tree._ = q;
|
|
|
}
|
|
|
|
|
|
q.U = parent;
|
|
|
p.U = q;
|
|
|
p.R = q.L;
|
|
|
if (p.R) p.R.U = p;
|
|
|
q.L = p;
|
|
|
}
|
|
|
|
|
|
function RedBlackRotateRight(tree, node) {
|
|
|
var p = node,
|
|
|
q = node.L,
|
|
|
parent = p.U;
|
|
|
|
|
|
if (parent) {
|
|
|
if (parent.L === p) parent.L = q;
|
|
|
else parent.R = q;
|
|
|
} else {
|
|
|
tree._ = q;
|
|
|
}
|
|
|
|
|
|
q.U = parent;
|
|
|
p.U = q;
|
|
|
p.L = q.R;
|
|
|
if (p.L) p.L.U = p;
|
|
|
q.R = p;
|
|
|
}
|
|
|
|
|
|
function RedBlackFirst(node) {
|
|
|
while (node.L) node = node.L;
|
|
|
return node;
|
|
|
}
|
|
|
|
|
|
function createEdge(left, right, v0, v1) {
|
|
|
var edge = [null, null],
|
|
|
index = edges.push(edge) - 1;
|
|
|
edge.left = left;
|
|
|
edge.right = right;
|
|
|
if (v0) setEdgeEnd(edge, left, right, v0);
|
|
|
if (v1) setEdgeEnd(edge, right, left, v1);
|
|
|
cells[left.index].halfedges.push(index);
|
|
|
cells[right.index].halfedges.push(index);
|
|
|
return edge;
|
|
|
}
|
|
|
|
|
|
function createBorderEdge(left, v0, v1) {
|
|
|
var edge = [v0, v1];
|
|
|
edge.left = left;
|
|
|
return edge;
|
|
|
}
|
|
|
|
|
|
function setEdgeEnd(edge, left, right, vertex) {
|
|
|
if (!edge[0] && !edge[1]) {
|
|
|
edge[0] = vertex;
|
|
|
edge.left = left;
|
|
|
edge.right = right;
|
|
|
} else if (edge.left === right) {
|
|
|
edge[1] = vertex;
|
|
|
} else {
|
|
|
edge[0] = vertex;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Liang–Barsky line clipping.
|
|
|
function clipEdge(edge, x0, y0, x1, y1) {
|
|
|
var a = edge[0],
|
|
|
b = edge[1],
|
|
|
ax = a[0],
|
|
|
ay = a[1],
|
|
|
bx = b[0],
|
|
|
by = b[1],
|
|
|
t0 = 0,
|
|
|
t1 = 1,
|
|
|
dx = bx - ax,
|
|
|
dy = by - ay,
|
|
|
r;
|
|
|
|
|
|
r = x0 - ax;
|
|
|
if (!dx && r > 0) return;
|
|
|
r /= dx;
|
|
|
if (dx < 0) {
|
|
|
if (r < t0) return;
|
|
|
if (r < t1) t1 = r;
|
|
|
} else if (dx > 0) {
|
|
|
if (r > t1) return;
|
|
|
if (r > t0) t0 = r;
|
|
|
}
|
|
|
|
|
|
r = x1 - ax;
|
|
|
if (!dx && r < 0) return;
|
|
|
r /= dx;
|
|
|
if (dx < 0) {
|
|
|
if (r > t1) return;
|
|
|
if (r > t0) t0 = r;
|
|
|
} else if (dx > 0) {
|
|
|
if (r < t0) return;
|
|
|
if (r < t1) t1 = r;
|
|
|
}
|
|
|
|
|
|
r = y0 - ay;
|
|
|
if (!dy && r > 0) return;
|
|
|
r /= dy;
|
|
|
if (dy < 0) {
|
|
|
if (r < t0) return;
|
|
|
if (r < t1) t1 = r;
|
|
|
} else if (dy > 0) {
|
|
|
if (r > t1) return;
|
|
|
if (r > t0) t0 = r;
|
|
|
}
|
|
|
|
|
|
r = y1 - ay;
|
|
|
if (!dy && r < 0) return;
|
|
|
r /= dy;
|
|
|
if (dy < 0) {
|
|
|
if (r > t1) return;
|
|
|
if (r > t0) t0 = r;
|
|
|
} else if (dy > 0) {
|
|
|
if (r < t0) return;
|
|
|
if (r < t1) t1 = r;
|
|
|
}
|
|
|
|
|
|
if (!(t0 > 0) && !(t1 < 1)) return true; // TODO Better check?
|
|
|
|
|
|
if (t0 > 0) edge[0] = [ax + t0 * dx, ay + t0 * dy];
|
|
|
if (t1 < 1) edge[1] = [ax + t1 * dx, ay + t1 * dy];
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
function connectEdge(edge, x0, y0, x1, y1) {
|
|
|
var v1 = edge[1];
|
|
|
if (v1) return true;
|
|
|
|
|
|
var v0 = edge[0],
|
|
|
left = edge.left,
|
|
|
right = edge.right,
|
|
|
lx = left[0],
|
|
|
ly = left[1],
|
|
|
rx = right[0],
|
|
|
ry = right[1],
|
|
|
fx = (lx + rx) / 2,
|
|
|
fy = (ly + ry) / 2,
|
|
|
fm,
|
|
|
fb;
|
|
|
|
|
|
if (ry === ly) {
|
|
|
if (fx < x0 || fx >= x1) return;
|
|
|
if (lx > rx) {
|
|
|
if (!v0) v0 = [fx, y0];
|
|
|
else if (v0[1] >= y1) return;
|
|
|
v1 = [fx, y1];
|
|
|
} else {
|
|
|
if (!v0) v0 = [fx, y1];
|
|
|
else if (v0[1] < y0) return;
|
|
|
v1 = [fx, y0];
|
|
|
}
|
|
|
} else {
|
|
|
fm = (lx - rx) / (ry - ly);
|
|
|
fb = fy - fm * fx;
|
|
|
if (fm < -1 || fm > 1) {
|
|
|
if (lx > rx) {
|
|
|
if (!v0) v0 = [(y0 - fb) / fm, y0];
|
|
|
else if (v0[1] >= y1) return;
|
|
|
v1 = [(y1 - fb) / fm, y1];
|
|
|
} else {
|
|
|
if (!v0) v0 = [(y1 - fb) / fm, y1];
|
|
|
else if (v0[1] < y0) return;
|
|
|
v1 = [(y0 - fb) / fm, y0];
|
|
|
}
|
|
|
} else {
|
|
|
if (ly < ry) {
|
|
|
if (!v0) v0 = [x0, fm * x0 + fb];
|
|
|
else if (v0[0] >= x1) return;
|
|
|
v1 = [x1, fm * x1 + fb];
|
|
|
} else {
|
|
|
if (!v0) v0 = [x1, fm * x1 + fb];
|
|
|
else if (v0[0] < x0) return;
|
|
|
v1 = [x0, fm * x0 + fb];
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
edge[0] = v0;
|
|
|
edge[1] = v1;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
function clipEdges(x0, y0, x1, y1) {
|
|
|
var i = edges.length,
|
|
|
edge;
|
|
|
|
|
|
while (i--) {
|
|
|
if (!connectEdge(edge = edges[i], x0, y0, x1, y1)
|
|
|
|| !clipEdge(edge, x0, y0, x1, y1)
|
|
|
|| !(Math.abs(edge[0][0] - edge[1][0]) > epsilon
|
|
|
|| Math.abs(edge[0][1] - edge[1][1]) > epsilon)) {
|
|
|
delete edges[i];
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function createCell(site) {
|
|
|
return cells[site.index] = {
|
|
|
site: site,
|
|
|
halfedges: []
|
|
|
};
|
|
|
}
|
|
|
|
|
|
function cellHalfedgeAngle(cell, edge) {
|
|
|
var site = cell.site,
|
|
|
va = edge.left,
|
|
|
vb = edge.right;
|
|
|
if (site === vb) vb = va, va = site;
|
|
|
if (vb) return Math.atan2(vb[1] - va[1], vb[0] - va[0]);
|
|
|
if (site === va) va = edge[1], vb = edge[0];
|
|
|
else va = edge[0], vb = edge[1];
|
|
|
return Math.atan2(va[0] - vb[0], vb[1] - va[1]);
|
|
|
}
|
|
|
|
|
|
function cellHalfedgeStart(cell, edge) {
|
|
|
return edge[+(edge.left !== cell.site)];
|
|
|
}
|
|
|
|
|
|
function cellHalfedgeEnd(cell, edge) {
|
|
|
return edge[+(edge.left === cell.site)];
|
|
|
}
|
|
|
|
|
|
function sortCellHalfedges() {
|
|
|
for (var i = 0, n = cells.length, cell, halfedges, j, m; i < n; ++i) {
|
|
|
if ((cell = cells[i]) && (m = (halfedges = cell.halfedges).length)) {
|
|
|
var index = new Array(m),
|
|
|
array = new Array(m);
|
|
|
for (j = 0; j < m; ++j) index[j] = j, array[j] = cellHalfedgeAngle(cell, edges[halfedges[j]]);
|
|
|
index.sort(function(i, j) { return array[j] - array[i]; });
|
|
|
for (j = 0; j < m; ++j) array[j] = halfedges[index[j]];
|
|
|
for (j = 0; j < m; ++j) halfedges[j] = array[j];
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function clipCells(x0, y0, x1, y1) {
|
|
|
var nCells = cells.length,
|
|
|
iCell,
|
|
|
cell,
|
|
|
site,
|
|
|
iHalfedge,
|
|
|
halfedges,
|
|
|
nHalfedges,
|
|
|
start,
|
|
|
startX,
|
|
|
startY,
|
|
|
end,
|
|
|
endX,
|
|
|
endY,
|
|
|
cover = true;
|
|
|
|
|
|
for (iCell = 0; iCell < nCells; ++iCell) {
|
|
|
if (cell = cells[iCell]) {
|
|
|
site = cell.site;
|
|
|
halfedges = cell.halfedges;
|
|
|
iHalfedge = halfedges.length;
|
|
|
|
|
|
// Remove any dangling clipped edges.
|
|
|
while (iHalfedge--) {
|
|
|
if (!edges[halfedges[iHalfedge]]) {
|
|
|
halfedges.splice(iHalfedge, 1);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Insert any border edges as necessary.
|
|
|
iHalfedge = 0, nHalfedges = halfedges.length;
|
|
|
while (iHalfedge < nHalfedges) {
|
|
|
end = cellHalfedgeEnd(cell, edges[halfedges[iHalfedge]]), endX = end[0], endY = end[1];
|
|
|
start = cellHalfedgeStart(cell, edges[halfedges[++iHalfedge % nHalfedges]]), startX = start[0], startY = start[1];
|
|
|
if (Math.abs(endX - startX) > epsilon || Math.abs(endY - startY) > epsilon) {
|
|
|
halfedges.splice(iHalfedge, 0, edges.push(createBorderEdge(site, end,
|
|
|
Math.abs(endX - x0) < epsilon && y1 - endY > epsilon ? [x0, Math.abs(startX - x0) < epsilon ? startY : y1]
|
|
|
: Math.abs(endY - y1) < epsilon && x1 - endX > epsilon ? [Math.abs(startY - y1) < epsilon ? startX : x1, y1]
|
|
|
: Math.abs(endX - x1) < epsilon && endY - y0 > epsilon ? [x1, Math.abs(startX - x1) < epsilon ? startY : y0]
|
|
|
: Math.abs(endY - y0) < epsilon && endX - x0 > epsilon ? [Math.abs(startY - y0) < epsilon ? startX : x0, y0]
|
|
|
: null)) - 1);
|
|
|
++nHalfedges;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (nHalfedges) cover = false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// If there weren’t any edges, have the closest site cover the extent.
|
|
|
// It doesn’t matter which corner of the extent we measure!
|
|
|
if (cover) {
|
|
|
var dx, dy, d2, dc = Infinity;
|
|
|
|
|
|
for (iCell = 0, cover = null; iCell < nCells; ++iCell) {
|
|
|
if (cell = cells[iCell]) {
|
|
|
site = cell.site;
|
|
|
dx = site[0] - x0;
|
|
|
dy = site[1] - y0;
|
|
|
d2 = dx * dx + dy * dy;
|
|
|
if (d2 < dc) dc = d2, cover = cell;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (cover) {
|
|
|
var v00 = [x0, y0], v01 = [x0, y1], v11 = [x1, y1], v10 = [x1, y0];
|
|
|
cover.halfedges.push(
|
|
|
edges.push(createBorderEdge(site = cover.site, v00, v01)) - 1,
|
|
|
edges.push(createBorderEdge(site, v01, v11)) - 1,
|
|
|
edges.push(createBorderEdge(site, v11, v10)) - 1,
|
|
|
edges.push(createBorderEdge(site, v10, v00)) - 1
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Lastly delete any cells with no edges; these were entirely clipped.
|
|
|
for (iCell = 0; iCell < nCells; ++iCell) {
|
|
|
if (cell = cells[iCell]) {
|
|
|
if (!cell.halfedges.length) {
|
|
|
delete cells[iCell];
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
var circlePool = [];
|
|
|
|
|
|
var firstCircle;
|
|
|
|
|
|
function Circle() {
|
|
|
RedBlackNode(this);
|
|
|
this.x =
|
|
|
this.y =
|
|
|
this.arc =
|
|
|
this.site =
|
|
|
this.cy = null;
|
|
|
}
|
|
|
|
|
|
function attachCircle(arc) {
|
|
|
var lArc = arc.P,
|
|
|
rArc = arc.N;
|
|
|
|
|
|
if (!lArc || !rArc) return;
|
|
|
|
|
|
var lSite = lArc.site,
|
|
|
cSite = arc.site,
|
|
|
rSite = rArc.site;
|
|
|
|
|
|
if (lSite === rSite) return;
|
|
|
|
|
|
var bx = cSite[0],
|
|
|
by = cSite[1],
|
|
|
ax = lSite[0] - bx,
|
|
|
ay = lSite[1] - by,
|
|
|
cx = rSite[0] - bx,
|
|
|
cy = rSite[1] - by;
|
|
|
|
|
|
var d = 2 * (ax * cy - ay * cx);
|
|
|
if (d >= -epsilon2) return;
|
|
|
|
|
|
var ha = ax * ax + ay * ay,
|
|
|
hc = cx * cx + cy * cy,
|
|
|
x = (cy * ha - ay * hc) / d,
|
|
|
y = (ax * hc - cx * ha) / d;
|
|
|
|
|
|
var circle = circlePool.pop() || new Circle;
|
|
|
circle.arc = arc;
|
|
|
circle.site = cSite;
|
|
|
circle.x = x + bx;
|
|
|
circle.y = (circle.cy = y + by) + Math.sqrt(x * x + y * y); // y bottom
|
|
|
|
|
|
arc.circle = circle;
|
|
|
|
|
|
var before = null,
|
|
|
node = circles._;
|
|
|
|
|
|
while (node) {
|
|
|
if (circle.y < node.y || (circle.y === node.y && circle.x <= node.x)) {
|
|
|
if (node.L) node = node.L;
|
|
|
else { before = node.P; break; }
|
|
|
} else {
|
|
|
if (node.R) node = node.R;
|
|
|
else { before = node; break; }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
circles.insert(before, circle);
|
|
|
if (!before) firstCircle = circle;
|
|
|
}
|
|
|
|
|
|
function detachCircle(arc) {
|
|
|
var circle = arc.circle;
|
|
|
if (circle) {
|
|
|
if (!circle.P) firstCircle = circle.N;
|
|
|
circles.remove(circle);
|
|
|
circlePool.push(circle);
|
|
|
RedBlackNode(circle);
|
|
|
arc.circle = null;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
var beachPool = [];
|
|
|
|
|
|
function Beach() {
|
|
|
RedBlackNode(this);
|
|
|
this.edge =
|
|
|
this.site =
|
|
|
this.circle = null;
|
|
|
}
|
|
|
|
|
|
function createBeach(site) {
|
|
|
var beach = beachPool.pop() || new Beach;
|
|
|
beach.site = site;
|
|
|
return beach;
|
|
|
}
|
|
|
|
|
|
function detachBeach(beach) {
|
|
|
detachCircle(beach);
|
|
|
beaches.remove(beach);
|
|
|
beachPool.push(beach);
|
|
|
RedBlackNode(beach);
|
|
|
}
|
|
|
|
|
|
function removeBeach(beach) {
|
|
|
var circle = beach.circle,
|
|
|
x = circle.x,
|
|
|
y = circle.cy,
|
|
|
vertex = [x, y],
|
|
|
previous = beach.P,
|
|
|
next = beach.N,
|
|
|
disappearing = [beach];
|
|
|
|
|
|
detachBeach(beach);
|
|
|
|
|
|
var lArc = previous;
|
|
|
while (lArc.circle
|
|
|
&& Math.abs(x - lArc.circle.x) < epsilon
|
|
|
&& Math.abs(y - lArc.circle.cy) < epsilon) {
|
|
|
previous = lArc.P;
|
|
|
disappearing.unshift(lArc);
|
|
|
detachBeach(lArc);
|
|
|
lArc = previous;
|
|
|
}
|
|
|
|
|
|
disappearing.unshift(lArc);
|
|
|
detachCircle(lArc);
|
|
|
|
|
|
var rArc = next;
|
|
|
while (rArc.circle
|
|
|
&& Math.abs(x - rArc.circle.x) < epsilon
|
|
|
&& Math.abs(y - rArc.circle.cy) < epsilon) {
|
|
|
next = rArc.N;
|
|
|
disappearing.push(rArc);
|
|
|
detachBeach(rArc);
|
|
|
rArc = next;
|
|
|
}
|
|
|
|
|
|
disappearing.push(rArc);
|
|
|
detachCircle(rArc);
|
|
|
|
|
|
var nArcs = disappearing.length,
|
|
|
iArc;
|
|
|
for (iArc = 1; iArc < nArcs; ++iArc) {
|
|
|
rArc = disappearing[iArc];
|
|
|
lArc = disappearing[iArc - 1];
|
|
|
setEdgeEnd(rArc.edge, lArc.site, rArc.site, vertex);
|
|
|
}
|
|
|
|
|
|
lArc = disappearing[0];
|
|
|
rArc = disappearing[nArcs - 1];
|
|
|
rArc.edge = createEdge(lArc.site, rArc.site, null, vertex);
|
|
|
|
|
|
attachCircle(lArc);
|
|
|
attachCircle(rArc);
|
|
|
}
|
|
|
|
|
|
function addBeach(site) {
|
|
|
var x = site[0],
|
|
|
directrix = site[1],
|
|
|
lArc,
|
|
|
rArc,
|
|
|
dxl,
|
|
|
dxr,
|
|
|
node = beaches._;
|
|
|
|
|
|
while (node) {
|
|
|
dxl = leftBreakPoint(node, directrix) - x;
|
|
|
if (dxl > epsilon) node = node.L; else {
|
|
|
dxr = x - rightBreakPoint(node, directrix);
|
|
|
if (dxr > epsilon) {
|
|
|
if (!node.R) {
|
|
|
lArc = node;
|
|
|
break;
|
|
|
}
|
|
|
node = node.R;
|
|
|
} else {
|
|
|
if (dxl > -epsilon) {
|
|
|
lArc = node.P;
|
|
|
rArc = node;
|
|
|
} else if (dxr > -epsilon) {
|
|
|
lArc = node;
|
|
|
rArc = node.N;
|
|
|
} else {
|
|
|
lArc = rArc = node;
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
createCell(site);
|
|
|
var newArc = createBeach(site);
|
|
|
beaches.insert(lArc, newArc);
|
|
|
|
|
|
if (!lArc && !rArc) return;
|
|
|
|
|
|
if (lArc === rArc) {
|
|
|
detachCircle(lArc);
|
|
|
rArc = createBeach(lArc.site);
|
|
|
beaches.insert(newArc, rArc);
|
|
|
newArc.edge = rArc.edge = createEdge(lArc.site, newArc.site);
|
|
|
attachCircle(lArc);
|
|
|
attachCircle(rArc);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (!rArc) { // && lArc
|
|
|
newArc.edge = createEdge(lArc.site, newArc.site);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// else lArc !== rArc
|
|
|
detachCircle(lArc);
|
|
|
detachCircle(rArc);
|
|
|
|
|
|
var lSite = lArc.site,
|
|
|
ax = lSite[0],
|
|
|
ay = lSite[1],
|
|
|
bx = site[0] - ax,
|
|
|
by = site[1] - ay,
|
|
|
rSite = rArc.site,
|
|
|
cx = rSite[0] - ax,
|
|
|
cy = rSite[1] - ay,
|
|
|
d = 2 * (bx * cy - by * cx),
|
|
|
hb = bx * bx + by * by,
|
|
|
hc = cx * cx + cy * cy,
|
|
|
vertex = [(cy * hb - by * hc) / d + ax, (bx * hc - cx * hb) / d + ay];
|
|
|
|
|
|
setEdgeEnd(rArc.edge, lSite, rSite, vertex);
|
|
|
newArc.edge = createEdge(lSite, site, null, vertex);
|
|
|
rArc.edge = createEdge(site, rSite, null, vertex);
|
|
|
attachCircle(lArc);
|
|
|
attachCircle(rArc);
|
|
|
}
|
|
|
|
|
|
function leftBreakPoint(arc, directrix) {
|
|
|
var site = arc.site,
|
|
|
rfocx = site[0],
|
|
|
rfocy = site[1],
|
|
|
pby2 = rfocy - directrix;
|
|
|
|
|
|
if (!pby2) return rfocx;
|
|
|
|
|
|
var lArc = arc.P;
|
|
|
if (!lArc) return -Infinity;
|
|
|
|
|
|
site = lArc.site;
|
|
|
var lfocx = site[0],
|
|
|
lfocy = site[1],
|
|
|
plby2 = lfocy - directrix;
|
|
|
|
|
|
if (!plby2) return lfocx;
|
|
|
|
|
|
var hl = lfocx - rfocx,
|
|
|
aby2 = 1 / pby2 - 1 / plby2,
|
|
|
b = hl / plby2;
|
|
|
|
|
|
if (aby2) return (-b + Math.sqrt(b * b - 2 * aby2 * (hl * hl / (-2 * plby2) - lfocy + plby2 / 2 + rfocy - pby2 / 2))) / aby2 + rfocx;
|
|
|
|
|
|
return (rfocx + lfocx) / 2;
|
|
|
}
|
|
|
|
|
|
function rightBreakPoint(arc, directrix) {
|
|
|
var rArc = arc.N;
|
|
|
if (rArc) return leftBreakPoint(rArc, directrix);
|
|
|
var site = arc.site;
|
|
|
return site[1] === directrix ? site[0] : Infinity;
|
|
|
}
|
|
|
|
|
|
var epsilon = 1e-6;
|
|
|
var epsilon2 = 1e-12;
|
|
|
var beaches;
|
|
|
var cells;
|
|
|
var circles;
|
|
|
var edges;
|
|
|
|
|
|
function triangleArea(a, b, c) {
|
|
|
return (a[0] - c[0]) * (b[1] - a[1]) - (a[0] - b[0]) * (c[1] - a[1]);
|
|
|
}
|
|
|
|
|
|
function lexicographic(a, b) {
|
|
|
return b[1] - a[1]
|
|
|
|| b[0] - a[0];
|
|
|
}
|
|
|
|
|
|
function Diagram(sites, extent) {
|
|
|
var site = sites.sort(lexicographic).pop(),
|
|
|
x,
|
|
|
y,
|
|
|
circle;
|
|
|
|
|
|
edges = [];
|
|
|
cells = new Array(sites.length);
|
|
|
beaches = new RedBlackTree;
|
|
|
circles = new RedBlackTree;
|
|
|
|
|
|
while (true) {
|
|
|
circle = firstCircle;
|
|
|
if (site && (!circle || site[1] < circle.y || (site[1] === circle.y && site[0] < circle.x))) {
|
|
|
if (site[0] !== x || site[1] !== y) {
|
|
|
addBeach(site);
|
|
|
x = site[0], y = site[1];
|
|
|
}
|
|
|
site = sites.pop();
|
|
|
} else if (circle) {
|
|
|
removeBeach(circle.arc);
|
|
|
} else {
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
sortCellHalfedges();
|
|
|
|
|
|
if (extent) {
|
|
|
var x0 = +extent[0][0],
|
|
|
y0 = +extent[0][1],
|
|
|
x1 = +extent[1][0],
|
|
|
y1 = +extent[1][1];
|
|
|
clipEdges(x0, y0, x1, y1);
|
|
|
clipCells(x0, y0, x1, y1);
|
|
|
}
|
|
|
|
|
|
this.edges = edges;
|
|
|
this.cells = cells;
|
|
|
|
|
|
beaches =
|
|
|
circles =
|
|
|
edges =
|
|
|
cells = null;
|
|
|
}
|
|
|
|
|
|
Diagram.prototype = {
|
|
|
constructor: Diagram,
|
|
|
|
|
|
polygons: function() {
|
|
|
var edges = this.edges;
|
|
|
|
|
|
return this.cells.map(function(cell) {
|
|
|
var polygon = cell.halfedges.map(function(i) { return cellHalfedgeStart(cell, edges[i]); });
|
|
|
polygon.data = cell.site.data;
|
|
|
return polygon;
|
|
|
});
|
|
|
},
|
|
|
|
|
|
triangles: function() {
|
|
|
var triangles = [],
|
|
|
edges = this.edges;
|
|
|
|
|
|
this.cells.forEach(function(cell, i) {
|
|
|
if (!(m = (halfedges = cell.halfedges).length)) return;
|
|
|
var site = cell.site,
|
|
|
halfedges,
|
|
|
j = -1,
|
|
|
m,
|
|
|
s0,
|
|
|
e1 = edges[halfedges[m - 1]],
|
|
|
s1 = e1.left === site ? e1.right : e1.left;
|
|
|
|
|
|
while (++j < m) {
|
|
|
s0 = s1;
|
|
|
e1 = edges[halfedges[j]];
|
|
|
s1 = e1.left === site ? e1.right : e1.left;
|
|
|
if (s0 && s1 && i < s0.index && i < s1.index && triangleArea(site, s0, s1) < 0) {
|
|
|
triangles.push([site.data, s0.data, s1.data]);
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
|
|
|
return triangles;
|
|
|
},
|
|
|
|
|
|
links: function() {
|
|
|
return this.edges.filter(function(edge) {
|
|
|
return edge.right;
|
|
|
}).map(function(edge) {
|
|
|
return {
|
|
|
source: edge.left.data,
|
|
|
target: edge.right.data
|
|
|
};
|
|
|
});
|
|
|
},
|
|
|
|
|
|
find: function(x, y, radius) {
|
|
|
var that = this, i0, i1 = that._found || 0, n = that.cells.length, cell;
|
|
|
|
|
|
// Use the previously-found cell, or start with an arbitrary one.
|
|
|
while (!(cell = that.cells[i1])) if (++i1 >= n) return null;
|
|
|
var dx = x - cell.site[0], dy = y - cell.site[1], d2 = dx * dx + dy * dy;
|
|
|
|
|
|
// Traverse the half-edges to find a closer cell, if any.
|
|
|
do {
|
|
|
cell = that.cells[i0 = i1], i1 = null;
|
|
|
cell.halfedges.forEach(function(e) {
|
|
|
var edge = that.edges[e], v = edge.left;
|
|
|
if ((v === cell.site || !v) && !(v = edge.right)) return;
|
|
|
var vx = x - v[0], vy = y - v[1], v2 = vx * vx + vy * vy;
|
|
|
if (v2 < d2) d2 = v2, i1 = v.index;
|
|
|
});
|
|
|
} while (i1 !== null);
|
|
|
|
|
|
that._found = i0;
|
|
|
|
|
|
return radius == null || d2 <= radius * radius ? cell.site : null;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
var voronoi = function() {
|
|
|
var x$$1 = x,
|
|
|
y$$1 = y,
|
|
|
extent = null;
|
|
|
|
|
|
function voronoi(data) {
|
|
|
return new Diagram(data.map(function(d, i) {
|
|
|
var s = [Math.round(x$$1(d, i, data) / epsilon) * epsilon, Math.round(y$$1(d, i, data) / epsilon) * epsilon];
|
|
|
s.index = i;
|
|
|
s.data = d;
|
|
|
return s;
|
|
|
}), extent);
|
|
|
}
|
|
|
|
|
|
voronoi.polygons = function(data) {
|
|
|
return voronoi(data).polygons();
|
|
|
};
|
|
|
|
|
|
voronoi.links = function(data) {
|
|
|
return voronoi(data).links();
|
|
|
};
|
|
|
|
|
|
voronoi.triangles = function(data) {
|
|
|
return voronoi(data).triangles();
|
|
|
};
|
|
|
|
|
|
voronoi.x = function(_) {
|
|
|
return arguments.length ? (x$$1 = typeof _ === "function" ? _ : constant(+_), voronoi) : x$$1;
|
|
|
};
|
|
|
|
|
|
voronoi.y = function(_) {
|
|
|
return arguments.length ? (y$$1 = typeof _ === "function" ? _ : constant(+_), voronoi) : y$$1;
|
|
|
};
|
|
|
|
|
|
voronoi.extent = function(_) {
|
|
|
return arguments.length ? (extent = _ == null ? null : [[+_[0][0], +_[0][1]], [+_[1][0], +_[1][1]]], voronoi) : extent && [[extent[0][0], extent[0][1]], [extent[1][0], extent[1][1]]];
|
|
|
};
|
|
|
|
|
|
voronoi.size = function(_) {
|
|
|
return arguments.length ? (extent = _ == null ? null : [[0, 0], [+_[0], +_[1]]], voronoi) : extent && [extent[1][0] - extent[0][0], extent[1][1] - extent[0][1]];
|
|
|
};
|
|
|
|
|
|
return voronoi;
|
|
|
};
|
|
|
|
|
|
exports.voronoi = voronoi;
|
|
|
|
|
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
|
|
|
})));
|