UNPKG

@xtor/cga.js

Version:

Xtor Compute Geometry Algorithm Libary 计算几何算法库

163 lines (146 loc) 6.17 kB
import { Delaunator } from "./delaunator"; import { Voronoi } from './voronoi'; const tau = 2 * Math.PI, pow = Math.pow; // A triangulation is collinear if all its triangles have a non-null area function collinear(d: any) { 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: number, y: number, r: number) { return [x + Math.sin(x + y) * r, y + Math.cos(x - y) * r]; } export default class Delaunay { _delaunator: Delaunator; inedges: Int32Array; _hullIndex: Int32Array; points: Float64Array; collinear?: Int32Array; halfedges: any; hull!: Uint32Array; triangles!: Uint32Array; static from(points: number[]) { return new Delaunay(new Float64Array(points)); } constructor(points: Float64Array) { 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; // check for collinear 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]); // for exact neighbors 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); // Compute an index from each point to an (arbitrary) incoming halfedge // Used to give the first neighbor of each point; for this reason, // on the hull we give priority to exterior halfedges 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; } // degenerate case: 1 or 2 (distinct) points if (hull.length <= 2 && hull.length > 0) { this.triangles = new Uint32Array(3).fill(-1); this.halfedges = new Uint32Array(3).fill(-1); this.triangles[0] = hull[0]; this.triangles[1] = hull[1]; this.triangles[2] = hull[1]; inedges[hull[0]] = 1; if (hull.length === 2) inedges[hull[1]] = 0; } } voronoi(bounds: [number, number, number, number] | undefined) { return new Voronoi(this, bounds); } *neighbors(i: number) { const { inedges, hull, _hullIndex, halfedges, triangles, collinear } = this; // degenerate case with several collinear points 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; // coincident point let e = e0, p0 = -1; do { yield p0 = triangles[e]; e = e % 3 === 2 ? e - 2 : e + 1; if (triangles[e] !== i) return; // bad triangulation 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:number, y:number, 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:number, x:number, y:number) { 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; // bad triangulation 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; } }