UNPKG

@xtor/cga.js

Version:

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

419 lines (418 loc) 17.2 kB
"use strict"; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Voronoi = void 0; var epsilon = 1e-6; var Polygon = /** @class */ (function () { function Polygon() { this._ = []; } Polygon.prototype.moveTo = function (x, y) { this._.push([x, y]); }; Polygon.prototype.closePath = function () { this._.push(this._[0].slice()); }; Polygon.prototype.lineTo = function (x, y) { this._.push([x, y]); }; Polygon.prototype.value = function () { return this._.length ? this._ : null; }; return Polygon; }()); var Voronoi = /** @class */ (function () { function Voronoi(delaunay, _a) { var _b = _a === void 0 ? [0, 0, 960, 500] : _a, xmin = _b[0], ymin = _b[1], xmax = _b[2], ymax = _b[3]; if (!((xmax = +xmax) >= (xmin = +xmin)) || !((ymax = +ymax) >= (ymin = +ymin))) throw new Error("invalid bounds"); this.delaunay = delaunay; this._circumcenters = new Float64Array(delaunay.points.length); this.vectors = new Float64Array(delaunay.points.length); this.xmax = xmax, this.xmin = xmin; this.ymax = ymax, this.ymin = ymin; this._init(); } Voronoi.prototype.update = function () { this.delaunay.update(); this._init(); return this; }; Voronoi.prototype._init = function () { var _a = this, _b = _a.delaunay, points = _b.points, hull = _b.hull, triangles = _b.triangles, vectors = _a.vectors; // Compute circumcenters. var circumcenters = this.circumcenters = this._circumcenters.subarray(0, triangles.length / 3 * 2); for (var i = 0, j = 0, n = triangles.length, x = void 0, y = void 0; i < n; i += 3, j += 2) { var t1 = triangles[i] * 2; var t2 = triangles[i + 1] * 2; var t3 = triangles[i + 2] * 2; var x1_1 = points[t1]; var y1_1 = points[t1 + 1]; var x2 = points[t2]; var y2 = points[t2 + 1]; var x3 = points[t3]; var y3 = points[t3 + 1]; var dx = x2 - x1_1; var dy = y2 - y1_1; var ex = x3 - x1_1; var ey = y3 - y1_1; var bl = dx * dx + dy * dy; var cl = ex * ex + ey * ey; var ab = (dx * ey - dy * ex) * 2; if (!ab) { // degenerate case (collinear diagram) x = (x1_1 + x3) / 2 - 1e8 * ey; y = (y1_1 + y3) / 2 + 1e8 * ex; } else if (Math.abs(ab) < 1e-8) { // almost equal points (degenerate triangle) x = (x1_1 + x3) / 2; y = (y1_1 + y3) / 2; } else { var d = 1 / ab; x = x1_1 + (ey * bl - dy * cl) * d; y = y1_1 + (dx * cl - ex * bl) * d; } circumcenters[j] = x; circumcenters[j + 1] = y; } // Compute exterior cell rays. var h = hull[hull.length - 1]; var p0, p1 = h * 4; var x0, x1 = points[2 * h]; var y0, y1 = points[2 * h + 1]; vectors.fill(0); for (var i = 0; i < hull.length; ++i) { h = hull[i]; p0 = p1, x0 = x1, y0 = y1; p1 = h * 4, x1 = points[2 * h], y1 = points[2 * h + 1]; vectors[p0 + 2] = vectors[p1] = y0 - y1; vectors[p0 + 3] = vectors[p1 + 1] = x1 - x0; } }; Voronoi.prototype.cellPolygons = function () { var points, i, n, cell; return __generator(this, function (_a) { switch (_a.label) { case 0: points = this.delaunay.points; i = 0, n = points.length / 2; _a.label = 1; case 1: if (!(i < n)) return [3 /*break*/, 4]; cell = this.cellPolygon(i); if (!cell) return [3 /*break*/, 3]; cell.index = i; return [4 /*yield*/, cell]; case 2: _a.sent(); _a.label = 3; case 3: ++i; return [3 /*break*/, 1]; case 4: return [2 /*return*/]; } }); }; Voronoi.prototype.cellPolygon = function (i) { var polygon = new Polygon; return polygon.value(); }; Voronoi.prototype._renderSegment = function (x0, y0, x1, y1, context) { var S; var c0 = this._regioncode(x0, y0); var c1 = this._regioncode(x1, y1); if (c0 === 0 && c1 === 0) { context.moveTo(x0, y0); context.lineTo(x1, y1); } else if (S = this._clipSegment(x0, y0, x1, y1, c0, c1)) { context.moveTo(S[0], S[1]); context.lineTo(S[2], S[3]); } }; Voronoi.prototype.contains = function (i, x, y) { if ((x = +x, x !== x) || (y = +y, y !== y)) return false; return this.delaunay._step(i, x, y) === i; }; Voronoi.prototype.neighbors = function (i) { var ci, _i, _a, j, cj, ai, li, aj, lj; return __generator(this, function (_b) { switch (_b.label) { case 0: ci = this._clip(i); if (!ci) return [3 /*break*/, 8]; _i = 0, _a = this.delaunay.neighbors(i); _b.label = 1; case 1: if (!(_i < _a.length)) return [3 /*break*/, 8]; j = _a[_i]; cj = this._clip(j); if (!cj) return [3 /*break*/, 7]; ai = 0, li = ci.length; _b.label = 2; case 2: if (!(ai < li)) return [3 /*break*/, 7]; aj = 0, lj = cj.length; _b.label = 3; case 3: if (!(aj < lj)) return [3 /*break*/, 6]; if (!(ci[ai] == cj[aj] && ci[ai + 1] == cj[aj + 1] && ci[(ai + 2) % li] == cj[(aj + lj - 2) % lj] && ci[(ai + 3) % li] == cj[(aj + lj - 1) % lj])) return [3 /*break*/, 5]; return [4 /*yield*/, j]; case 4: _b.sent(); return [3 /*break*/, 7]; case 5: aj += 2; return [3 /*break*/, 3]; case 6: ai += 2; return [3 /*break*/, 2]; case 7: _i++; return [3 /*break*/, 1]; case 8: return [2 /*return*/]; } }); }; Voronoi.prototype._cell = function (i) { var _a = this, circumcenters = _a.circumcenters, _b = _a.delaunay, inedges = _b.inedges, halfedges = _b.halfedges, triangles = _b.triangles; var e0 = inedges[i]; if (e0 === -1) return null; // coincident point var points = []; var e = e0; do { var t = Math.floor(e / 3); points.push(circumcenters[t * 2], circumcenters[t * 2 + 1]); e = e % 3 === 2 ? e - 2 : e + 1; if (triangles[e] !== i) break; // bad triangulation e = halfedges[e]; } while (e !== e0 && e !== -1); return points; }; Voronoi.prototype._clip = function (i) { // degenerate case (1 valid point: return the box) if (i === 0 && this.delaunay.hull.length === 1) { return [this.xmax, this.ymin, this.xmax, this.ymax, this.xmin, this.ymax, this.xmin, this.ymin]; } var points = this._cell(i); if (points === null) return null; var V = this.vectors; var v = i * 4; return V[v] || V[v + 1] ? this._clipInfinite(i, points, V[v], V[v + 1], V[v + 2], V[v + 3]) : this._clipFinite(i, points); }; Voronoi.prototype._clipFinite = function (i, points) { var n = points.length; var P = null; var x0, y0, x1 = points[n - 2], y1 = points[n - 1]; var c0, c1 = this._regioncode(x1, y1); var e0, e1; for (var j = 0; j < n; j += 2) { x0 = x1, y0 = y1, x1 = points[j], y1 = points[j + 1]; c0 = c1, c1 = this._regioncode(x1, y1); if (c0 === 0 && c1 === 0) { e0 = e1, e1 = 0; if (P) P.push(x1, y1); else P = [x1, y1]; } else { var S = void 0, sx0 = void 0, sy0 = void 0, sx1 = void 0, sy1 = void 0; if (c0 === 0) { if ((S = this._clipSegment(x0, y0, x1, y1, c0, c1)) === null) continue; sx0 = S[0], sy0 = S[1], sx1 = S[2], sy1 = S[3]; } else { if ((S = this._clipSegment(x1, y1, x0, y0, c1, c0)) === null) continue; sx1 = S[0], sy1 = S[1], sx0 = S[2], sy0 = S[3]; e0 = e1, e1 = this._edgecode(sx0, sy0); if (e0 && e1) this._edge(i, e0, e1, P, P.length); if (P) P.push(sx0, sy0); else P = [sx0, sy0]; } e0 = e1, e1 = this._edgecode(sx1, sy1); if (e0 && e1) this._edge(i, e0, e1, P, P.length); if (P) P.push(sx1, sy1); else P = [sx1, sy1]; } } if (P) { e0 = e1, e1 = this._edgecode(P[0], P[1]); if (e0 && e1) this._edge(i, e0, e1, P, P.length); } else if (this.contains(i, (this.xmin + this.xmax) / 2, (this.ymin + this.ymax) / 2)) { return [this.xmax, this.ymin, this.xmax, this.ymax, this.xmin, this.ymax, this.xmin, this.ymin]; } return P; }; Voronoi.prototype._clipSegment = function (x0, y0, x1, y1, c0, c1) { while (true) { if (c0 === 0 && c1 === 0) return [x0, y0, x1, y1]; if (c0 & c1) return null; var x = void 0, y = void 0, c = c0 || c1; if (c & 8) x = x0 + (x1 - x0) * (this.ymax - y0) / (y1 - y0), y = this.ymax; else if (c & 4) x = x0 + (x1 - x0) * (this.ymin - y0) / (y1 - y0), y = this.ymin; else if (c & 2) y = y0 + (y1 - y0) * (this.xmax - x0) / (x1 - x0), x = this.xmax; else y = y0 + (y1 - y0) * (this.xmin - x0) / (x1 - x0), x = this.xmin; if (c0) x0 = x, y0 = y, c0 = this._regioncode(x0, y0); else x1 = x, y1 = y, c1 = this._regioncode(x1, y1); } }; Voronoi.prototype._clipInfinite = function (i, points, vx0, vy0, vxn, vyn) { var P = Array.from(points), p; if (p = this._project(P[0], P[1], vx0, vy0)) P.unshift(p[0], p[1]); if (p = this._project(P[P.length - 2], P[P.length - 1], vxn, vyn)) P.push(p[0], p[1]); if (P = this._clipFinite(i, P)) { for (var j = 0, n = P.length, c0 = void 0, c1 = this._edgecode(P[n - 2], P[n - 1]); j < n; j += 2) { c0 = c1, c1 = this._edgecode(P[j], P[j + 1]); if (c0 && c1) j = this._edge(i, c0, c1, P, j), n = P.length; } } else if (this.contains(i, (this.xmin + this.xmax) / 2, (this.ymin + this.ymax) / 2)) { P = [this.xmin, this.ymin, this.xmax, this.ymin, this.xmax, this.ymax, this.xmin, this.ymax]; } return P; }; Voronoi.prototype._edge = function (i, e0, e1, P, j) { while (e0 !== e1) { var x = void 0, y = void 0; switch (e0) { case 5: e0 = 4; continue; // top-left case 4: e0 = 6, x = this.xmax, y = this.ymin; break; // top case 6: e0 = 2; continue; // top-right case 2: e0 = 10, x = this.xmax, y = this.ymax; break; // right case 10: e0 = 8; continue; // bottom-right case 8: e0 = 9, x = this.xmin, y = this.ymax; break; // bottom case 9: e0 = 1; continue; // bottom-left case 1: e0 = 5, x = this.xmin, y = this.ymin; break; // left } if ((P[j] !== x || P[j + 1] !== y) && this.contains(i, x, y)) { P.splice(j, 0, x, y), j += 2; } } if (P.length > 4) { for (var i_1 = 0; i_1 < P.length; i_1 += 2) { var j_1 = (i_1 + 2) % P.length, k = (i_1 + 4) % P.length; if (P[i_1] === P[j_1] && P[j_1] === P[k] || P[i_1 + 1] === P[j_1 + 1] && P[j_1 + 1] === P[k + 1]) P.splice(j_1, 2), i_1 -= 2; } } return j; }; Voronoi.prototype._project = function (x0, y0, vx, vy) { var t = Infinity, c, x, y; if (vy < 0) { // top if (y0 <= this.ymin) return null; if ((c = (this.ymin - y0) / vy) < t) y = this.ymin, x = x0 + (t = c) * vx; } else if (vy > 0) { // bottom if (y0 >= this.ymax) return null; if ((c = (this.ymax - y0) / vy) < t) y = this.ymax, x = x0 + (t = c) * vx; } if (vx > 0) { // right if (x0 >= this.xmax) return null; if ((c = (this.xmax - x0) / vx) < t) x = this.xmax, y = y0 + (t = c) * vy; } else if (vx < 0) { // left if (x0 <= this.xmin) return null; if ((c = (this.xmin - x0) / vx) < t) x = this.xmin, y = y0 + (t = c) * vy; } return [x, y]; }; Voronoi.prototype._edgecode = function (x, y) { return (x === this.xmin ? 1 : x === this.xmax ? 2 : 0) | (y === this.ymin ? 4 : y === this.ymax ? 8 : 0); }; Voronoi.prototype._regioncode = function (x, y) { return (x < this.xmin ? 1 : x > this.xmax ? 2 : 0) | (y < this.ymin ? 4 : y > this.ymax ? 8 : 0); }; return Voronoi; }()); exports.Voronoi = Voronoi;