Monday, April 11, 2011

Approximating a Circular Arc With a Cubic Bezier Path

[Note: I've written an update to the material presented here, it's More About Approximating Circular Arcs With a Cubic Bezier Path]

A few days ago I decided to create a circular arc in a Flex program.  Of course I could have just used the Flash drawing API and the Graphics curveTo() method, but what I wanted was a FXG graphic element.   Like 'Rect' or 'Ellipse' or 'Line'.

There is no 'CircularArc' graphic element.  This was mildly disappointing, but I assumed that there was some pat algorithm for creating an approximation to a circular arc using the Path primitive.   Among the Path primitive's many talents is support for drawing quartic or cubic Bezier curves and I expected to find that somone had already sorted out how to coax one into approximating an arc.  I searched in vain for a nicely coded example and eventually gave up.   So I set about solving the problem using more basic sources.   There's a paper on this topic called "Approximation of a Cubic Bezier Curve by Circular Arcs and Vice Versa" which explains how a single cubic Bezier curve can be used to approximate an arc of 90 degrees or less.

The derivation of the the control points for a 90 degree circular arc is pretty simple.  The control points are on the tangent lines at the curves's endpoints, so if the curve begins on the X axis, the first tangent line is vertical and the second one is horizontal.   The distance from the end points to the control points is just r*k, where r is the arc's radius and k is a constant:


That's k = 0.5522847498, and it's generally referred to as the "magic number".  I am not making this up.

The derivation of the control points for an arc of less than 90 degrees is a little more complicated.  If the arc is centered around the X axis, then the length of the tangent line is r * tan(a/2), instead of just r.   The magnitude of the vector from each arc endpoint to its control point is k * r * tan(a/2).

The arc's x1,y2 and x4,y4 end points, and the control points, x2,y2 and x3,y3 are symmetric relative to the X axis:
              
x4 = r * Math.cos(a/2);
y4 = r * Math.sin(a/2);
x1 = x4;
y1 = -y4

x2 = x1 + k * tan(a/2) * y4;
y2 = y1 + k * tan(a/2) * x4;
x3 = x2;
y3 = -y2;

There's a deriviation of this in the Riskus paper although the explanation of the magnitude of the control point vector gets short shrift.

Given this forumula for computing the control points of an arc that spans between 0 and 90 degrees that is centered around the X axis, we can create a list of curves that span a total of up to 360 degrees.  Each list element's four points - the curve's two end points and two control points -  must be rotated to into their final position on the circle.

The example below uses an ActionScript implementation of this algorithm to compute up to four cubic Bezier curves which are combined in a single Path graphical element.  The Path begins at the start angle, and ends at the end angle.  Remember that the Y axis increases downwards, so positive angles rotate clockwise from the X axis.  The red points correspond to the endpoints of the curves and the blue points indicate the control points.   If you vary the angles you'll see that the algorithm strings together as many 90 degree arcs as possible, and then finishes with one small arc.



To use this code in other applications, lift the static createArc() and createSmallArc() methods, as well as the EPISILON constant.   The createArc() method returns an array of objects, one per Bezier curve, for an arc centered at the origin:

  createArc(radius:Number, startAngle:Number, endAngle:Number):Array

Each curve is represented by an object with four x,y points: x1, y1, x2, y2, x3, x4, y4.
Points x1,y1 and x4,y4 are the curve's endpoints, x2,y2 and x3,y3 are the control points.  To translate the curves to an arbitrary center point xc, yc, just add xc to the x coordinates, yc to the y coordinates.  The displayArc() method in the example does this, and demonstrates how to use the curve objects to create the data for a Path graphic element.  Sadly the Path's data is a String, so all of the coordinates must be converted to text.  On the upside, if you've been searching for an example that demonstrates how to approximate a circular arc with Beziers in Flex, here you are.

13 comments:

  1. Very interesting implementation. Nicely illustrated effect showing the control points.

    Thanks for sharing.

    Alex Kiriako

    ReplyDelete
  2. This looks like exactly what I need. Unfortunately, the "View Source" command does not take me to a page. I would be very grateful if you were able to fix that. Thanks much.

    ReplyDelete
  3. Sorry about the "view source" failure, I'll have to sort out how to make that work. There are two links to the CircularArc.mxml in the text, it's:
    https://sites.google.com/site/hansmuller/flex-blog/CircularArc.mxml

    ReplyDelete
  4. I've written an update to the material presented here, it's "More About Approximating Circular Arcs With a Cubic Bezier Path" - http://hansmuller-flex.blogspot.com/2011/10/more-about-approximating-circular-arcs.html.

    ReplyDelete
  5. [quote]The derivation of the control points for an arc of less than 90 degrees is a little more complicated. If the arc is centered around the X axis, then the length of the tangent line is r * tan(a/2), instead of just r. The magnitude of the vector from each arc endpoint to its control point is k * r * tan(a/2).[/quote]

    It`s wrong as if a = 90 degrees formula is not reduced to well known case. Actually magnitude is equal to k1 * r, where k1 = 4/3 * (1 - cos(a/2)) / sin(a/2), for a = 90 it reduced to 4/3 * (sqrt(2) - 1).

    ReplyDelete
  6. Hi,
    What platform do i need to check and use this method?
    I've been using pro-engineer to create bezier from equations.
    I calculated the control points manually and tested in pro-e but the curve is nowhere near a circular arc.

    I have been trying to construct a circular arc using cubic Bezier. I referred a research article written by RISKUS A. in 2006. If the circle center point is at (o,o) it gives an arc approximation. But when i want to plot an arc using cubic bezier on an arbitrary center then it does not work.

    My bezier is formed somewhere else only.

    I tried all three of his methods using kappa 'K' and 'K2". Although values of both come to same only.

    Please guide.

    ReplyDelete
  7. Would it be possible to calculate the maximum deviation between this curve and a true radiused arc through 90 degrees? Would the location of this maximum deviation be at 30 degrees off one end (assuming a 90 degree arc)?

    ReplyDelete
  8. Thanks for the information, and a HUGE thanks to eugene-gff for providing the correct computation for the tangents. My demo code can be found at http://jsfiddle.net/blyon/4toxqjro/

    ReplyDelete
  9. Googling around I found the solution for approximating an elliptical arc with a cubic Bezier path at http://www.spaceroots.org/documents/ellipse/node22.html. The gist is as follows:

    The parametric equations of an ellipse are x = a * cos(t) and y = b * sin(t) or
    f(t) = (a * cos(t), b * sin(t))
    The tangent line is simply the first derivative of f with respect to t or
    f′(t) = (-a * sin(t), b * cos(t))
    So the control points for an elliptical arc starting at parametric angle s and ending at parametric angle e are
    c(s) = f(s) + alpha * f′(s)
    c(e) = f(e) - alpha * f′(e)
    where alpha is a scaler computed by
    delta = e - s; // the arc's parametric subtended angle
    tand = tan(delta / 2); // tangent of half the angle used below
    alpha = sin(delta) * (sqrt(4 + 3*tand*tand) - 1) / 3;

    As with previous solutions, arcs greater than 90° need to be segmented. This solution requires no rotation. If large arcs are equally segmented, then the same alpha applies to all segments. Humans rarely specify s and e using parametric arcs measures so typical arcs measures need to be converted to their parametric equivalents.

    I have a Javascript implementation at http://jsfiddle.net/blyon/jyb45uxt/

    ReplyDelete
  10. The arc's x1,y2 and x4,y4 end points - should actually be x1, y1 and x4, y4. I think its a typo.

    ReplyDelete
  11. In the code shared, createSmallArc what is the purpose of a = (a2 - a1) / 2.0; division by 2?

    Also in createArc why are we normalizing the angles (dividing by 2pi)?

    ReplyDelete
  12. With width and height = 300, radius = 100, beginangle=0 and endangle=90 the output is
    M :250, 150
    C 250, 156.929 248.608, 167.832 246.891, 174.74

    If you see the end point is really close to beginning point.

    Can some one please reply, what I am missing here.

    ReplyDelete