三次贝塞尔曲线拟合圆弧

 由于工作需求,需要用三次贝塞尔曲线拟合圆弧,所以查阅了一些资料,主要参考如下文章:

       使用贝塞尔曲线拟合圆

       但是文章写的过于简单,也没有推演步骤,而我需要知道任意圆弧如何求出贝塞尔曲线的两个控制点,所以自己进行了推算,如有疏漏望指正。

一、绘图        

        最开始解这个的时候其实我是用代数去解的,但是后面发现代数运算过于复杂,很难写出解,于是想到通过几何的方式来解这个,比较参考文章里也用了半角的特殊值。于是我尝试从几何的角度来解。先贴我画的几何图,如下,其中实线部分是很容易就想到要连起来的,虚线部分是作的辅助线:

        简单解释下上面的图,其中点 O 是圆心,P0、P3 分别是圆弧的起点和终点,P1 和 P2 分别是贝塞尔曲线的两个控制点。 M2 是 线段 P1-P2 的中点,M0 是线段 O-M2 和 P0-P3 的交点。 M1 是贝塞尔曲线 t = 0.5 时的点,也就是圆弧的中点,同时很容易就可以证明 M1 在线段 O-M2 上(根据几个垂直可以推出)。

        其中有几个中点没有标,根据三次贝塞尔曲线的几何性质可以知道:M1 是一个很特殊的中点,它可以这样取得:取 P0-P1、P1-P2、P2-P3的中点 C0、M2、C2,连接 C0-M2 和 M2-C2,取 C0-M2 和 M2-C2 的中点 D1、D2,连接 D1-D2,取 D1-D2 的中点就是 M1,M1 是在贝塞尔曲线上的,也就是在圆弧上的。

        接下来我们做两条辅助线,过 P1 作 P0-P2 的垂线,垂足为 F0,延长 0-M2 和 P0-P1 交于 F1。

二、推算

        其实我觉得把图画好就成功一半了,所以画图真的很重要:)

        接下来给出已知量,已知圆的半径 r , P0 和 P2 的坐标。求 P1 和 P2 的坐标。

2.1 推算 P0-P1 的长度     

       1. 假设 P0-P1 的长度为 l(小写的L 不是 1),M0-M2 的长度为 d,∠P0-O-P1 的角度为 θ。

        2.首先可以证明出 ∠O-P0-P1 和 ∠O-P3-P2 是直角,这里省略。(大致过程应该可以通过求出贝塞尔曲线的切线,然后得出在 t = 0 和 t = 1 是两个控制点 P1、P2 是在切线上的,结合圆的切线垂直于圆点到圆心的连线)

        3. 推算出线段 M2-M1 的长度是 M1-M0 的 1/4,同时 M1-M0 是 M2-M0 的 3/4。(这个步骤相对简单,根据贝塞尔曲线的 M1 点由来(多次取中点)可以证明)

        5. (线段长度M0-M1) = r – (线段长度O-M0),O-M0的长度 = r*cos(θ/2),θ为 ∠P0-O-P1 的角度。结合 M0-M1 是 M0-M2 的 3/4,如果 M0-M2 的长度为 d。那么

           d*3/4 = r – r*cos(θ/2)  (方程 1)

       6. 由于 ∠P0-M0-O 和 ∠P1-P0-O 都是直接,那么可以很快证明出 ∠P1-P0-F0 = ∠P0-O-M0 = θ/2,进而得出 P1-F0 的长度等于 l*sin(θ/2),l 的长度是 P0-P1 的长度。之后可以得出 M0-M2 的长度是等于 P1-F0 的长度的,也就是

           d = l*sin((θ/2)) (方程 2)

       7. 联合 方程1 和 方程2 ,可以求出 l ,也就是 P0-P1 的长度

            l = (4/3)*((1-cos(θ/2))/sin(θ/2))*r 

2.2 求P1、P2 的坐标

       1. 假设 O 点的坐标为 (x,y),∠P1-O-P0 的角度为 β,线段 P1-O 的长度为 s,点 P0 转换成极坐标 (α,r),点 P3 的极坐标为 (σ,r)。α 是 P0 的极坐标角度,σ 是 P3 的极坐标角度。

       2. 给出这轮的已知条件

           l = (4/3)*((1-cos(θ/2))/sin(θ/2))*r 

          sinβ = l/s

          cosβ = r/s

          x0 = r*cosα + x

          y0 = r*sinα + y
          x3 = r*cosα + x
          y3 = r*sinσ + x

       3. x1 = s*cos(α+β) + x

                = s*cosα*cosβ – s*sinα*sinβ + x

                = r*cosα – l*sinα + x

                = x0 -(4/3)*((1-cos(θ/2))/sin(θ/2))*r *sinα

                = x0 – (4/3)*((1-cos(θ/2))/sin(θ/2))*(y0-y)

   y1 = s*sin(α+β) + y

                = s*sinα*cosβ + s*cosα*sinβ + y

                = r*sinα + l*cosα + y

                = y0 + (4/3)*((1-cos(θ/2))/sin(θ/2))*r *cosα

                = y0 + (4/3)*((1-cos(θ/2))/sin(θ/2))*(x0-x)

           x2 = s*cos(σ-β) + x

                = s*cosσ*cosβ + s*sinσ*sinβ + x

                = r*cosσ + l*sinσ + x

                = x3 + (4/3)*((1-cos(θ/2))/sin(θ/2))*r*sinσ

                = x3 + (4/3)*((1-cos(θ/2))/sin(θ/2))*(y3-y)

           y2 = s*sin(σ-β) + y

                = s*sinσ*cosβ – s*cosσ*sinβ + y

                = r*sinσ – l*cosσ + y

                = y3 – (4/3)*((1-cos(θ/2))/sin(θ/2))*r*cosσ

                = y3 – (4/3)*((1-cos(θ/2))/sin(θ/2))*(x3-x)

       4. 如果假设 a = (4/3)*((1-cos(θ/2))/sin(θ/2)) = (4/3)*tan(θ/4),结果可以简化为

            x1 = x0 – a*(y0-y)

            y1 = y0 + a*(x0-x)

            x2 = x3 + a*(y3-y)

            y2 = y3 – a*(x3-x)

至此全部推算结束,这里补充说明几点,首先拟合的圆弧角度不能大于 90°,如果是大弧度的圆弧可以划分成多段拟合,其次拟合的偏移量每隔 45° 就回归一次,从 0° –  45° 偏移量从小到大,再到小。

最后贴一段实际的 javascript 代码:

//r 为圆弧半径,cx,cy 为圆弧圆心,startAngle,endAngle 为圆弧的起始角度和结束角度
arcToBezier(r,cx,cy,startAngle,endAngle)
{
    var x0 = cx + Math.cos(startAngle)*r;
    var y0 = cy + Math.sin(startAngle)*r;
    var x3 = cx + Math.cos(endAngle)*r;
    var y3 = cy + Math.sin(endAngle)*r;
    var addAngle = endAngle – startAngle;
    var a = 4*Math.tan(addAngle/4)/3;
    var x1 = x0 – a*(y0 – cy);
    var y1 = y0 + a*(x0 – cx);
    var x2 = x3 + a*(y3 – cy);
    var y2 = y3 – a*(x3 – cx);
    return {“x0″:x0,”y0″:y0,”x1″:x1,”y1″:y1,”x2″:x2,”y2″:y2,”x3″:x3,”y3”:y3};
}

————————————————
版权声明:本文为CSDN博主「SimpleLiMengJie」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/jiexiaopei_2004/article/details/48496475

    原文作者:hbaizj
    原文地址: https://blog.csdn.net/hbaizj/article/details/121257500
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞