Changed Voronoi generator to be more class-based, added mediocre documentation.

This commit is contained in:
Gabriel 2021-02-02 13:27:34 -05:00
parent 40e5930571
commit 449fded687
3 changed files with 113 additions and 69 deletions

View file

@ -12,7 +12,7 @@ document.title += " v" + version;
// Switches to disable/enable logging features
const INFO = 0;
const TIME = 0;
const TIME = 1;
const WARN = 1;
const ERROR = 1;
@ -611,7 +611,7 @@ function calculateVoronoi(graph, points) {
TIME && console.timeEnd("calculateDelaunay");
TIME && console.time("calculateVoronoi");
const voronoi = Voronoi(delaunay, allPoints, n);
const voronoi = new Voronoi(delaunay, allPoints, n);
graph.cells = voronoi.cells;
graph.cells.i = n < 65535 ? Uint16Array.from(d3.range(n)) : Uint32Array.from(d3.range(n)); // array of indexes
graph.vertices = voronoi.vertices;

View file

@ -394,7 +394,7 @@
const hull = getHull(start, s.i, s.cells / 10);
const points = [...hull].map(v => pack.vertices.p[v]);
const delaunay = Delaunator.from(points);
const voronoi = Voronoi(delaunay, points, points.length);
const voronoi = new Voronoi(delaunay, points, points.length);
const chain = connectCenters(voronoi.vertices, s.pole[1]);
const relaxed = chain.map(i => voronoi.vertices.p[i]).filter((p, i) => i%15 === 0 || i+1 === chain.length);
paths.push([s.i, relaxed]);

View file

@ -1,82 +1,126 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.Voronoi = factory());
}(this, (function () { 'use strict';
class Voronoi {
/**
* 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}
* @param {{triangles: Uint32Array, halfedges: Int32Array}} delaunay A {@link https://github.com/mapbox/delaunator/blob/master/index.js|Delaunator} instance
* @param {[number, number][]} points A list of coordinates.
* @param {number} pointsN The number of points.
*/
constructor(delaunay, points, pointsN) {
this.delaunay = delaunay;
this.points = points;
this.pointsN = pointsN;
this.cells = { v: [], c: [], b: [] }; // voronoi cells: v = cell vertices, c = adjacent cells, b = near-border cell
this.vertices = { p: [], v: [], c: [] }; // cells vertices: p = vertex coordinates, v = neighboring vertices, c = adjacent cells
var Voronoi = function Voronoi(delaunay, points, pointsN) {
const cells = {v: [], c: [], b: []}; // voronoi cells: v = cell vertices, c = adjacent cells, b = near-border cell
const vertices = {p: [], v: [], c: []}; // cells vertices: p = vertex coordinates, v = neighboring vertices, c = adjacent cells
for (let e = 0; e < this.delaunay.triangles.length; e++) {
for (let e=0; e < delaunay.triangles.length; e++) {
const p = delaunay.triangles[nextHalfedge(e)];
if (p < pointsN && !cells.c[p]) {
const edges = edgesAroundPoint(e);
cells.v[p] = edges.map(e => triangleOfEdge(e)); // cell: adjacent vertex
cells.c[p] = edges.map(e => delaunay.triangles[e]).filter(c => c < pointsN); // cell: adjacent valid cells
cells.b[p] = edges.length > cells.c[p].length ? 1 : 0; // cell: is border
const p = this.delaunay.triangles[this.nextHalfedge(e)];
if (p < this.pointsN && !this.cells.c[p]) {
const edges = this.edgesAroundPoint(e);
this.cells.v[p] = edges.map(e => this.triangleOfEdge(e)); // cell: adjacent vertex
this.cells.c[p] = edges.map(e => this.delaunay.triangles[e]).filter(c => c < this.pointsN); // cell: adjacent valid cells
this.cells.b[p] = edges.length > this.cells.c[p].length ? 1 : 0; // cell: is border
}
const t = triangleOfEdge(e);
if (!vertices.p[t]) {
vertices.p[t] = triangleCenter(t); // vertex: coordinates
vertices.v[t] = trianglesAdjacentToTriangle(t); // vertex: adjacent vertices
vertices.c[t] = pointsOfTriangle(t); // vertex: adjacent cells
const t = this.triangleOfEdge(e);
if (!this.vertices.p[t]) {
this.vertices.p[t] = this.triangleCenter(t); // vertex: coordinates
this.vertices.v[t] = this.trianglesAdjacentToTriangle(t); // vertex: adjacent vertices
this.vertices.c[t] = this.pointsOfTriangle(t); // vertex: adjacent cells
}
}
}
function pointsOfTriangle(t) {
return edgesOfTriangle(t).map(e => delaunay.triangles[e]);
/**
*
* @param {number} t The index of the triangle
* @returns {[number, number, number]}
*/
pointsOfTriangle(t) {
return this.edgesOfTriangle(t).map(edge => this.delaunay.triangles[edge]);
}
function trianglesAdjacentToTriangle(t) {
/**
* Identifies what triangles are adjacent to the given triangle
* @param {number} t The index of the triangle
* @returns {number[]}
*/
trianglesAdjacentToTriangle(t) {
let triangles = [];
for (let e of edgesOfTriangle(t)) {
let opposite = delaunay.halfedges[e];
triangles.push(triangleOfEdge(opposite));
for (let edge of this.edgesOfTriangle(t)) {
let opposite = this.delaunay.halfedges[edge];
triangles.push(this.triangleOfEdge(opposite));
}
return triangles;
}
function edgesAroundPoint(start) {
let result = [], incoming = start;
/**
*
* @param {number} start
* @returns {number[]}
*/
edgesAroundPoint(start) {
const result = [];
let incoming = start;
do {
result.push(incoming);
const outgoing = nextHalfedge(incoming);
incoming = delaunay.halfedges[outgoing];
const outgoing = this.nextHalfedge(incoming);
incoming = this.delaunay.halfedges[outgoing];
} while (incoming !== -1 && incoming !== start && result.length < 20);
return result;
}
function triangleCenter(t) {
let vertices = pointsOfTriangle(t).map(p => points[p]);
return circumcenter(vertices[0], vertices[1], vertices[2]);
/**
* Returns the center of the triangle located at the given index.
* @param {number} t The index of the triangle
* @returns {number}
*/
triangleCenter(t) {
let vertices = this.pointsOfTriangle(t).map(p => this.points[p]);
return this.circumcenter(vertices[0], vertices[1], vertices[2]);
}
return {cells, vertices}
/**
* Gets all of the edges of a triangle starting at index t
* @param {number} t The index of the triangle
* @returns {[number, number, number]} The edges of the triangle
*/
edgesOfTriangle(t) { return [3 * t, 3 * t + 1, 3 * t + 2]; }
}
/**
* Identifies the triangle that corresponds to the given edge index
* @param {number} e The index of the edge
* @returns {number} The index of the triangle
*/
triangleOfEdge(e) { return Math.floor(e / 3); }
/**
* Determines the index of the next half edge of the current triangle in the this.delaunay.triangles array
* @param {number} e The index of the current half edge
* @returns {number} The index of the next half edge
*/
nextHalfedge(e) { return (e % 3 === 2) ? e - 2 : e + 1; }
/**
* Determines the index of the previous half edge of the current triangle in the this.delaunay.triangles array
* @param {number} e The index of the current half edge
* @returns {number} The index of the previous half edge
*/
prevHalfedge(e) { return (e % 3 === 0) ? e + 2 : e - 1; }
function edgesOfTriangle(t) {return [3*t, 3*t+1, 3*t+2];}
function triangleOfEdge(e) {return Math.floor(e/3);}
function nextHalfedge(e) {return (e % 3 === 2) ? e-2 : e+1;}
function prevHalfedge(e) {return (e % 3 === 0) ? e+2 : e-1;}
function circumcenter(a, b, c) {
let ad = a[0]*a[0] + a[1]*a[1],
bd = b[0]*b[0] + b[1]*b[1],
cd = c[0]*c[0] + c[1]*c[1];
/**
* Finds the circumcenter of the triangle identified by points a, b, and c.
* @param {[number, number]} a
* @param {[number, number]} b
* @param {[number, number]} c
* @return {[number, number]} The coordinates of the circumcenter of the triangle.
*/
circumcenter(a, b, c) {
let ad = a[0] * a[0] + a[1] * a[1];
let bd = b[0] * b[0] + b[1] * b[1];
let cd = c[0] * c[0] + c[1] * c[1];
let D = 2 * (a[0] * (b[1] - c[1]) + b[0] * (c[1] - a[1]) + c[0] * (a[1] - b[1]));
return [
Math.floor(1/D * (ad * (b[1] - c[1]) + bd * (c[1] - a[1]) + cd * (a[1] - b[1]))),
Math.floor(1/D * (ad * (c[0] - b[0]) + bd * (a[0] - c[0]) + cd * (b[0] - a[0])))
Math.floor(1 / D * (ad * (b[1] - c[1]) + bd * (c[1] - a[1]) + cd * (a[1] - b[1]))),
Math.floor(1 / D * (ad * (c[0] - b[0]) + bd * (a[0] - c[0]) + cd * (b[0] - a[0])))
];
}
return Voronoi;
})));
}