如何从椭圆上的 2 个点在 canvas 上绘制椭圆,其中长轴 (rx) 的斜率和短轴 (ry) 长度未知

How to plot an ellipse on canvas from 2 points on the ellipse, where slope of major axis (rx), and minor axis (ry) length are unknown

这可能更像是一个数学问题,但也许我缺少一个简单的 javascript 解决方案。

我想根据用户输入的中心点、长(最长)轴的半径在 html canvas 上绘制一个椭圆,并且 2 个点将落在椭圆上。

这可能会创建 2 条可能的椭圆路径,两条路径都将以中心点为中心,并穿过这 2 个点。

例如,如果center = [2, 1]长轴半径a = 10,点1u = [4, 2]和点2v = [5, 6],短轴半径是多少b和旋转角度theta?

到目前为止,我已尝试实现从 https://math.stackexchange.com/questions/3210414/find-the-angle-of-rotation-and-minor-axis-length-of-ellipse-from-major-axis-leng 中找到的方程式, 但它不是 return 有效值。我的 javascript 代码如下所示:


function getEllipseFrom2Points(center, u, v, a) {
  function getSlope(plusOrMinus) {
      return Math.sqrt(((uy * vx - ux * vy) ** 2) / (-ux * uy * (a * (v2x + v2y) - 1) + vx * vy * (a * (u2x + u2y) - 1) - plusOrMinus * (uy * vx - ux * vy) * q) / (u2x * (1 - a * v2y) + v2x * (a * u2y - 1)));
  }
  function getMinorAxis(plusOrMinus) {
      return (u2x + u2y + v2x + v2y - a * (2 * u2x * v2x + 2 * u2y * v2y + 2 * ux * uy * vx * vy + u2y * v2x + u2x * v2y) + plusOrMinus * 2 * (ux * vx + uy * vy) * q);
  }
  var vx = v[0],
      vy = v[1],
      ux = u[0],
      uy = u[1],
      v2x = vx ** 2,
      v2y = vy ** 2,
      u2x = ux ** 2,
      u2y = uy ** 2,
      q = Math.sqrt((1 - a * (u2x + u2y)) * (1 - a * (v2x + v2y))),
      ellipse1 = { rx: a, ry: getMinorAxis(1), origin: center, rotation: getSlope(1) },
      ellipse2 = { rx: a, ry: getMinorAxis(-1), origin: center, rotation: getSlope(-1) };
}

要么是我遵循的等式是错误的,要么是我执行的不对

万一有人感兴趣,这里是我对问题的解决方案,这并不是真正的 "the" 解决方案。如果有人能解决这个问题,我仍然很乐意知道。

因为我不能同时求解长轴的斜率和短轴的长度,所以我只是猜测斜率然后测试它有多接近,然后通过尝试更小的来优化结果和较小的区域。由于最终绘制的椭圆实际上是根据贝塞尔曲线构建的估计,因此我可以在合理的时间内足够接近。

function getEllipseFrom2Points (center, u, v, a) {
    function getSemiMinorAxis([x, y], a, t) {
        // equation for rotated ellipse
        // b = a(ycos(t) - xsin(t)) / sqrt(a^2 - x^2cos^2(t) - 2xysin(t)cos(t) - y^2sin^2(t)) and
        // b = a(xsin(t) - ycos(t)) / sqrt(a^2 - x^2cos^2(t) - 2xysin(t)cos(t) - y^2sin^2(t)) 
        // where a^2 !== (xcos(t) + ysin(t))^2
        // and aycos(t) !== axsin(t)

        if (a ** 2 !== (x * Math.cos(t) + y * Math.sin(t)) ** 2 &&
            a * y * Math.cos(t) !== a * x * Math.sin(t)) {
            var b = [],
                q = (Math.sqrt(a ** 2 - x ** 2 * (Math.cos(t)) ** 2 - 2 * x * y * Math.sin(t) * Math.cos(t) - y ** 2 * (Math.sin(t)) ** 2));
            b[0] = (a * (y * Math.cos(t) - x * Math.sin(t))) / q;
            b[1] = (a * (x * Math.sin(t) - y * Math.cos(t))) / q;
            return b;
        }
    }
    function getAngle_radians(point1, point2){
        return Math.atan2(point2[1] - point1[1], point2[0] - point1[0]);
    }
    function getDistance(point1, point2) {
        return Math.sqrt((point2[0] - point1[0]) ** 2 + (point2[1] - point1[1]) ** 2);
    }
    function rotatePoint(point, center, radians) {
        var x = (point[0] - center[0]) * Math.cos(radians) - (point[1] - center[1]) * Math.sin(radians) + center[0];
        var y = (point[1] - center[1]) * Math.cos(radians) + (point[0] - center[0]) * Math.sin(radians) + center[1];
        return [x, y];
    }
    function measure(ellipseRotation, pointOnEllipse, minorAxisLength) {
        var d = getDistance(point, pointOnEllipse);
        if (d < bestDistanceBetweenPointAndEllipse) {
            bestDistanceBetweenPointAndEllipse = d;
            bestEstimationOfB = minorAxisLength;
            bestEstimationOfR = ellipseRotation;
        }
    }
    function getBestEstimate(min, max) {
        var testIncrement = (max - min) / 10;
        for (let r = min; r < max; r = r + testIncrement) {
            if (radPoint1 < r && radPoint2 < r || radPoint1 > r && radPoint2 > r) {//points both on same side of ellipse
                semiMinorAxis = getSemiMinorAxis(v, a, r);
                if (semiMinorAxis) {
                    for (let t = 0; t < circle; t = t + degree) {
                        ellipsePoint1 = [a * Math.cos(t), semiMinorAxis[0] * Math.sin(t)];
                        ellipsePoint2 = [a * Math.cos(t), semiMinorAxis[1] * Math.sin(t)];
                        point = rotatePoint(u, [0, 0], -r);
                        measure(r, ellipsePoint1, semiMinorAxis[0]);
                        measure(r, ellipsePoint2, semiMinorAxis[1]);
                    }
                }
            }
        }
        count++;
        if (new Date().getTime() - startTime < 200 && count < 10) //refine estimate
            getBestEstimate(bestEstimationOfR - testIncrement, bestEstimationOfR + testIncrement);
    }
    if (center instanceof Array &&
        typeof center[0] === "number" &&
        typeof center[1] === "number" &&
        u instanceof Array &&
        typeof u[0] === "number" &&
        typeof u[1] === "number" &&
        v instanceof Array &&
        typeof v[0] === "number" &&
        typeof v[1] === "number" &&
        typeof a === "number") {

        // translate points
        u = [u[0] - center[0], u[1] - center[1]];
        v = [v[0] - center[0], v[1] - center[1]];

        var bestDistanceBetweenPointAndEllipse = a,
            point,
            semiMinorAxis,
            ellipsePoint1,
            ellipsePoint2,
            bestEstimationOfB,
            bestEstimationOfR,
            radPoint1 = getAngle_radians([0, 0], v),
            radPoint2 = getAngle_radians([0, 0], u),
            circle = 2 * Math.PI,
            degree = circle / 360,
            startTime = new Date().getTime(),
            count = 0;

        getBestEstimate(0, circle);

        var ellipseModel = MakerJs.$(new MakerJs.models.Ellipse(a, bestEstimationOfB))
            .rotate(MakerJs.angle.toDegrees(bestEstimationOfR), [0, 0])
            .move(center)
            .originate([0, 0])
            .$result;

        return ellipseModel;
}