@xtor/cga.js
Version:
Xtor Compute Geometry Algorithm Libary 计算几何算法库
419 lines (418 loc) • 17.2 kB
JavaScript
"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;