SourceTermAnalysisSystem_vue/node_modules/@antv/x6-geometry/lib/curve.js
2026-05-15 10:22:44 +08:00

693 lines
27 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Curve = void 0;
const line_1 = require("./line");
const point_1 = require("./point");
const polyline_1 = require("./polyline");
const rectangle_1 = require("./rectangle");
const geometry_1 = require("./geometry");
class Curve extends geometry_1.Geometry {
constructor(start, controlPoint1, controlPoint2, end) {
super();
this.PRECISION = 3;
this.start = point_1.Point.create(start);
this.controlPoint1 = point_1.Point.create(controlPoint1);
this.controlPoint2 = point_1.Point.create(controlPoint2);
this.end = point_1.Point.create(end);
}
bbox() {
const start = this.start;
const controlPoint1 = this.controlPoint1;
const controlPoint2 = this.controlPoint2;
const end = this.end;
const x0 = start.x;
const y0 = start.y;
const x1 = controlPoint1.x;
const y1 = controlPoint1.y;
const x2 = controlPoint2.x;
const y2 = controlPoint2.y;
const x3 = end.x;
const y3 = end.y;
const points = []; // local extremes
const tvalues = []; // t values of local extremes
const bounds = [[], []];
let a;
let b;
let c;
let t;
let t1;
let t2;
let b2ac;
let sqrtb2ac;
for (let i = 0; i < 2; i += 1) {
if (i === 0) {
b = 6 * x0 - 12 * x1 + 6 * x2;
a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
c = 3 * x1 - 3 * x0;
}
else {
b = 6 * y0 - 12 * y1 + 6 * y2;
a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
c = 3 * y1 - 3 * y0;
}
if (Math.abs(a) < 1e-12) {
if (Math.abs(b) < 1e-12) {
continue;
}
t = -c / b;
if (t > 0 && t < 1)
tvalues.push(t);
continue;
}
b2ac = b * b - 4 * c * a;
sqrtb2ac = Math.sqrt(b2ac);
if (b2ac < 0)
continue;
t1 = (-b + sqrtb2ac) / (2 * a);
if (t1 > 0 && t1 < 1)
tvalues.push(t1);
t2 = (-b - sqrtb2ac) / (2 * a);
if (t2 > 0 && t2 < 1)
tvalues.push(t2);
}
let x;
let y;
let mt;
let j = tvalues.length;
const jlen = j;
while (j) {
j -= 1;
t = tvalues[j];
mt = 1 - t;
x =
mt * mt * mt * x0 +
3 * mt * mt * t * x1 +
3 * mt * t * t * x2 +
t * t * t * x3;
bounds[0][j] = x;
y =
mt * mt * mt * y0 +
3 * mt * mt * t * y1 +
3 * mt * t * t * y2 +
t * t * t * y3;
bounds[1][j] = y;
points[j] = { X: x, Y: y };
}
tvalues[jlen] = 0;
tvalues[jlen + 1] = 1;
points[jlen] = { X: x0, Y: y0 };
points[jlen + 1] = { X: x3, Y: y3 };
bounds[0][jlen] = x0;
bounds[1][jlen] = y0;
bounds[0][jlen + 1] = x3;
bounds[1][jlen + 1] = y3;
tvalues.length = jlen + 2;
bounds[0].length = jlen + 2;
bounds[1].length = jlen + 2;
points.length = jlen + 2;
const left = Math.min.apply(null, bounds[0]);
const top = Math.min.apply(null, bounds[1]);
const right = Math.max.apply(null, bounds[0]);
const bottom = Math.max.apply(null, bounds[1]);
return new rectangle_1.Rectangle(left, top, right - left, bottom - top);
}
closestPoint(p, options = {}) {
return this.pointAtT(this.closestPointT(p, options));
}
closestPointLength(p, options = {}) {
const opts = this.getOptions(options);
return this.lengthAtT(this.closestPointT(p, opts), opts);
}
closestPointNormalizedLength(p, options = {}) {
const opts = this.getOptions(options);
const cpLength = this.closestPointLength(p, opts);
if (!cpLength) {
return 0;
}
const length = this.length(opts);
if (length === 0) {
return 0;
}
return cpLength / length;
}
closestPointT(p, options = {}) {
const precision = this.getPrecision(options);
const subdivisions = this.getDivisions(options);
const precisionRatio = Math.pow(10, -precision); // eslint-disable-line
let investigatedSubdivision = null;
let investigatedSubdivisionStartT = 0;
let investigatedSubdivisionEndT = 0;
let distFromStart = 0;
let distFromEnd = 0;
let chordLength = 0;
let minSumDist = null;
const count = subdivisions.length;
let piece = count > 0 ? 1 / count : 0;
subdivisions.forEach((division, i) => {
const startDist = division.start.distance(p);
const endDist = division.end.distance(p);
const sumDist = startDist + endDist;
if (minSumDist == null || sumDist < minSumDist) {
investigatedSubdivision = division;
investigatedSubdivisionStartT = i * piece;
investigatedSubdivisionEndT = (i + 1) * piece;
distFromStart = startDist;
distFromEnd = endDist;
minSumDist = sumDist;
chordLength = division.endpointDistance();
}
});
// Recursively divide investigated subdivision, until distance between
// baselinePoint and closest path endpoint is within `10^(-precision)`,
// then return the closest endpoint of that final subdivision.
// eslint-disable-next-line
while (true) {
// check if we have reached at least one required observed precision
// - calculated as: the difference in distances from point to start and end divided by the distance
// - note that this function is not monotonic = it doesn't converge stably but has "teeth"
// - the function decreases while one of the endpoints is fixed but "jumps" whenever we switch
// - this criterion works well for points lying far away from the curve
const startPrecisionRatio = distFromStart
? Math.abs(distFromStart - distFromEnd) / distFromStart
: 0;
const endPrecisionRatio = distFromEnd != null
? Math.abs(distFromStart - distFromEnd) / distFromEnd
: 0;
const hasRequiredPrecision = startPrecisionRatio < precisionRatio ||
endPrecisionRatio < precisionRatio;
// check if we have reached at least one required minimal distance
// - calculated as: the subdivision chord length multiplied by precisionRatio
// - calculation is relative so it will work for arbitrarily large/small curves and their subdivisions
// - this is a backup criterion that works well for points lying "almost at" the curve
const hasMiniStartDistance = distFromStart
? distFromStart < chordLength * precisionRatio
: true;
const hasMiniEndDistance = distFromEnd
? distFromEnd < chordLength * precisionRatio
: true;
const hasMiniDistance = hasMiniStartDistance || hasMiniEndDistance;
if (hasRequiredPrecision || hasMiniDistance) {
return distFromStart <= distFromEnd
? investigatedSubdivisionStartT
: investigatedSubdivisionEndT;
}
// otherwise, set up for next iteration
const divided = investigatedSubdivision.divide(0.5);
piece /= 2;
const startDist1 = divided[0].start.distance(p);
const endDist1 = divided[0].end.distance(p);
const sumDist1 = startDist1 + endDist1;
const startDist2 = divided[1].start.distance(p);
const endDist2 = divided[1].end.distance(p);
const sumDist2 = startDist2 + endDist2;
if (sumDist1 <= sumDist2) {
investigatedSubdivision = divided[0];
investigatedSubdivisionEndT -= piece;
distFromStart = startDist1;
distFromEnd = endDist1;
}
else {
investigatedSubdivision = divided[1];
investigatedSubdivisionStartT += piece;
distFromStart = startDist2;
distFromEnd = endDist2;
}
}
}
closestPointTangent(p, options = {}) {
return this.tangentAtT(this.closestPointT(p, options));
}
containsPoint(p, options = {}) {
const polyline = this.toPolyline(options);
return polyline.containsPoint(p);
}
divideAt(ratio, options = {}) {
if (ratio <= 0) {
return this.divideAtT(0);
}
if (ratio >= 1) {
return this.divideAtT(1);
}
const t = this.tAt(ratio, options);
return this.divideAtT(t);
}
divideAtLength(length, options = {}) {
const t = this.tAtLength(length, options);
return this.divideAtT(t);
}
divide(t) {
return this.divideAtT(t);
}
divideAtT(t) {
const start = this.start;
const controlPoint1 = this.controlPoint1;
const controlPoint2 = this.controlPoint2;
const end = this.end;
if (t <= 0) {
return [
new Curve(start, start, start, start),
new Curve(start, controlPoint1, controlPoint2, end),
];
}
if (t >= 1) {
return [
new Curve(start, controlPoint1, controlPoint2, end),
new Curve(end, end, end, end),
];
}
const dividerPoints = this.getSkeletonPoints(t);
const startControl1 = dividerPoints.startControlPoint1;
const startControl2 = dividerPoints.startControlPoint2;
const divider = dividerPoints.divider;
const dividerControl1 = dividerPoints.dividerControlPoint1;
const dividerControl2 = dividerPoints.dividerControlPoint2;
return [
new Curve(start, startControl1, startControl2, divider),
new Curve(divider, dividerControl1, dividerControl2, end),
];
}
endpointDistance() {
return this.start.distance(this.end);
}
getSkeletonPoints(t) {
const start = this.start;
const control1 = this.controlPoint1;
const control2 = this.controlPoint2;
const end = this.end;
// shortcuts for `t` values that are out of range
if (t <= 0) {
return {
startControlPoint1: start.clone(),
startControlPoint2: start.clone(),
divider: start.clone(),
dividerControlPoint1: control1.clone(),
dividerControlPoint2: control2.clone(),
};
}
if (t >= 1) {
return {
startControlPoint1: control1.clone(),
startControlPoint2: control2.clone(),
divider: end.clone(),
dividerControlPoint1: end.clone(),
dividerControlPoint2: end.clone(),
};
}
const midpoint1 = new line_1.Line(start, control1).pointAt(t);
const midpoint2 = new line_1.Line(control1, control2).pointAt(t);
const midpoint3 = new line_1.Line(control2, end).pointAt(t);
const subControl1 = new line_1.Line(midpoint1, midpoint2).pointAt(t);
const subControl2 = new line_1.Line(midpoint2, midpoint3).pointAt(t);
const divideLine = new line_1.Line(subControl1, subControl2).pointAt(t);
return {
startControlPoint1: midpoint1,
startControlPoint2: subControl1,
divider: divideLine,
dividerControlPoint1: subControl2,
dividerControlPoint2: midpoint3,
};
}
getSubdivisions(options = {}) {
const precision = this.getPrecision(options);
let subdivisions = [
new Curve(this.start, this.controlPoint1, this.controlPoint2, this.end),
];
if (precision === 0) {
return subdivisions;
}
let previousLength = this.endpointDistance();
const precisionRatio = Math.pow(10, -precision); // eslint-disable-line
// Recursively divide curve at `t = 0.5`, until the difference between
// observed length at subsequent iterations is lower than precision.
let iteration = 0;
// eslint-disable-next-line
while (true) {
iteration += 1;
const divisions = [];
subdivisions.forEach((c) => {
// dividing at t = 0.5 (not at middle length!)
const divided = c.divide(0.5);
divisions.push(divided[0], divided[1]);
});
// measure new length
const length = divisions.reduce((memo, c) => memo + c.endpointDistance(), 0);
// check if we have reached required observed precision
// sine-like curves may have the same observed length in iteration 0 and 1 - skip iteration 1
// not a problem for further iterations because cubic curves cannot have more than two local extrema
// (i.e. cubic curves cannot intersect the baseline more than once)
// therefore two subsequent iterations cannot produce sampling with equal length
const ratio = length !== 0 ? (length - previousLength) / length : 0;
if (iteration > 1 && ratio < precisionRatio) {
return divisions;
}
subdivisions = divisions;
previousLength = length;
}
}
length(options = {}) {
const divisions = this.getDivisions(options);
return divisions.reduce((memo, c) => {
return memo + c.endpointDistance();
}, 0);
}
lengthAtT(t, options = {}) {
if (t <= 0) {
return 0;
}
const precision = options.precision === undefined ? this.PRECISION : options.precision;
const subCurve = this.divide(t)[0];
return subCurve.length({ precision });
}
pointAt(ratio, options = {}) {
if (ratio <= 0) {
return this.start.clone();
}
if (ratio >= 1) {
return this.end.clone();
}
const t = this.tAt(ratio, options);
return this.pointAtT(t);
}
pointAtLength(length, options = {}) {
const t = this.tAtLength(length, options);
return this.pointAtT(t);
}
pointAtT(t) {
if (t <= 0) {
return this.start.clone();
}
if (t >= 1) {
return this.end.clone();
}
return this.getSkeletonPoints(t).divider;
}
isDifferentiable() {
const start = this.start;
const control1 = this.controlPoint1;
const control2 = this.controlPoint2;
const end = this.end;
return !(start.equals(control1) &&
control1.equals(control2) &&
control2.equals(end));
}
tangentAt(ratio, options = {}) {
if (!this.isDifferentiable())
return null;
if (ratio < 0) {
ratio = 0; // eslint-disable-line
}
else if (ratio > 1) {
ratio = 1; // eslint-disable-line
}
const t = this.tAt(ratio, options);
return this.tangentAtT(t);
}
tangentAtLength(length, options = {}) {
if (!this.isDifferentiable()) {
return null;
}
const t = this.tAtLength(length, options);
return this.tangentAtT(t);
}
tangentAtT(t) {
if (!this.isDifferentiable()) {
return null;
}
if (t < 0) {
t = 0; // eslint-disable-line
}
if (t > 1) {
t = 1; // eslint-disable-line
}
const skeletonPoints = this.getSkeletonPoints(t);
const p1 = skeletonPoints.startControlPoint2;
const p2 = skeletonPoints.dividerControlPoint1;
const tangentStart = skeletonPoints.divider;
const tangentLine = new line_1.Line(p1, p2);
// move so that tangent line starts at the point requested
tangentLine.translate(tangentStart.x - p1.x, tangentStart.y - p1.y);
return tangentLine;
}
getPrecision(options = {}) {
return options.precision == null ? this.PRECISION : options.precision;
}
getDivisions(options = {}) {
if (options.subdivisions != null) {
return options.subdivisions;
}
const precision = this.getPrecision(options);
return this.getSubdivisions({ precision });
}
getOptions(options = {}) {
const precision = this.getPrecision(options);
const subdivisions = this.getDivisions(options);
return { precision, subdivisions };
}
tAt(ratio, options = {}) {
if (ratio <= 0) {
return 0;
}
if (ratio >= 1) {
return 1;
}
const opts = this.getOptions(options);
const total = this.length(opts);
const length = total * ratio;
return this.tAtLength(length, opts);
}
tAtLength(length, options = {}) {
let fromStart = true;
if (length < 0) {
fromStart = false;
length = -length; // eslint-disable-line
}
const precision = this.getPrecision(options);
const subdivisions = this.getDivisions(options);
const opts = { precision, subdivisions };
let investigatedSubdivision = null;
let investigatedSubdivisionStartT;
let investigatedSubdivisionEndT;
let baselinePointDistFromStart = 0;
let baselinePointDistFromEnd = 0;
let memo = 0;
const count = subdivisions.length;
let piece = count > 0 ? 1 / count : 0;
for (let i = 0; i < count; i += 1) {
const index = fromStart ? i : count - 1 - i;
const division = subdivisions[i];
const dist = division.endpointDistance();
if (length <= memo + dist) {
investigatedSubdivision = division;
investigatedSubdivisionStartT = index * piece;
investigatedSubdivisionEndT = (index + 1) * piece;
baselinePointDistFromStart = fromStart
? length - memo
: dist + memo - length;
baselinePointDistFromEnd = fromStart
? dist + memo - length
: length - memo;
break;
}
memo += dist;
}
if (investigatedSubdivision == null) {
return fromStart ? 1 : 0;
}
// note that precision affects what length is recorded
// (imprecise measurements underestimate length by up to 10^(-precision) of the precise length)
// e.g. at precision 1, the length may be underestimated by up to 10% and cause this function to return 1
const total = this.length(opts);
const precisionRatio = Math.pow(10, -precision); // eslint-disable-line
// recursively divide investigated subdivision:
// until distance between baselinePoint and closest path endpoint is within 10^(-precision)
// then return the closest endpoint of that final subdivision
// eslint-disable-next-line
while (true) {
let ratio;
ratio = total !== 0 ? baselinePointDistFromStart / total : 0;
if (ratio < precisionRatio) {
return investigatedSubdivisionStartT;
}
ratio = total !== 0 ? baselinePointDistFromEnd / total : 0;
if (ratio < precisionRatio) {
return investigatedSubdivisionEndT;
}
// otherwise, set up for next iteration
let newBaselinePointDistFromStart;
let newBaselinePointDistFromEnd;
const divided = investigatedSubdivision.divide(0.5);
piece /= 2;
const baseline1Length = divided[0].endpointDistance();
const baseline2Length = divided[1].endpointDistance();
if (baselinePointDistFromStart <= baseline1Length) {
investigatedSubdivision = divided[0];
investigatedSubdivisionEndT -= piece;
newBaselinePointDistFromStart = baselinePointDistFromStart;
newBaselinePointDistFromEnd =
baseline1Length - newBaselinePointDistFromStart;
}
else {
investigatedSubdivision = divided[1];
investigatedSubdivisionStartT += piece;
newBaselinePointDistFromStart =
baselinePointDistFromStart - baseline1Length;
newBaselinePointDistFromEnd =
baseline2Length - newBaselinePointDistFromStart;
}
baselinePointDistFromStart = newBaselinePointDistFromStart;
baselinePointDistFromEnd = newBaselinePointDistFromEnd;
}
}
toPoints(options = {}) {
const subdivisions = this.getDivisions(options);
const points = [subdivisions[0].start.clone()];
subdivisions.forEach((c) => points.push(c.end.clone()));
return points;
}
toPolyline(options = {}) {
return new polyline_1.Polyline(this.toPoints(options));
}
scale(sx, sy, origin) {
this.start.scale(sx, sy, origin);
this.controlPoint1.scale(sx, sy, origin);
this.controlPoint2.scale(sx, sy, origin);
this.end.scale(sx, sy, origin);
return this;
}
rotate(angle, origin) {
this.start.rotate(angle, origin);
this.controlPoint1.rotate(angle, origin);
this.controlPoint2.rotate(angle, origin);
this.end.rotate(angle, origin);
return this;
}
translate(tx, ty) {
if (typeof tx === 'number') {
this.start.translate(tx, ty);
this.controlPoint1.translate(tx, ty);
this.controlPoint2.translate(tx, ty);
this.end.translate(tx, ty);
}
else {
this.start.translate(tx);
this.controlPoint1.translate(tx);
this.controlPoint2.translate(tx);
this.end.translate(tx);
}
return this;
}
equals(c) {
return (c != null &&
this.start.equals(c.start) &&
this.controlPoint1.equals(c.controlPoint1) &&
this.controlPoint2.equals(c.controlPoint2) &&
this.end.equals(c.end));
}
clone() {
return new Curve(this.start, this.controlPoint1, this.controlPoint2, this.end);
}
toJSON() {
return {
start: this.start.toJSON(),
controlPoint1: this.controlPoint1.toJSON(),
controlPoint2: this.controlPoint2.toJSON(),
end: this.end.toJSON(),
};
}
serialize() {
return [
this.start.serialize(),
this.controlPoint1.serialize(),
this.controlPoint2.serialize(),
this.end.serialize(),
].join(' ');
}
}
exports.Curve = Curve;
(function (Curve) {
function isCurve(instance) {
return instance != null && instance instanceof Curve;
}
Curve.isCurve = isCurve;
})(Curve = exports.Curve || (exports.Curve = {}));
(function (Curve) {
function getFirstControlPoints(rhs) {
const n = rhs.length;
const x = []; // `x` is a solution vector.
const tmp = [];
let b = 2.0;
x[0] = rhs[0] / b;
// Decomposition and forward substitution.
for (let i = 1; i < n; i += 1) {
tmp[i] = 1 / b;
b = (i < n - 1 ? 4.0 : 3.5) - tmp[i];
x[i] = (rhs[i] - x[i - 1]) / b;
}
for (let i = 1; i < n; i += 1) {
// Backsubstitution.
x[n - i - 1] -= tmp[n - i] * x[n - i];
}
return x;
}
function getCurveControlPoints(points) {
const knots = points.map((p) => point_1.Point.clone(p));
const firstControlPoints = [];
const secondControlPoints = [];
const n = knots.length - 1;
// Special case: Bezier curve should be a straight line.
if (n === 1) {
// 3P1 = 2P0 + P3
firstControlPoints[0] = new point_1.Point((2 * knots[0].x + knots[1].x) / 3, (2 * knots[0].y + knots[1].y) / 3);
// P2 = 2P1 P0
secondControlPoints[0] = new point_1.Point(2 * firstControlPoints[0].x - knots[0].x, 2 * firstControlPoints[0].y - knots[0].y);
return [firstControlPoints, secondControlPoints];
}
// Calculate first Bezier control points.
// Right hand side vector.
const rhs = [];
// Set right hand side X values.
for (let i = 1; i < n - 1; i += 1) {
rhs[i] = 4 * knots[i].x + 2 * knots[i + 1].x;
}
rhs[0] = knots[0].x + 2 * knots[1].x;
rhs[n - 1] = (8 * knots[n - 1].x + knots[n].x) / 2.0;
// Get first control points X-values.
const x = getFirstControlPoints(rhs);
// Set right hand side Y values.
for (let i = 1; i < n - 1; i += 1) {
rhs[i] = 4 * knots[i].y + 2 * knots[i + 1].y;
}
rhs[0] = knots[0].y + 2 * knots[1].y;
rhs[n - 1] = (8 * knots[n - 1].y + knots[n].y) / 2.0;
// Get first control points Y-values.
const y = getFirstControlPoints(rhs);
// Fill output arrays.
for (let i = 0; i < n; i += 1) {
// First control point.
firstControlPoints.push(new point_1.Point(x[i], y[i]));
// Second control point.
if (i < n - 1) {
secondControlPoints.push(new point_1.Point(2 * knots[i + 1].x - x[i + 1], 2 * knots[i + 1].y - y[i + 1]));
}
else {
secondControlPoints.push(new point_1.Point((knots[n].x + x[n - 1]) / 2, (knots[n].y + y[n - 1]) / 2));
}
}
return [firstControlPoints, secondControlPoints];
}
function throughPoints(points) {
if (points == null || (Array.isArray(points) && points.length < 2)) {
throw new Error('At least 2 points are required');
}
const controlPoints = getCurveControlPoints(points);
const curves = [];
for (let i = 0, ii = controlPoints[0].length; i < ii; i += 1) {
const controlPoint1 = new point_1.Point(controlPoints[0][i].x, controlPoints[0][i].y);
const controlPoint2 = new point_1.Point(controlPoints[1][i].x, controlPoints[1][i].y);
curves.push(new Curve(points[i], controlPoint1, controlPoint2, points[i + 1]));
}
return curves;
}
Curve.throughPoints = throughPoints;
})(Curve = exports.Curve || (exports.Curve = {}));
//# sourceMappingURL=curve.js.map