Drawing arc with bezier curves

我们两清 提交于 2019-12-02 03:27:51

Feng Yuan proposed simple method in his book Windows Graphics Programming: build an arc with radius 1, centered at OX axis, calculate Bezier approximation for it, and scale, translate and rotate control points for needed arc parameters. Here is my implementation of this method (in Delphi), modified for large arcs. C++ sources can be found somewhere in the Internet, but I hope that the logic is clear.

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

result:

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;

The article referenced by Duncan's post is actually the result for 90 degree circular arc from a journal paper authored by Tor Dokken (the main author) and published in Computer Aided Geometric Design Vol 7 in 1990. It cited two approaches for approximating a 90 degree arc: a standard approach and a better approach. I will list the general formula for the "standard approach" below and leave out the general formula for the "better approach" as it requires a lot of typing:

For a circular arc with angular span A and unit radius, described as C(t) = (cos(t), sin(t)), where t=[0, A], a good cubic Bezier curve approximation can be obtained with the following control points:

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

where L is a scalar constant depending on A as

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

Please note that the cubic Bezier curve approximation obtained this way always interpolates the two end points and the mid-point of the circular arc and the approximation error is always positive, which means the cubic Bezier curve is always "outside" the circular arc.

The maximum radial error (x(t)^2 + y(t)^2 - 1) from this simple formula is

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

When you want to approximate a general circular arc (any angle span and any radius radius) within a certain tolerance, you can use this formula to compute how many segments you need to break the circular arc into and approximate each arc segment with a cubic Bezier curve. Since this cubic Bezier curve will honor the end points and end slopes, all cubic Bezier curves obtained will join smoothly together.

This article gives a set of 4 bezier curves that generates a very close approximation of a circle. It divides the circle into 4 quarters and each curve generates 1/4 of the circle.

I don't know how you'd come up with the control points for an arbitrary arc along a circle. You'd use trig to find the start and end points, but the middle points would be harder.

The conclusion of the article:

The maximum radial drift is 0.019608% with this approximation. This is 28% better than the standard approximation. Here is the final result:

Figure 4. The Bézier approximation is almost indistinguishable from a circle. Figure 4 was created using the Bézier curves: 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) with c = 0.551915024494.

That's for a unit circle (a circle on the origin with a radius of 1) You'd need to scale it for other radius values.

EDIT:

If you assume that your arc will always be 1/4 of a circle or less, then you could use the Bezier curve for a 1/4 circle, and draw a portion of that arc by varying the range of the t parameter to a range less than t=0 -> t=1. You'd need to apply a rotation transform to your points to move them around the circle.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!