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.

4 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