用贝塞尔曲线绘制圆弧

Posted

技术标签:

【中文标题】用贝塞尔曲线绘制圆弧【英文标题】:Drawing arc with bezier curves 【发布时间】:2014-11-05 18:21:55 【问题描述】:

我正在尝试使用贝塞尔曲线绘制弧线。我知道你不能用贝塞尔曲线画一个完美的圆,但你可以接近。不幸的是,数学太复杂了,我个人无法弄清楚。

我可以将下面的 A1 切片创建为三角形,但我不知道如何确定控制点。此外,如果我尝试在相反方向上从圆中绘制切片,请注意控制点似乎指向负方向。

所以如果我想要一个半径为 R 的圆切片并且我已经计算了锚点,我该如何计算控制点 1 和控制点 2 的位置???

【问题讨论】:

说到ios中的贝塞尔曲线,你指的是UIBezierPath吗?如果你是,你可以使用bezierPathWithArcCenter:radius:startAngle:endAngle:clockwise: 做你想做的事...... 或使用addArcWithCenter:radius:startAngle:endAngle:clockwise: 将圆形部分添加到您正在创建的现有路径对象中。 You must watch this fantastic explanation of how computers draw curves。也许它可以帮助您确定如何计算控制点。 谢谢大家,但我需要手动计算控制点。 zisoft,这是我所见过的最奇妙的解释。像我这样的白痴也能理解。 【参考方案1】:

冯远在他的书Windows Graphics Programming中提出了一个简单的方法:建立一个半径为1的圆弧,以OX轴为中心,计算它的Bezier近似值,并根据需要的圆弧参数缩放、平移和旋转控制点。这是我对这种方法的实现(在 Delphi 中),针对大弧进行了修改。 C++源码可以在网上某处找到,但我希望逻辑清楚。

  GenerateBezierArc(200, 200, 150, Pi / 4, 3 * Pi / 2, Pts);
  Canvas.PolyBezier(Pts);

结果:

type
  TPointArray = array of TPoint;

//calculates array of Bezier control points
//for circle arc with center CX, CY and radius R
procedure GenerateBezierArc(CX, CY, R: Integer;
                            StartAngle, SweepAngle: Double;
                            var Pts: TPointArray);
// C-Pascal translation from Feng Yuan book, with correction of source errors
var
  iCurve, NCurves: Integer;
  i: Integer;
  x0, y0, tx, ty, sn, cs, ASweep, AStart: Double;
  Px, Py: array [0 .. 3] of Double;
begin
  if SweepAngle = 0 then
    Exit;
  // if SweepAngle is too large, divide arc to smaller ones
  NCurves := Ceil(Abs(SweepAngle) / (Pi/2));
  SetLength(Pts, 3 * NCurves + 1);
  ASweep := SweepAngle / NCurves;

  // calculates control points for Bezier approx. of arc with radius=1,
  // circle center at (0,0), middle of arc at (1,0)
  y0 := Sin(ASweep / 2);
  x0 := Cos(ASweep / 2);
  tx := (1 - x0) * 4 / 3;
  ty := y0 - tx * x0 / (y0 + 0.0001);
  Px[0] := x0;
  Py[0] := -y0;
  Px[1] := x0 + tx;
  Py[1] := -ty;
  Px[2] := x0 + tx;
  Py[2] := ty;
  Px[3] := x0;
  Py[3] := y0;

  // rotation and translation of control points
  sn := Sin(StartAngle + ASweep / 2);
  cs := Cos(StartAngle + ASweep / 2);
  Pts[0].X := CX + Round(R * (Px[0] * cs - Py[0] * sn));
  Pts[0].Y := CY + Round(R * (Px[0] * sn + Py[0] * cs));

  for iCurve := 0 to NCurves - 1 do begin
    AStart := StartAngle + ASweep * iCurve;
    sn := Sin(AStart + ASweep / 2);
    cs := Cos(AStart + ASweep / 2);
    for i := 1 to 3 do begin
      Pts[i + iCurve * 3].X := CX + Round(R * (Px[i] * cs - Py[i] * sn));
      Pts[i + iCurve * 3].Y := CY + Round(R * (Px[i] * sn + Py[i] * cs));
    end;
  end;
end;

【讨论】:

【参考方案2】:

This article 给出了一组 4 条贝塞尔曲线,它们生成了一个非常接近的近似圆。它将圆分成 4 个四分之一,每条曲线生成圆的 1/4。

我不知道您如何得出沿圆的任意弧线的控制点。您可以使用 trig 来查找起点和终点,但中间点会更难。

文章结论:

使用此近似值,最大径向漂移为 0.019608%。这比标准近似值好 28%。 这是最终结果:

图 4. Bézier 近似与圆几乎无法区分。 图 4 是使用贝塞尔曲线创建的: P_0 = (0,1), P_1 = (c,1), P_2 = (1,c), P_3 = (1,0) P_0 = (1,0), P_1 = (1,-c), P_2 = (c,-1), P_3 = (0,-1) P_0 = (0,-1), P_1 = (-c,-1), P_3 = (-1,-c), P_4 = (-1,0) P_0 = (-1,0), P_1 = (-1,c), P_2 = (-c,1), P_3 = (0,1) c = 0.551915024494。

这是一个单位圆(原点上半径为 1 的圆),您需要将其缩放为其他半径值。

编辑:

如果您假设您的圆弧始终是圆的 1/4 或更小,那么您可以将贝塞尔曲线用于 1/4 圆,并通过改变 t 参数的范围来绘制该圆弧的一部分到小于 t=0 -> t=1 的范围。您需要对您的点应用旋转变换以围绕圆圈移动它们。

【讨论】:

我连这篇文章都看不懂。另外,我认为他不计算控制点,只是计算锚点。 OP 说“...我已经计算了锚点,如何计算控制点 1 和控制点 2 的位置???”如果你看他的插图,控制点是 2 个内部点。使用正弦和余弦计算锚点很简单。 你说的是“三次贝塞尔曲线的一般公式” B(t) = (1-t)^3*P_0 + 3*(1-t)^2*t*P_1 + 3*(1-t)*t^2*P_2 + t^3*P_3, t in [0,1] 第一个约束意味着:P_0 = (0,1), P_1 = (c,1), P_2 = (1,c), P_3 = (1,0) 第二个约束提供 c 的值: c = (4/3)*(sqrt(2) - 1) 如果我的第一个 anchorPoint 是 25,35,如何我要不要用这个公式来得到anchorPoint 1的第一个控制点(x,y)???? 那篇文章解释了如何使用 4 条三次贝塞尔曲线来创建一个非常接近整圆的近似值。正如我所说,我不知道您如何为任意圆弧提出中间贝塞尔控制点。我读过的其他文章表明,贝塞尔曲线可以创建非常好的近似圆弧,最多可达圆的 1/4。 链接不是答案。您应该至少在此处提供最终公式,以防链接停止工作。【参考方案3】:

Duncan 的帖子所引用的文章实际上是由 Tor Dokken(主要作者)撰写并于 1990 年发表在 Computer Aided Geometric Design Vol 7 上的期刊论文中 90 度圆弧的结果。它引用了两种方法来近似90 度圆弧:标准方法和更好的方法。我将在下面列出“标准方法”的通用公式,并省略“更好方法”的通用公式,因为它需要大量输入:

对于角跨度为A、单位半径的圆弧,描述为C(t) = (cos(t), sin(t)),其中t=[0, A],一个好的三次贝塞尔曲线近似可以可以通过以下控制点获得:

P(0) = (1, 0), P(1) = (1, 0) + L(0,1), P(2) = (cosA, sinA) - L (-sinA, cosA), P(3) = (cosA, sinA)

其中 L 是一个标量常数,取决于 A as

L = (4/3)*tan(A/4)

请注意,这种方式得到的三次贝塞尔曲线近似总是对圆弧的两个端点和中点进行插值,并且近似误差总是为正的,这意味着三次贝塞尔曲线总是在圆的“外面”弧线。

这个简单公式的最大径向误差 (x(t)^2 + y(t)^2 - 1) 是

Error_max = (4/27) * ( power(sin(A/4),6)/power(cos(A/4),2) )

当您想在某个公差内逼近一般圆弧(任何角度跨度和任何半径半径)时,您可以使用此公式计算您需要将圆弧分成多少段,并用以下公式逼近每个弧段三次贝塞尔曲线。由于这条三次贝塞尔曲线将尊重端点和结束斜率,因此获得的所有三次贝塞尔曲线将平滑地连接在一起。

【讨论】:

以上是关于用贝塞尔曲线绘制圆弧的主要内容,如果未能解决你的问题,请参考以下文章

三次方贝塞尔曲线的绘制

2021-09-16 WPF上位机 22-几何图形(StreamGeometry)

Android 贝塞尔曲线实现QQ拖拽清除效果

pr 贝塞尔曲线怎么用?

贝塞尔曲线基本用法

用几何画板怎样制作贝塞尔曲线