用三次贝塞尔曲线拟合圆弧

本文详细介绍了如何利用三次贝塞尔曲线精确拟合圆弧,并提供了完整的数学推导过程,包括求解贝塞尔曲线控制点的具体公式及JavaScript实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

       使用贝塞尔曲线拟合圆

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



一、绘图        

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

        简单解释下上面的图,其中点 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};
}


可以使用以下步骤来拟合一个带有三个控制点的贝塞尔曲线: 1. 定义四分之一的起始点、结束点和一个控制点。可以使用以下代码来定义四分之一: ```python import math # 定义四分之一的半径和心 radius = 10 center_x = 0 center_y = 0 # 定义起始点、结束点和一个控制点 start_x = center_x + radius start_y = center_y end_x = center_x end_y = center_y + radius control_x = center_x + radius control_y = center_y + radius # 将圆弧分成 10 个点 num_points = 10 angle = math.pi / 2 / num_points points = [] for i in range(num_points + 1): x = center_x + radius * math.cos(i * angle) y = center_y + radius * math.sin(i * angle) points.append((x, y)) ``` 2. 使用三次贝塞尔曲线的公式来计算曲线上的点。三次贝塞尔曲线的公式为: $$B(t) = (1-t)^3P_0 + 3t(1-t)^2P_1 + 3t^2(1-t)P_2 + t^3P_3$$ 其中,$P_0$ 为起始点,$P_1$ 为第一个控制点,$P_2$ 为第二个控制点,$P_3$ 为结束点,$t$ 是在 0 到 1 之间的一个值,用来控制曲线的形状。 可以使用以下代码来计算曲线上的点: ```python def bezier_curve(points, num_points): # 计算三次贝塞尔曲线上的点 curve_points = [] for t in range(num_points): x = (1-t/num_points)**3*points[0][0] + 3*t/num_points*(1-t/num_points)**2*points[1][0] + 3*(t/num_points)**2*(1-t/num_points)*points[2][0] + (t/num_points)**3*points[3][0] y = (1-t/num_points)**3*points[0][1] + 3*t/num_points*(1-t/num_points)**2*points[1][1] + 3*(t/num_points)**2*(1-t/num_points)*points[2][1] + (t/num_points)**3*points[3][1] curve_points.append((x, y)) return curve_points # 计算贝塞尔曲线上的点 curve_points = bezier_curve([(start_x, start_y), (control_x, control_y), (control_x, control_y), (end_x, end_y)], num_points) ``` 3. 将计算出的曲线上的点绘制出来。可以使用以下代码来绘制曲线上的点: ```python import matplotlib.pyplot as plt # 绘制圆弧和控制点 plt.plot([p[0] for p in points], [p[1] for p in points], 'ro') plt.plot([start_x, control_x, end_x], [start_y, control_y, end_y], 'bo') # 绘制贝塞尔曲线 plt.plot([p[0] for p in curve_points], [p[1] for p in curve_points], 'g') plt.axis('equal') plt.show() ``` 运行上述代码,即可得到一个拟合四分之一贝塞尔曲线
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值