|
import Delaunator from "delaunator"; |
|
import Path from "./path.js"; |
|
import Polygon from "./polygon.js"; |
|
import Voronoi from "./voronoi.js"; |
|
|
|
const tau = 2 * Math.PI, pow = Math.pow; |
|
|
|
function pointX(p) { |
|
return p[0]; |
|
} |
|
|
|
function pointY(p) { |
|
return p[1]; |
|
} |
|
|
|
|
|
function collinear(d) { |
|
const {triangles, coords} = d; |
|
for (let i = 0; i < triangles.length; i += 3) { |
|
const a = 2 * triangles[i], |
|
b = 2 * triangles[i + 1], |
|
c = 2 * triangles[i + 2], |
|
cross = (coords[c] - coords[a]) * (coords[b + 1] - coords[a + 1]) |
|
- (coords[b] - coords[a]) * (coords[c + 1] - coords[a + 1]); |
|
if (cross > 1e-10) return false; |
|
} |
|
return true; |
|
} |
|
|
|
function jitter(x, y, r) { |
|
return [x + Math.sin(x + y) * r, y + Math.cos(x - y) * r]; |
|
} |
|
|
|
export default class Delaunay { |
|
static from(points, fx = pointX, fy = pointY, that) { |
|
return new Delaunay("length" in points |
|
? flatArray(points, fx, fy, that) |
|
: Float64Array.from(flatIterable(points, fx, fy, that))); |
|
} |
|
constructor(points) { |
|
this._delaunator = new Delaunator(points); |
|
this.inedges = new Int32Array(points.length / 2); |
|
this._hullIndex = new Int32Array(points.length / 2); |
|
this.points = this._delaunator.coords; |
|
this._init(); |
|
} |
|
update() { |
|
this._delaunator.update(); |
|
this._init(); |
|
return this; |
|
} |
|
_init() { |
|
const d = this._delaunator, points = this.points; |
|
|
|
|
|
if (d.hull && d.hull.length > 2 && collinear(d)) { |
|
this.collinear = Int32Array.from({length: points.length/2}, (_,i) => i) |
|
.sort((i, j) => points[2 * i] - points[2 * j] || points[2 * i + 1] - points[2 * j + 1]); |
|
const e = this.collinear[0], f = this.collinear[this.collinear.length - 1], |
|
bounds = [ points[2 * e], points[2 * e + 1], points[2 * f], points[2 * f + 1] ], |
|
r = 1e-8 * Math.hypot(bounds[3] - bounds[1], bounds[2] - bounds[0]); |
|
for (let i = 0, n = points.length / 2; i < n; ++i) { |
|
const p = jitter(points[2 * i], points[2 * i + 1], r); |
|
points[2 * i] = p[0]; |
|
points[2 * i + 1] = p[1]; |
|
} |
|
this._delaunator = new Delaunator(points); |
|
} else { |
|
delete this.collinear; |
|
} |
|
|
|
const halfedges = this.halfedges = this._delaunator.halfedges; |
|
const hull = this.hull = this._delaunator.hull; |
|
const triangles = this.triangles = this._delaunator.triangles; |
|
const inedges = this.inedges.fill(-1); |
|
const hullIndex = this._hullIndex.fill(-1); |
|
|
|
|
|
|
|
|
|
for (let e = 0, n = halfedges.length; e < n; ++e) { |
|
const p = triangles[e % 3 === 2 ? e - 2 : e + 1]; |
|
if (halfedges[e] === -1 || inedges[p] === -1) inedges[p] = e; |
|
} |
|
for (let i = 0, n = hull.length; i < n; ++i) { |
|
hullIndex[hull[i]] = i; |
|
} |
|
|
|
|
|
if (hull.length <= 2 && hull.length > 0) { |
|
this.triangles = new Int32Array(3).fill(-1); |
|
this.halfedges = new Int32Array(3).fill(-1); |
|
this.triangles[0] = hull[0]; |
|
inedges[hull[0]] = 1; |
|
if (hull.length === 2) { |
|
inedges[hull[1]] = 0; |
|
this.triangles[1] = hull[1]; |
|
this.triangles[2] = hull[1]; |
|
} |
|
} |
|
} |
|
voronoi(bounds) { |
|
return new Voronoi(this, bounds); |
|
} |
|
*neighbors(i) { |
|
const {inedges, hull, _hullIndex, halfedges, triangles, collinear} = this; |
|
|
|
|
|
if (collinear) { |
|
const l = collinear.indexOf(i); |
|
if (l > 0) yield collinear[l - 1]; |
|
if (l < collinear.length - 1) yield collinear[l + 1]; |
|
return; |
|
} |
|
|
|
const e0 = inedges[i]; |
|
if (e0 === -1) return; |
|
let e = e0, p0 = -1; |
|
do { |
|
yield p0 = triangles[e]; |
|
e = e % 3 === 2 ? e - 2 : e + 1; |
|
if (triangles[e] !== i) return; |
|
e = halfedges[e]; |
|
if (e === -1) { |
|
const p = hull[(_hullIndex[i] + 1) % hull.length]; |
|
if (p !== p0) yield p; |
|
return; |
|
} |
|
} while (e !== e0); |
|
} |
|
find(x, y, i = 0) { |
|
if ((x = +x, x !== x) || (y = +y, y !== y)) return -1; |
|
const i0 = i; |
|
let c; |
|
while ((c = this._step(i, x, y)) >= 0 && c !== i && c !== i0) i = c; |
|
return c; |
|
} |
|
_step(i, x, y) { |
|
const {inedges, hull, _hullIndex, halfedges, triangles, points} = this; |
|
if (inedges[i] === -1 || !points.length) return (i + 1) % (points.length >> 1); |
|
let c = i; |
|
let dc = pow(x - points[i * 2], 2) + pow(y - points[i * 2 + 1], 2); |
|
const e0 = inedges[i]; |
|
let e = e0; |
|
do { |
|
let t = triangles[e]; |
|
const dt = pow(x - points[t * 2], 2) + pow(y - points[t * 2 + 1], 2); |
|
if (dt < dc) dc = dt, c = t; |
|
e = e % 3 === 2 ? e - 2 : e + 1; |
|
if (triangles[e] !== i) break; |
|
e = halfedges[e]; |
|
if (e === -1) { |
|
e = hull[(_hullIndex[i] + 1) % hull.length]; |
|
if (e !== t) { |
|
if (pow(x - points[e * 2], 2) + pow(y - points[e * 2 + 1], 2) < dc) return e; |
|
} |
|
break; |
|
} |
|
} while (e !== e0); |
|
return c; |
|
} |
|
render(context) { |
|
const buffer = context == null ? context = new Path : undefined; |
|
const {points, halfedges, triangles} = this; |
|
for (let i = 0, n = halfedges.length; i < n; ++i) { |
|
const j = halfedges[i]; |
|
if (j < i) continue; |
|
const ti = triangles[i] * 2; |
|
const tj = triangles[j] * 2; |
|
context.moveTo(points[ti], points[ti + 1]); |
|
context.lineTo(points[tj], points[tj + 1]); |
|
} |
|
this.renderHull(context); |
|
return buffer && buffer.value(); |
|
} |
|
renderPoints(context, r) { |
|
if (r === undefined && (!context || typeof context.moveTo !== "function")) r = context, context = null; |
|
r = r == undefined ? 2 : +r; |
|
const buffer = context == null ? context = new Path : undefined; |
|
const {points} = this; |
|
for (let i = 0, n = points.length; i < n; i += 2) { |
|
const x = points[i], y = points[i + 1]; |
|
context.moveTo(x + r, y); |
|
context.arc(x, y, r, 0, tau); |
|
} |
|
return buffer && buffer.value(); |
|
} |
|
renderHull(context) { |
|
const buffer = context == null ? context = new Path : undefined; |
|
const {hull, points} = this; |
|
const h = hull[0] * 2, n = hull.length; |
|
context.moveTo(points[h], points[h + 1]); |
|
for (let i = 1; i < n; ++i) { |
|
const h = 2 * hull[i]; |
|
context.lineTo(points[h], points[h + 1]); |
|
} |
|
context.closePath(); |
|
return buffer && buffer.value(); |
|
} |
|
hullPolygon() { |
|
const polygon = new Polygon; |
|
this.renderHull(polygon); |
|
return polygon.value(); |
|
} |
|
renderTriangle(i, context) { |
|
const buffer = context == null ? context = new Path : undefined; |
|
const {points, triangles} = this; |
|
const t0 = triangles[i *= 3] * 2; |
|
const t1 = triangles[i + 1] * 2; |
|
const t2 = triangles[i + 2] * 2; |
|
context.moveTo(points[t0], points[t0 + 1]); |
|
context.lineTo(points[t1], points[t1 + 1]); |
|
context.lineTo(points[t2], points[t2 + 1]); |
|
context.closePath(); |
|
return buffer && buffer.value(); |
|
} |
|
*trianglePolygons() { |
|
const {triangles} = this; |
|
for (let i = 0, n = triangles.length / 3; i < n; ++i) { |
|
yield this.trianglePolygon(i); |
|
} |
|
} |
|
trianglePolygon(i) { |
|
const polygon = new Polygon; |
|
this.renderTriangle(i, polygon); |
|
return polygon.value(); |
|
} |
|
} |
|
|
|
function flatArray(points, fx, fy, that) { |
|
const n = points.length; |
|
const array = new Float64Array(n * 2); |
|
for (let i = 0; i < n; ++i) { |
|
const p = points[i]; |
|
array[i * 2] = fx.call(that, p, i, points); |
|
array[i * 2 + 1] = fy.call(that, p, i, points); |
|
} |
|
return array; |
|
} |
|
|
|
function* flatIterable(points, fx, fy, that) { |
|
let i = 0; |
|
for (const p of points) { |
|
yield fx.call(that, p, i, points); |
|
yield fy.call(that, p, i, points); |
|
++i; |
|
} |
|
} |
|
|