Skip to content

Commit 7b91151

Browse files
Modernize voronoi (#557)
Voronoi.js modernization by @SaltyQuetzals * Changed Voronoi generator to be more class-based, added mediocre documentation. * Added spaces to JSDoc links so text doesn't bleed into URL * Found delaunator docs, very helpful in writing function documentation.
1 parent ed33864 commit 7b91151

File tree

3 files changed

+125
-71
lines changed

3 files changed

+125
-71
lines changed

main.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ document.title += " v" + version;
1212

1313
// Switches to disable/enable logging features
1414
const INFO = 0;
15-
const TIME = 0;
15+
const TIME = 1;
1616
const WARN = 1;
1717
const ERROR = 1;
1818

@@ -610,7 +610,7 @@ function calculateVoronoi(graph, points) {
610610
TIME && console.timeEnd("calculateDelaunay");
611611

612612
TIME && console.time("calculateVoronoi");
613-
const voronoi = Voronoi(delaunay, allPoints, n);
613+
const voronoi = new Voronoi(delaunay, allPoints, n);
614614
graph.cells = voronoi.cells;
615615
graph.cells.i = n < 65535 ? Uint16Array.from(d3.range(n)) : Uint32Array.from(d3.range(n)); // array of indexes
616616
graph.vertices = voronoi.vertices;

modules/burgs-and-states.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@
394394
const hull = getHull(start, s.i, s.cells / 10);
395395
const points = [...hull].map(v => pack.vertices.p[v]);
396396
const delaunay = Delaunator.from(points);
397-
const voronoi = Voronoi(delaunay, points, points.length);
397+
const voronoi = new Voronoi(delaunay, points, points.length);
398398
const chain = connectCenters(voronoi.vertices, s.pole[1]);
399399
const relaxed = chain.map(i => voronoi.vertices.p[i]).filter((p, i) => i%15 === 0 || i+1 === chain.length);
400400
paths.push([s.i, relaxed]);

modules/voronoi.js

Lines changed: 122 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,136 @@
1-
(function (global, factory) {
2-
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3-
typeof define === 'function' && define.amd ? define(factory) :
4-
(global.Voronoi = factory());
5-
}(this, (function () { 'use strict';
6-
7-
var Voronoi = function Voronoi(delaunay, points, pointsN) {
8-
const cells = {v: [], c: [], b: []}; // voronoi cells: v = cell vertices, c = adjacent cells, b = near-border cell
9-
const vertices = {p: [], v: [], c: []}; // cells vertices: p = vertex coordinates, v = neighboring vertices, c = adjacent cells
10-
11-
for (let e=0; e < delaunay.triangles.length; e++) {
12-
13-
const p = delaunay.triangles[nextHalfedge(e)];
14-
if (p < pointsN && !cells.c[p]) {
15-
const edges = edgesAroundPoint(e);
16-
cells.v[p] = edges.map(e => triangleOfEdge(e)); // cell: adjacent vertex
17-
cells.c[p] = edges.map(e => delaunay.triangles[e]).filter(c => c < pointsN); // cell: adjacent valid cells
18-
cells.b[p] = edges.length > cells.c[p].length ? 1 : 0; // cell: is border
19-
}
20-
21-
const t = triangleOfEdge(e);
22-
if (!vertices.p[t]) {
23-
vertices.p[t] = triangleCenter(t); // vertex: coordinates
24-
vertices.v[t] = trianglesAdjacentToTriangle(t); // vertex: adjacent vertices
25-
vertices.c[t] = pointsOfTriangle(t); // vertex: adjacent cells
1+
class Voronoi {
2+
/**
3+
* Creates a Voronoi diagram from the given Delaunator, a list of points, and the number of points. The Voronoi diagram is constructed using (I think) the {@link https://en.wikipedia.org/wiki/Bowyer%E2%80%93Watson_algorithm |Bowyer-Watson Algorithm}
4+
* The {@link https://github.com/mapbox/delaunator/ |Delaunator} library uses {@link https://en.wikipedia.org/wiki/Doubly_connected_edge_list |half-edges} to represent the relationship between points and triangles.
5+
* @param {{triangles: Uint32Array, halfedges: Int32Array}} delaunay A {@link https://github.com/mapbox/delaunator/blob/master/index.js |Delaunator} instance.
6+
* @param {[number, number][]} points A list of coordinates.
7+
* @param {number} pointsN The number of points.
8+
*/
9+
constructor(delaunay, points, pointsN) {
10+
this.delaunay = delaunay;
11+
this.points = points;
12+
this.pointsN = pointsN;
13+
this.cells = { v: [], c: [], b: [] }; // voronoi cells: v = cell vertices, c = adjacent cells, b = near-border cell
14+
this.vertices = { p: [], v: [], c: [] }; // cells vertices: p = vertex coordinates, v = neighboring vertices, c = adjacent cells
15+
16+
17+
// Half-edges are the indices into the delaunator outputs:
18+
// delaunay.triangles[e] gives the point ID where the half-edge starts
19+
// delaunay.triangles[e] returns either the opposite half-edge in the adjacent triangle, or -1 if there's not an adjacent triangle.
20+
for (let e = 0; e < this.delaunay.triangles.length; e++) {
21+
22+
const p = this.delaunay.triangles[this.nextHalfedge(e)];
23+
if (p < this.pointsN && !this.cells.c[p]) {
24+
const edges = this.edgesAroundPoint(e);
25+
this.cells.v[p] = edges.map(e => this.triangleOfEdge(e)); // cell: adjacent vertex
26+
this.cells.c[p] = edges.map(e => this.delaunay.triangles[e]).filter(c => c < this.pointsN); // cell: adjacent valid cells
27+
this.cells.b[p] = edges.length > this.cells.c[p].length ? 1 : 0; // cell: is border
2628
}
27-
}
28-
29-
function pointsOfTriangle(t) {
30-
return edgesOfTriangle(t).map(e => delaunay.triangles[e]);
31-
}
3229

33-
function trianglesAdjacentToTriangle(t) {
34-
let triangles = [];
35-
for (let e of edgesOfTriangle(t)) {
36-
let opposite = delaunay.halfedges[e];
37-
triangles.push(triangleOfEdge(opposite));
30+
const t = this.triangleOfEdge(e);
31+
if (!this.vertices.p[t]) {
32+
this.vertices.p[t] = this.triangleCenter(t); // vertex: coordinates
33+
this.vertices.v[t] = this.trianglesAdjacentToTriangle(t); // vertex: adjacent vertices
34+
this.vertices.c[t] = this.pointsOfTriangle(t); // vertex: adjacent cells
3835
}
39-
return triangles;
4036
}
37+
}
4138

42-
function edgesAroundPoint(start) {
43-
let result = [], incoming = start;
44-
do {
45-
result.push(incoming);
46-
const outgoing = nextHalfedge(incoming);
47-
incoming = delaunay.halfedges[outgoing];
48-
} while (incoming !== -1 && incoming !== start && result.length < 20);
49-
return result;
50-
}
39+
/**
40+
* Gets the IDs of the points comprising the given triangle. Taken from {@link https://mapbox.github.io/delaunator/#triangle-to-points| the Delaunator docs.}
41+
* @param {number} t The index of the triangle
42+
* @returns {[number, number, number]} The IDs of the points comprising the given triangle.
43+
*/
44+
pointsOfTriangle(t) {
45+
return this.edgesOfTriangle(t).map(edge => this.delaunay.triangles[edge]);
46+
}
5147

52-
function triangleCenter(t) {
53-
let vertices = pointsOfTriangle(t).map(p => points[p]);
54-
return circumcenter(vertices[0], vertices[1], vertices[2]);
48+
/**
49+
* Identifies what triangles are adjacent to the given triangle. Taken from {@link https://mapbox.github.io/delaunator/#triangle-to-triangles| the Delaunator docs.}
50+
* @param {number} t The index of the triangle
51+
* @returns {number[]} The indices of the triangles that share half-edges with this triangle.
52+
*/
53+
trianglesAdjacentToTriangle(t) {
54+
let triangles = [];
55+
for (let edge of this.edgesOfTriangle(t)) {
56+
let opposite = this.delaunay.halfedges[edge];
57+
triangles.push(this.triangleOfEdge(opposite));
5558
}
56-
57-
return {cells, vertices}
58-
59+
return triangles;
5960
}
6061

61-
function edgesOfTriangle(t) {return [3*t, 3*t+1, 3*t+2];}
62-
63-
function triangleOfEdge(e) {return Math.floor(e/3);}
64-
65-
function nextHalfedge(e) {return (e % 3 === 2) ? e-2 : e+1;}
62+
/**
63+
* Gets the indices of all the incoming and outgoing half-edges that touch the given point. Taken from {@link https://mapbox.github.io/delaunator/#point-to-edges| the Delaunator docs.}
64+
* @param {number} start The index of an incoming half-edge that leads to the desired point
65+
* @returns {number[]} The indices of all half-edges (incoming or outgoing) that touch the point.
66+
*/
67+
edgesAroundPoint(start) {
68+
const result = [];
69+
let incoming = start;
70+
do {
71+
result.push(incoming);
72+
const outgoing = this.nextHalfedge(incoming);
73+
incoming = this.delaunay.halfedges[outgoing];
74+
} while (incoming !== -1 && incoming !== start && result.length < 20);
75+
return result;
76+
}
6677

67-
function prevHalfedge(e) {return (e % 3 === 0) ? e+2 : e-1;}
78+
/**
79+
* Returns the center of the triangle located at the given index.
80+
* @param {number} t The index of the triangle
81+
* @returns {[number, number]}
82+
*/
83+
triangleCenter(t) {
84+
let vertices = this.pointsOfTriangle(t).map(p => this.points[p]);
85+
return this.circumcenter(vertices[0], vertices[1], vertices[2]);
86+
}
6887

69-
function circumcenter(a, b, c) {
70-
let ad = a[0]*a[0] + a[1]*a[1],
71-
bd = b[0]*b[0] + b[1]*b[1],
72-
cd = c[0]*c[0] + c[1]*c[1];
73-
let D = 2 * (a[0] * (b[1] - c[1]) + b[0] * (c[1] - a[1]) + c[0] * (a[1] - b[1]));
88+
/**
89+
* Retrieves all of the half-edges for a specific triangle `t`. Taken from {@link https://mapbox.github.io/delaunator/#edge-and-triangle| the Delaunator docs.}
90+
* @param {number} t The index of the triangle
91+
* @returns {[number, number, number]} The edges of the triangle.
92+
*/
93+
edgesOfTriangle(t) { return [3 * t, 3 * t + 1, 3 * t + 2]; }
94+
95+
/**
96+
* Enables lookup of a triangle, given one of the half-edges of that triangle. Taken from {@link https://mapbox.github.io/delaunator/#edge-and-triangle| the Delaunator docs.}
97+
* @param {number} e The index of the edge
98+
* @returns {number} The index of the triangle
99+
*/
100+
triangleOfEdge(e) { return Math.floor(e / 3); }
101+
102+
/**
103+
* Moves to the next half-edge of a triangle, given the current half-edge's index. Taken from {@link https://mapbox.github.io/delaunator/#edge-to-edges| the Delaunator docs.}
104+
* @param {number} e The index of the current half edge
105+
* @returns {number} The index of the next half edge
106+
*/
107+
nextHalfedge(e) { return (e % 3 === 2) ? e - 2 : e + 1; }
108+
109+
/**
110+
* Moves to the previous half-edge of a triangle, given the current half-edge's index. Taken from {@link https://mapbox.github.io/delaunator/#edge-to-edges| the Delaunator docs.}
111+
* @param {number} e The index of the current half edge
112+
* @returns {number} The index of the previous half edge
113+
*/
114+
prevHalfedge(e) { return (e % 3 === 0) ? e + 2 : e - 1; }
115+
116+
/**
117+
* Finds the circumcenter of the triangle identified by points a, b, and c. Taken from {@link https://en.wikipedia.org/wiki/Circumscribed_circle#Circumcenter_coordinates| Wikipedia}
118+
* @param {[number, number]} a The coordinates of the first point of the triangle
119+
* @param {[number, number]} b The coordinates of the second point of the triangle
120+
* @param {[number, number]} c The coordinates of the third point of the triangle
121+
* @return {[number, number]} The coordinates of the circumcenter of the triangle.
122+
*/
123+
circumcenter(a, b, c) {
124+
const [ax, ay] = a;
125+
const [bx, by] = b;
126+
const [cx, cy] = c;
127+
const ad = ax * ax + ay * ay;
128+
const bd = bx * bx + by * by;
129+
const cd = cx * cx + cy * cy;
130+
const D = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by));
74131
return [
75-
Math.floor(1/D * (ad * (b[1] - c[1]) + bd * (c[1] - a[1]) + cd * (a[1] - b[1]))),
76-
Math.floor(1/D * (ad * (c[0] - b[0]) + bd * (a[0] - c[0]) + cd * (b[0] - a[0])))
132+
Math.floor(1 / D * (ad * (by - cy) + bd * (cy - ay) + cd * (ay - by))),
133+
Math.floor(1 / D * (ad * (cx - bx) + bd * (ax - cx) + cd * (bx - ax)))
77134
];
78135
}
79-
80-
return Voronoi;
81-
82-
})));
136+
}

0 commit comments

Comments
 (0)