代码之家  ›  专栏  ›  技术社区  ›  Mike Caron

如何用贝塞尔曲线最接近一个几何弧?

  •  25
  • Mike Caron  · 技术社区  · 16 年前

    在二维中绘制圆弧时,使用贝塞尔曲线近似值,如何计算两个控制点,前提是您有一个圆的中心点、起点和终点角度以及半径?

    8 回复  |  直到 16 年前
        1
  •  17
  •   John Feminella    16 年前

    这在stackoverflow帖子中不容易解释,特别是向您证明这一点需要很多详细的步骤。然而,你所描述的是一个常见的问题,有很多彻底的解释。见 here here 我非常喜欢2,以前也用过。

        2
  •  10
  •   k88lawrence    8 年前

    这是一个8岁的问题,但我最近一直在努力解决,所以我想我会分享我的想法。我花了很多时间试图使用 this text 直到我在谷歌上搜索并得知,显然,方程式中有一些错别字,我才能从中得到任何合理的数字。根据中列出的更正 this blog post ,给定圆弧的起点和终点([x1,y1]和[x4,y4],以及圆的中心([xc,yc]),可以得出三次贝塞尔曲线([x2,y2]和[x3,y3])的控制点如下:

    ax = x1 – xc
    ay = y1 – yc
    bx = x4 – xc
    by = y4 – yc
    q1 = ax * ax + ay * ay
    q2 = q1 + ax * bx + ay * by
    k2 = 4/3 * (√(2 * q1 * q2) – q2) / (ax * by – ay * bx)
    
    
    x2 = xc + ax – k2 * ay
    y2 = yc + ay + k2 * ax
    x3 = xc + bx + k2 * by                                 
    y3 = yc + by – k2 * bx
    

    希望这能帮助我以外的人!

        4
  •  4
  •   Timo Kähkönen    13 年前

    Raphael2.1.0支持arc->cubic(path2curve函数),在修复了s和t路径规范化中的错误之后,它现在似乎可以工作了。我更新 * 随机路径发生器 * 所以它只生成弧,所以很容易测试所有可能的路径组合:

    http://jsbin.com/oqojan/53/

    测试,如果某条路径失败,我会很高兴得到报告。

    编辑:刚刚意识到这是3年前的线索…

        5
  •  4
  •   Nic    9 年前

    在“A的近似值”中给出了一个很好的解释。 Cubic Bezier Curve by Circular Arcs

    长话短说:使用贝塞尔曲线,您可以获得1.96_10^-4的最小误差,这对于大多数应用程序来说都很好。

    对于正象限弧,使用以下点:

    p0 = [0, radius]
    
    p1 = [radius * K, radius]  
    
    p2 = [radius, radius * K]
    
    p3 = [radius, 0]
    

    其中k是所谓的“幻数”,它是一个非有理数。可近似如下:

    K = 0.5522847498
    
        6
  •  3
  •   MaxArt    9 年前

    我在回答这个古老的问题(它应该属于数学,所以写公式会很糟糕)的时候做了一些演示。

    假设 P0 P3 是你弧的起点和终点, P1 P2 b_)zier曲线的控制点,以及 X 是角度除以2的度量。假设 X 少一些 圆周率 2。

    颗粒物 段的中点 P0P3 酸碱度 弧的中点。为了接近弧,我们希望b_zier曲线从 P0 通过 酸碱度 结束 P3 ,并与中的弧相切 P0 P3 .

    (单击“运行代码段”以显示该图。诅咒伊姆古尔仍然不支持SVG。)

    <svg xmlns="http://www.w3.org/2000/svg" viewBox="10 20 80 80">
        <style>text{font-size:40%;font-style:italic;text-anchor:middle}tspan{font-size:50%;font-style:normal}</style>
        <rect x="10" y="20" width="80" height="80" fill="none" stroke="gray"></rect>
        <path stroke="gray" stroke-dasharray="3,2" fill="none" d="M25,30 62.6,31.62 80,65 22.19,95.13 25,30 80,65 M22.19,95.13 62.6,31.62"></path>
        <path stroke="black" fill="none" d="M25,30A65.19 65.19 0 0 1 80,65"></path>
        <circle r="1" fill="red" cx="25" cy="30"></circle>
        <circle r="1" fill="green" cx="80" cy="65"></circle>
        <circle r="1" fill="magenta" cx="22.19" cy="95.13"></circle>
        <circle r="1" fill="darkgreen" cx="52.5" cy="47.5"></circle>
        <circle r="1" fill="yellow" cx="57.19" cy="40.13"></circle>
        <circle r="1" fill="maroon" cx="62.6" cy="31.62"></circle>
        <circle r="1" fill="orange" cx="48.27" cy="31"></circle>
        <circle r="1" fill="teal" cx="69.24" cy="44.35"></circle>
        <text x="25" y="28">P<tspan>0</tspan></text>
        <text x="48.27" y="29">P<tspan>1</tspan></text>
        <text x="71.24" y="42.35">P<tspan>2</tspan></text>
        <text x="83" y="63">P<tspan>3</tspan></text>
        <text x="62.6" y="29.62">P<tspan>E</tspan></text>
        <text x="59.19" y="47.13">P<tspan>H</tspan></text>
        <text x="54.5" y="54.5">P<tspan>M</tspan></text>
    </svg>

    体育课 与弧相切的直线的交点 P0 P3 . 为了使曲线与弧相切, P1 必须位于段上 P0PE P2 必须躺在 P3PE . 让 K 成为比例 P0P1 / P0PE (也等于 P3P2 / P3PE ):

    P1 =(1) K ) P0 + K 体育课

    P2 =(1) K ) P3 + K 体育课

    我们还有以下内容(做一些比例):

    颗粒物 =( P0 + P3 )/ 2

    酸碱度 = 颗粒物 CoS(COS) X = 颗粒物 X (=) P0 + P3 (秒) X )/ 2

    体育课 = 酸碱度 CoS(COS) X = 颗粒物 X ^ ^=2 P0 + P3 (秒) X ^ ^ 2/2

    为了简化我们的计算,我考虑了所有的向量点都是基于中心的,但最后这并不重要。

    一般的4点b_)zier曲线由以下公式给出:

    C ( T = T ^ 3 P3 + 3(1) T ) T ^ 2 P2 + 3(1) T ^ ^ 2 T P1 +(1) T ^ ^ 3 P0

    我们必须拥有 C (1/2)= 酸碱度 如此

    C (1/2)=( P0 + 3 P1 + 3 P2 + P3 )/ 8

    =(() P0 + P3 + 3(1) K ) P0 + 3 K-PE + 3(1) K ) P3 + 3 K-PE )/ 8

    =(() P0 + P3 + 3(1) K ( P0 + P3 + 6 K-PE )/ 8

    =( P0 + P3 (1±3(1)- K + 3 K X ^ ^)/ 8

    这是我们的方程(乘以8) K :

    C (1/2)=8 酸碱度

    =; P0 + P3 (4—3) K + 3 K X ^ ^)=4(2) P0 + P3 (秒) X )

    让我们去掉向量( P0 + P3 我们得到:

    4—3 K + 3 K X ^ ^=4秒(2秒) X )

    = 3; K (SEC) X )^ 2-1)=4(秒( X - 1)

    = & gt; K = 4/3(秒) X + 1)

    现在你知道在哪里放置控制点了。万岁!

    如果你有 X = 圆周率 4,你会得到 K = 0.552…您可能已经看到了这个值。

    在处理椭圆弧时,您所要做的就是相应地缩放点的坐标。

    如果必须处理较大的角度,我建议将它们分割成更多的曲线。这实际上是一些软件在绘制圆弧时所做的,因为计算B_zier曲线有时比使用正弦和余弦更快。

        7
  •  2
  •   xan    14 年前

    我已经成功了 general solution for any elliptical arc 作为一条三次贝塞尔曲线。它甚至在公式中包含了起始角和终止角,因此不需要额外的旋转(这对于非圆形椭圆来说是个问题)。

        8
  •  1
  •   dodev    8 年前

    我最近偶然发现了这个问题。我以模块的形式从这里提到的文章中编译了一个解决方案。

    它接受起始角、终止角、中心和半径作为输入。

    它很好地近似小圆弧(<=pi/2)。如果您需要将一些弧从pi/2近似为2*pi,则可以将它们分成多个部分<pi/2,然后计算出相应的曲线并将它们连接起来。

    这个解是起始角和终止角顺序不可知论-它总是选取小弧。

    结果你得到了所有四个点,你需要在绝对坐标下定义一个三次贝塞尔曲线。

    我认为这最好用代码和注释来解释:

    'use strict';
    
    module.exports = function (angleStart, angleEnd, center, radius) {
        // assuming angleStart and angleEnd are in degrees
        const angleStartRadians = angleStart * Math.PI / 180;
        const angleEndRadians = angleEnd * Math.PI / 180;
    
        // Finding the coordinates of the control points in a simplified case where the center of the circle is at [0,0]
        const relControlPoints = getRelativeControlPoints(angleStartRadians, angleEndRadians, radius);
    
        return {
            pointStart: getPointAtAngle(angleStartRadians, center, radius),
            pointEnd: getPointAtAngle(angleEndRadians, center, radius),
            // To get the absolute control point coordinates we just translate by the center coordinates
            controlPoint1: {
                x: center.x + relControlPoints[0].x,
                y: center.y + relControlPoints[0].y
            },
            controlPoint2: {
                x: center.x + relControlPoints[1].x,
                y: center.y + relControlPoints[1].y
            }
        };
    };
    
    function getRelativeControlPoints(angleStart, angleEnd, radius) {
        // factor is the commonly reffered parameter K in the articles about arc to cubic bezier approximation 
        const factor = getApproximationFactor(angleStart, angleEnd);
    
        // Distance from [0, 0] to each of the control points. Basically this is the hypotenuse of the triangle [0,0], a control point and the projection of the point on Ox
        const distToCtrPoint = Math.sqrt(radius * radius * (1 + factor * factor));
        // Angle between the hypotenuse and Ox for control point 1.
        const angle1 = angleStart + Math.atan(factor);
        // Angle between the hypotenuse and Ox for control point 2.
        const angle2 = angleEnd - Math.atan(factor);
    
        return [
            {
                x: Math.cos(angle1) * distToCtrPoint,
                y: Math.sin(angle1) * distToCtrPoint
            },
            {
                x: Math.cos(angle2) * distToCtrPoint,
                y: Math.sin(angle2) * distToCtrPoint
            }
        ];
    }
    
    function getPointAtAngle(angle, center, radius) {
        return {
            x: center.x + radius * Math.cos(angle),
            y: center.y + radius * Math.sin(angle)
        };
    }
    
    // Calculating K as done in https://pomax.github.io/bezierinfo/#circles_cubic
    function getApproximationFactor(angleStart, angleEnd) {
        let arc = angleEnd - angleStart;
    
        // Always choose the smaller arc
        if (Math.abs(arc) > Math.PI) {
            arc -= Math.PI * 2;
            arc %= Math.PI * 2;
        }
        return (4 / 3) * Math.tan(arc / 4);
    }