| 1 | import Delaunator from "delaunator";
|
|---|
| 2 | import Path from "./path.js";
|
|---|
| 3 | import Polygon from "./polygon.js";
|
|---|
| 4 | import Voronoi from "./voronoi.js";
|
|---|
| 5 |
|
|---|
| 6 | const tau = 2 * Math.PI, pow = Math.pow;
|
|---|
| 7 |
|
|---|
| 8 | function pointX(p) {
|
|---|
| 9 | return p[0];
|
|---|
| 10 | }
|
|---|
| 11 |
|
|---|
| 12 | function pointY(p) {
|
|---|
| 13 | return p[1];
|
|---|
| 14 | }
|
|---|
| 15 |
|
|---|
| 16 | // A triangulation is collinear if all its triangles have a non-null area
|
|---|
| 17 | function collinear(d) {
|
|---|
| 18 | const {triangles, coords} = d;
|
|---|
| 19 | for (let i = 0; i < triangles.length; i += 3) {
|
|---|
| 20 | const a = 2 * triangles[i],
|
|---|
| 21 | b = 2 * triangles[i + 1],
|
|---|
| 22 | c = 2 * triangles[i + 2],
|
|---|
| 23 | cross = (coords[c] - coords[a]) * (coords[b + 1] - coords[a + 1])
|
|---|
| 24 | - (coords[b] - coords[a]) * (coords[c + 1] - coords[a + 1]);
|
|---|
| 25 | if (cross > 1e-10) return false;
|
|---|
| 26 | }
|
|---|
| 27 | return true;
|
|---|
| 28 | }
|
|---|
| 29 |
|
|---|
| 30 | function jitter(x, y, r) {
|
|---|
| 31 | return [x + Math.sin(x + y) * r, y + Math.cos(x - y) * r];
|
|---|
| 32 | }
|
|---|
| 33 |
|
|---|
| 34 | export default class Delaunay {
|
|---|
| 35 | static from(points, fx = pointX, fy = pointY, that) {
|
|---|
| 36 | return new Delaunay("length" in points
|
|---|
| 37 | ? flatArray(points, fx, fy, that)
|
|---|
| 38 | : Float64Array.from(flatIterable(points, fx, fy, that)));
|
|---|
| 39 | }
|
|---|
| 40 | constructor(points) {
|
|---|
| 41 | this._delaunator = new Delaunator(points);
|
|---|
| 42 | this.inedges = new Int32Array(points.length / 2);
|
|---|
| 43 | this._hullIndex = new Int32Array(points.length / 2);
|
|---|
| 44 | this.points = this._delaunator.coords;
|
|---|
| 45 | this._init();
|
|---|
| 46 | }
|
|---|
| 47 | update() {
|
|---|
| 48 | this._delaunator.update();
|
|---|
| 49 | this._init();
|
|---|
| 50 | return this;
|
|---|
| 51 | }
|
|---|
| 52 | _init() {
|
|---|
| 53 | const d = this._delaunator, points = this.points;
|
|---|
| 54 |
|
|---|
| 55 | // check for collinear
|
|---|
| 56 | if (d.hull && d.hull.length > 2 && collinear(d)) {
|
|---|
| 57 | this.collinear = Int32Array.from({length: points.length/2}, (_,i) => i)
|
|---|
| 58 | .sort((i, j) => points[2 * i] - points[2 * j] || points[2 * i + 1] - points[2 * j + 1]); // for exact neighbors
|
|---|
| 59 | const e = this.collinear[0], f = this.collinear[this.collinear.length - 1],
|
|---|
| 60 | bounds = [ points[2 * e], points[2 * e + 1], points[2 * f], points[2 * f + 1] ],
|
|---|
| 61 | r = 1e-8 * Math.hypot(bounds[3] - bounds[1], bounds[2] - bounds[0]);
|
|---|
| 62 | for (let i = 0, n = points.length / 2; i < n; ++i) {
|
|---|
| 63 | const p = jitter(points[2 * i], points[2 * i + 1], r);
|
|---|
| 64 | points[2 * i] = p[0];
|
|---|
| 65 | points[2 * i + 1] = p[1];
|
|---|
| 66 | }
|
|---|
| 67 | this._delaunator = new Delaunator(points);
|
|---|
| 68 | } else {
|
|---|
| 69 | delete this.collinear;
|
|---|
| 70 | }
|
|---|
| 71 |
|
|---|
| 72 | const halfedges = this.halfedges = this._delaunator.halfedges;
|
|---|
| 73 | const hull = this.hull = this._delaunator.hull;
|
|---|
| 74 | const triangles = this.triangles = this._delaunator.triangles;
|
|---|
| 75 | const inedges = this.inedges.fill(-1);
|
|---|
| 76 | const hullIndex = this._hullIndex.fill(-1);
|
|---|
| 77 |
|
|---|
| 78 | // Compute an index from each point to an (arbitrary) incoming halfedge
|
|---|
| 79 | // Used to give the first neighbor of each point; for this reason,
|
|---|
| 80 | // on the hull we give priority to exterior halfedges
|
|---|
| 81 | for (let e = 0, n = halfedges.length; e < n; ++e) {
|
|---|
| 82 | const p = triangles[e % 3 === 2 ? e - 2 : e + 1];
|
|---|
| 83 | if (halfedges[e] === -1 || inedges[p] === -1) inedges[p] = e;
|
|---|
| 84 | }
|
|---|
| 85 | for (let i = 0, n = hull.length; i < n; ++i) {
|
|---|
| 86 | hullIndex[hull[i]] = i;
|
|---|
| 87 | }
|
|---|
| 88 |
|
|---|
| 89 | // degenerate case: 1 or 2 (distinct) points
|
|---|
| 90 | if (hull.length <= 2 && hull.length > 0) {
|
|---|
| 91 | this.triangles = new Int32Array(3).fill(-1);
|
|---|
| 92 | this.halfedges = new Int32Array(3).fill(-1);
|
|---|
| 93 | this.triangles[0] = hull[0];
|
|---|
| 94 | inedges[hull[0]] = 1;
|
|---|
| 95 | if (hull.length === 2) {
|
|---|
| 96 | inedges[hull[1]] = 0;
|
|---|
| 97 | this.triangles[1] = hull[1];
|
|---|
| 98 | this.triangles[2] = hull[1];
|
|---|
| 99 | }
|
|---|
| 100 | }
|
|---|
| 101 | }
|
|---|
| 102 | voronoi(bounds) {
|
|---|
| 103 | return new Voronoi(this, bounds);
|
|---|
| 104 | }
|
|---|
| 105 | *neighbors(i) {
|
|---|
| 106 | const {inedges, hull, _hullIndex, halfedges, triangles, collinear} = this;
|
|---|
| 107 |
|
|---|
| 108 | // degenerate case with several collinear points
|
|---|
| 109 | if (collinear) {
|
|---|
| 110 | const l = collinear.indexOf(i);
|
|---|
| 111 | if (l > 0) yield collinear[l - 1];
|
|---|
| 112 | if (l < collinear.length - 1) yield collinear[l + 1];
|
|---|
| 113 | return;
|
|---|
| 114 | }
|
|---|
| 115 |
|
|---|
| 116 | const e0 = inedges[i];
|
|---|
| 117 | if (e0 === -1) return; // coincident point
|
|---|
| 118 | let e = e0, p0 = -1;
|
|---|
| 119 | do {
|
|---|
| 120 | yield p0 = triangles[e];
|
|---|
| 121 | e = e % 3 === 2 ? e - 2 : e + 1;
|
|---|
| 122 | if (triangles[e] !== i) return; // bad triangulation
|
|---|
| 123 | e = halfedges[e];
|
|---|
| 124 | if (e === -1) {
|
|---|
| 125 | const p = hull[(_hullIndex[i] + 1) % hull.length];
|
|---|
| 126 | if (p !== p0) yield p;
|
|---|
| 127 | return;
|
|---|
| 128 | }
|
|---|
| 129 | } while (e !== e0);
|
|---|
| 130 | }
|
|---|
| 131 | find(x, y, i = 0) {
|
|---|
| 132 | if ((x = +x, x !== x) || (y = +y, y !== y)) return -1;
|
|---|
| 133 | const i0 = i;
|
|---|
| 134 | let c;
|
|---|
| 135 | while ((c = this._step(i, x, y)) >= 0 && c !== i && c !== i0) i = c;
|
|---|
| 136 | return c;
|
|---|
| 137 | }
|
|---|
| 138 | _step(i, x, y) {
|
|---|
| 139 | const {inedges, hull, _hullIndex, halfedges, triangles, points} = this;
|
|---|
| 140 | if (inedges[i] === -1 || !points.length) return (i + 1) % (points.length >> 1);
|
|---|
| 141 | let c = i;
|
|---|
| 142 | let dc = pow(x - points[i * 2], 2) + pow(y - points[i * 2 + 1], 2);
|
|---|
| 143 | const e0 = inedges[i];
|
|---|
| 144 | let e = e0;
|
|---|
| 145 | do {
|
|---|
| 146 | let t = triangles[e];
|
|---|
| 147 | const dt = pow(x - points[t * 2], 2) + pow(y - points[t * 2 + 1], 2);
|
|---|
| 148 | if (dt < dc) dc = dt, c = t;
|
|---|
| 149 | e = e % 3 === 2 ? e - 2 : e + 1;
|
|---|
| 150 | if (triangles[e] !== i) break; // bad triangulation
|
|---|
| 151 | e = halfedges[e];
|
|---|
| 152 | if (e === -1) {
|
|---|
| 153 | e = hull[(_hullIndex[i] + 1) % hull.length];
|
|---|
| 154 | if (e !== t) {
|
|---|
| 155 | if (pow(x - points[e * 2], 2) + pow(y - points[e * 2 + 1], 2) < dc) return e;
|
|---|
| 156 | }
|
|---|
| 157 | break;
|
|---|
| 158 | }
|
|---|
| 159 | } while (e !== e0);
|
|---|
| 160 | return c;
|
|---|
| 161 | }
|
|---|
| 162 | render(context) {
|
|---|
| 163 | const buffer = context == null ? context = new Path : undefined;
|
|---|
| 164 | const {points, halfedges, triangles} = this;
|
|---|
| 165 | for (let i = 0, n = halfedges.length; i < n; ++i) {
|
|---|
| 166 | const j = halfedges[i];
|
|---|
| 167 | if (j < i) continue;
|
|---|
| 168 | const ti = triangles[i] * 2;
|
|---|
| 169 | const tj = triangles[j] * 2;
|
|---|
| 170 | context.moveTo(points[ti], points[ti + 1]);
|
|---|
| 171 | context.lineTo(points[tj], points[tj + 1]);
|
|---|
| 172 | }
|
|---|
| 173 | this.renderHull(context);
|
|---|
| 174 | return buffer && buffer.value();
|
|---|
| 175 | }
|
|---|
| 176 | renderPoints(context, r) {
|
|---|
| 177 | if (r === undefined && (!context || typeof context.moveTo !== "function")) r = context, context = null;
|
|---|
| 178 | r = r == undefined ? 2 : +r;
|
|---|
| 179 | const buffer = context == null ? context = new Path : undefined;
|
|---|
| 180 | const {points} = this;
|
|---|
| 181 | for (let i = 0, n = points.length; i < n; i += 2) {
|
|---|
| 182 | const x = points[i], y = points[i + 1];
|
|---|
| 183 | context.moveTo(x + r, y);
|
|---|
| 184 | context.arc(x, y, r, 0, tau);
|
|---|
| 185 | }
|
|---|
| 186 | return buffer && buffer.value();
|
|---|
| 187 | }
|
|---|
| 188 | renderHull(context) {
|
|---|
| 189 | const buffer = context == null ? context = new Path : undefined;
|
|---|
| 190 | const {hull, points} = this;
|
|---|
| 191 | const h = hull[0] * 2, n = hull.length;
|
|---|
| 192 | context.moveTo(points[h], points[h + 1]);
|
|---|
| 193 | for (let i = 1; i < n; ++i) {
|
|---|
| 194 | const h = 2 * hull[i];
|
|---|
| 195 | context.lineTo(points[h], points[h + 1]);
|
|---|
| 196 | }
|
|---|
| 197 | context.closePath();
|
|---|
| 198 | return buffer && buffer.value();
|
|---|
| 199 | }
|
|---|
| 200 | hullPolygon() {
|
|---|
| 201 | const polygon = new Polygon;
|
|---|
| 202 | this.renderHull(polygon);
|
|---|
| 203 | return polygon.value();
|
|---|
| 204 | }
|
|---|
| 205 | renderTriangle(i, context) {
|
|---|
| 206 | const buffer = context == null ? context = new Path : undefined;
|
|---|
| 207 | const {points, triangles} = this;
|
|---|
| 208 | const t0 = triangles[i *= 3] * 2;
|
|---|
| 209 | const t1 = triangles[i + 1] * 2;
|
|---|
| 210 | const t2 = triangles[i + 2] * 2;
|
|---|
| 211 | context.moveTo(points[t0], points[t0 + 1]);
|
|---|
| 212 | context.lineTo(points[t1], points[t1 + 1]);
|
|---|
| 213 | context.lineTo(points[t2], points[t2 + 1]);
|
|---|
| 214 | context.closePath();
|
|---|
| 215 | return buffer && buffer.value();
|
|---|
| 216 | }
|
|---|
| 217 | *trianglePolygons() {
|
|---|
| 218 | const {triangles} = this;
|
|---|
| 219 | for (let i = 0, n = triangles.length / 3; i < n; ++i) {
|
|---|
| 220 | yield this.trianglePolygon(i);
|
|---|
| 221 | }
|
|---|
| 222 | }
|
|---|
| 223 | trianglePolygon(i) {
|
|---|
| 224 | const polygon = new Polygon;
|
|---|
| 225 | this.renderTriangle(i, polygon);
|
|---|
| 226 | return polygon.value();
|
|---|
| 227 | }
|
|---|
| 228 | }
|
|---|
| 229 |
|
|---|
| 230 | function flatArray(points, fx, fy, that) {
|
|---|
| 231 | const n = points.length;
|
|---|
| 232 | const array = new Float64Array(n * 2);
|
|---|
| 233 | for (let i = 0; i < n; ++i) {
|
|---|
| 234 | const p = points[i];
|
|---|
| 235 | array[i * 2] = fx.call(that, p, i, points);
|
|---|
| 236 | array[i * 2 + 1] = fy.call(that, p, i, points);
|
|---|
| 237 | }
|
|---|
| 238 | return array;
|
|---|
| 239 | }
|
|---|
| 240 |
|
|---|
| 241 | function* flatIterable(points, fx, fy, that) {
|
|---|
| 242 | let i = 0;
|
|---|
| 243 | for (const p of points) {
|
|---|
| 244 | yield fx.call(that, p, i, points);
|
|---|
| 245 | yield fy.call(that, p, i, points);
|
|---|
| 246 | ++i;
|
|---|
| 247 | }
|
|---|
| 248 | }
|
|---|