Understanding curves and control point placement

http://www.actionscript.org/resources/articles/172/1/Understanding-curves-and-control-point-placement/Page1.html

Patrick Mineault

Freelancer behind 5 1/2 math and physics enthusiast Patrick has a knack for making seemingly simple things overly complicated. Perfect for a tutorial writer.

By Patrick Mineault

Published on September 9, 2005
**Written by:** Patrick Mineault, http://www.5etdemi.com

**Difficulty Level:** Advanced

**Requirements: **MX and experience with actionscript

**Topics Covered:** Drawing API, curveTo, bezier curves

**Assumed Knowledge:** some actionscript, college math (algebra, trig and

some calculus)

**Written by:** Patrick Mineault, [email:patrick@5etdemi.com] , http://www.5etdemi.com

**Difficulty Level:** Advanced

**Requirements: **MX and experience with actionscript

**Topics Covered:** Drawing API, curveTo, bezier curves

**Assumed Knowledge:** some actionscript, college math (algebra, trig and

some calculus)### Understanding curves and control point placement

#### Degenerate cases

#### Application: sine curve

res = 100;

xscale = 100;

yscale = 100;

sine.lineStyle(1);

for(i =0; i <= res; i++)

{

var ang = 2*Math.PI*i/res;

sine.lineTo(xscale*ang, yscale*Math.sin(ang));

}

[/as]

res = 6;

xscale = 100;

yscale = 100;

sine.lineStyle(1);

var m1 = yscale/xscale;

var b1 = 0;

for(i = 1; i <= res; i++)

{

var ang = 2*Math.PI*i/res;

var si = Math.sin(ang);

var co = Math.cos(ang)

var m2 = yscale/xscale*co;

var b2 = yscale*(si - ang*co);

var x = (b1 - b2)/(m2 - m1);

var y = m2*x + b2;

if(Math.abs(m1 - m2) < 0.001)

{

sine.lineTo(xscale*ang, yscale*si);

}

else

{

sine.curveTo(x, y, xscale*ang, yscale*si);

}

m1 = m2;

b1 = b2;

}

[/as]#### Conclusion

some calculus)

Page 1 of 2

some calculus)

In a previous tutorial, Dynamic masking using the drawing API II (see related articles), I've introduced the use of control points in Flash curves to morph between different curves. In that context, it was useful to think of control points as "magnetic points" to which the curve is "attracted" to. In this tutorial, we will study Flash curves in a more systematic way in order to plot mathematically correct figures and functions. We'll then analyze a sine curve drawing tool which can be downloaded here.

Bezier curves are an integral part of vector-drawing applications such as Illustrator and Freehand. Bezier curves are a general type of spline, a mathematical curve determined by a certain number of control points. While most drawing applications use cubic Bezier curves, Flash uses quad Bezier curves, which use 3 control points instead of four.

Quad Bezier curves are useful at several levels in Flash. Curves that are drawn with the built-in cubic Bezier tool are converted to quad beziers when the curve is unselected. The method outlined here may be used with the **curveTo** drawing API function and also for the function by the same name in the JS extension API (for custom tool development).

Bezier curves are defined by a formula (which can seen in this excellent article) which takes a parameter u varying from 0 to 1 and spits out the intermediate points of the curve. As can be seen from the previous link, Bezier curves have this nice property:

"At u=0 the curve is tangent to the line (P0, P1) and at u=1, the curve is tangent to the line (PN, PN-1)"

Since quads use only three control points, that means **the second control point constrains the tangent of both the start point and the end point**.

This gives us a straightforward and general method for finding out the location of the middle control point in a curve that is made to fit a mathematical figure or curve.

1. Find the slope at the beginning and end points of the curve, using geometry or calculus

2. From the slopes, determine the equations of the tangent curves

3. Determine the intersection of the tangent curves in a single coordinate (x or y)

4. Use the equation of one of the tangents to find the location of the control point in the other coordinate

Before going any further, it's important to note that there are a few degenerate cases in which the preceding algorithm will fail to work properly. We'll have to take these into account. In the first case, the curve to be approximated crosses the line between the beginning and end points.

Here we have the curve to be approximated in blue, the tangents at the end point in red, the line joining p0 and p2 and the final curve in gray. As you see, since the curve to be approximated crosses the p0-p2 line, the results of the algorithm are erroneous. In fact, the results will be even worse when the tangents are close to parallel; in the extreme case of exactly parallel slopes, the algorithm will not find an intersection between the two tangents and will produce erratic results. These kinds of problems may also happen in the case of an almost straight line, where numeric instability can cause a straight line to be approximated with an extremely curved one, for example when slopes differ by as little as 10E-15.

In order for our algorithm to work correctly, we'll have to detect these cases and eliminate them. A sophisticated way to do so would be to split the curve in two parts and then draw a curve for each part. In our case, we'll assume that the resolution of the curve is already sufficient, and that we will only correct for cases where the two tangents are almost parallel. In these cases, we'll approximate the problematic curves with a straight line joining p0 and p2.

In addition, since beziers are parametric and not functional curves, it's entirely possible that one of the tangent slopes will be infinite. It will be important to detect and eliminate these cases where appropriate, for example if we want to draw a spiral, circle or lemniscate.

With this background in hand, we're ready to apply this method to create a sine curve.

Page 2 of 2

Keith Peters of bit-101 created a sine wave tool for Flash using the JSAPI a while ago using straight lines. He challenged the community to modify it to use curves. I came up with the following solution after elaborating the method outlined above. You may download the resulting tool here.

Here we analyze a script based on the tool, modified for ActionScript. The following will draw a single period of a sine wave:

[as]_root.createEmptyMovieClip('sine',0);res = 100;

xscale = 100;

yscale = 100;

sine.lineStyle(1);

for(i =0; i <= res; i++)

{

var ang = 2*Math.PI*i/res;

sine.lineTo(xscale*ang, yscale*Math.sin(ang));

}

[/as]

So let's see how we can modify this script to use curves instead of lines. First we have to find out what the slope of this curve is at any point. From calculus, you may remember that:

We may conclude immediately that the slope of the sine wave will be proportional to the cosine of the angle. Now the tricky bit here is that we are using a parametric and not functional notation; hence it's important to realize that the slope needs to be multiplied by **yscale/xscale**.

The generic equation for a straight line is:

Here, **m** is the slope and **b** is the coordinate at the origin. In our particular case, we know **m** is equal to **yscale/xscale*cos(ang)**. The line must go through the point (**x**,**y**) = (**xscale*ang**, **yscale*sin(ang)**) in order to be tangent to the curve. Putting these values into the equation for the straight line, we isolate the value of b:

Now that we know the values for **m** and **b** at any point, it's time to find the location of the control point on a segment of curve. We pick any two points p0 and p2 with tangent equations:

Equating **y1** and **y2**, we find that:

Plugging that value into either of the tangent equations, we find:

All that we need now is to transform these considerations into code:

[as]_root.createEmptyMovieClip('sine',0);res = 6;

xscale = 100;

yscale = 100;

sine.lineStyle(1);

var m1 = yscale/xscale;

var b1 = 0;

for(i = 1; i <= res; i++)

{

var ang = 2*Math.PI*i/res;

var si = Math.sin(ang);

var co = Math.cos(ang)

var m2 = yscale/xscale*co;

var b2 = yscale*(si - ang*co);

var x = (b1 - b2)/(m2 - m1);

var y = m2*x + b2;

if(Math.abs(m1 - m2) < 0.001)

{

sine.lineTo(xscale*ang, yscale*si);

}

else

{

sine.curveTo(x, y, xscale*ang, yscale*si);

}

m1 = m2;

b1 = b2;

}

[/as]

Voilà! What took a few pages of math analysis shrunk down to a 20-line script. It follows the algorithm outlined above very closely. A few notes: first, a resolution of 6 points is enough to get a very realistic curve. This is a great advance over the previous script which required close to 100 points to get a smooth curve. A sine curve is locally very close to a parabola; this is evidenced by the following Taylor series:

The coefficients fall off very rapidly, leaving the x^2 term dominant. Therefore it should not be surprising that the sine curve can be represented easily with few quadratic Bezier curves.

We considered the degenerate case of two parallel slopes, isolating the condition by checking if their difference is less than a very small amount. This is better than to check for actual equality between the two slopes as it is numerically stable and therefore handles a wider variety of cases.

We've covered control points in quad bezier curves systematically, allowing us to find a general method for locating control points on mathematical curves and figures. We've used this method to plot a sine curve accurately with only six points. You can now apply this method to a wide variety of cases, either in conjunction with the drawing API or with the JSAPI to create new and useful tools for Flash.

If you need professional help with Actionscript, Flash or JSAPI extension development, please visit my site 5etdemi.com for my portfolio and contact info. Happy flashing!