首页 文章

如何使用贝塞尔曲线最佳逼近几何圆弧?

提问于
浏览
25

当使用贝塞尔曲线近似在2D中绘制圆弧时,如果您具有圆的中心点,起点和终点角以及半径,如何计算两个控制点?

8 回答

  • 2

    这不是一个常见的问题,并且有许多详尽的解释 . 见 herehere ;我非常喜欢#2并且之前使用过它 .

  • 1

    这是一个8年的问题,但是我最近一直在努力解决这个问题,所以我想我会分享我想出的东西 . 我花了很多时间尝试使用来自this text的解决方案(9)并且在我做了一些谷歌搜索之前无法获得任何合理的数字并且了解到,显然,方程中存在一些拼写错误 . 根据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

    在Wolfram MathWorld上有Mathematica代码:Bézier Curve Approximation of an Arc,这应该可以帮到你 .

    也可以看看:

  • 5

    Raphael 2.1.0支持Arc-> Cubic(path2curve-function),修复了S和T路径规范化中的错误后,它现在似乎正常工作 . 我更新了 the Random Path Generator 以便它只生成弧,因此可以轻松测试所有可能的路径组合:

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

    测试,如果某条路径失败,我很乐意收到报告 .

    编辑:刚才意识到这是3岁的线程......

  • 4

    "Approximation of a " Cubic Bezier Curve by Circular Arcs中提供了一个很好的解释 . “

    长话短说:使用Bezier曲线可以实现1.96×10 ^ -4的最小误差,这对于大多数应用来说都是相当不错的 .

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

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

    其中K是所谓的“幻数”,这是一个非理性数 . 它可以近似如下:

    K = 0.5522847498
    
  • 3

    我正在回答这个古老的问题(这应该属于数学,因此编写公式会很糟糕)并进行一些演示 .

    假设P0和P3是弧的初始和最终点,P1和P2是Bézier曲线的控制点,x是角度除以2的度量 . 假设x小于pi / 2 .

    设PM段P0P3和PH的中点为弧的中点 . 为了逼近弧线,我们希望Bézier曲线在P0中开始,通过PH,在P3中结束,并且与P0和P3中的弧相切 .

    (点击“运行代码片段”来显示图 . 诅咒到imgur仍然不支持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>
    

    设PE为P0和P3中与圆弧相切的线的交点 . 为了使曲线与弧相切,P1必须位于P0PE段上,P2必须位于P3PE上 . 设k为P0P1 / P0PE的比值(也等于P3P2 / P3PE):

    P1 =(1-k)P0 k PE P2 =(1-k)P3 k PE

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

    PM =(P0 P3)/ 2 PH = PM / cos(x)= PM sec(x)=(P0 P3)sec(x)/ 2 PE = PH / cos(x)= PM sec(x)^ 2 =(P0 P3)sec(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)= PH,所以

    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 sec(x)^ 2)/ 8

    所以,这是我们的等式(乘以8)来找到k:

    8 C(1/2)= 8 PH =>(P0 P3)(4 - 3 k 3 k sec(x)^ 2)= 4(P0 P3)sec(x)

    让我们摆脱向量(P0 P3),我们得到:

    4 - 3 k 3 k sec(x)^ 2 = 4 sec(x)=> 3 k(sec(x)^ 2 - 1)= 4(sec(x) - 1)=> k = 4/3 (秒(x)1)

    现在您知道了控制点的放置位置 . 万岁!

    如果你有x = pi / 4,你将得到k = 0.552 ......你可能已经看到了这个值 .

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

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

  • 17

    我用general solution for any elliptical arc作为立方贝塞尔曲线取得了成功 . 它甚至包括开始和结束角度配方,所以不需要额外的旋转(这对于非圆形椭圆来说是个问题) .

  • 11

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

    它接受起始角度,终止角度,中心和半径作为输入 .

    它非常接近小弧(<= 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);
    }
    

相关问题