How to find the Curve Length in AS3

Recently when working on one of the features of Creately (an online collaborative diagram software) I had the need to calculate the curve length of a quadratic curve. The Flex/AS3 curves are quadratic (when you draw using the curveTo function of the Graphics class).The requirement I had was a pretty simple and straightforward one. However I didn’t find a simple straightforward piece of code that I could use to get this done. Of course there are frameworks such as Degrafa (an article on the topic) and Singularity libraries that allow you to achieve this very easily using those libraries itself. But if you are looking for just a function to get this done and not so worried about the accuracy of the calculation, the following should help. I’ve tried my best to explain this in the simplest for so that anyone can make use of it.

The most simple and naive method of calculating a curve length is to go through the curve in regular intervals and sum up the length of the line segments that are formed by the intervals. The accuracy of the curve length will vary by the frequency of the intervals. The higher the frequency the accurate and curve length would be, and the lower the frequency the more efficient your calculation will be. Depending on your requirement you can adjust the frequency when calculating the curve length. The following diagrams would further explain this (Please note that these diagrams are purely for the purpose of conceptualizing and are not accurate).

The above diagram explains the fundamental rule of a quadratic curve. Value t being a value between 0 and 1 can be used to find a point on the curve in relation to the full length of the curve. If t=0.5 (middle), the line interconnecting the middle point on line p1,cp and middle point of line cp,p2 will touch the the middle point of the curve at the middle of the formed line.

By deriving f number of regular intervals of t values (minimum being 1/f), we can form line segments along the curve that can be used to calculate the full length of  the curve. This can be done using the following two AS3 functions.

	
//Function that calculates the curve length
public function curveLength ( p1 : Point, p2 : Point, cp : Point,
	f : int ) : Number {

	var length : Number = 0;
	var step : Number = (1/f);
	var tx : Number;
	var lastPoint : Point;
	var curPoint : Point;

	for ( tx = step; tx < 1; tx += step ) {
		curPoint = interpolateCurve( p1, p2, cp, tx );
		if ( !lastPoint ) {
			length += Point.distance( p1, curPoint );
		} else {
			length += Point.distance( lastPoint, curPoint );
		}
		lastPoint = curPoint;
	}

	length += Point.distance( lastPoint, p2 );
	return length;
}

//Function to interpolate a curve on t
public function interpolateCurve ( p1 : Point, p2 : Point, cp : Point,
	t : Number ) : Point {

	var ret : Point;
	var x1 : Point = Point.interpolate( cp, p1, t );
	var x2 : Point = Point.interpolate( p2, cp, t );
	ret = Point.interpolate( x2, x1, t );

	return ret;
}

You can find a suitable f value for your purpose. To better understand this I have written a demo app that draws a given curve using both the above method and the Flex Graphics.curveTo method on top of each other. By setting different f values you can understand the difference between each curve visually. I have found 10-20 to be a decent value for f depending on the size of the curve. You can further play with this by downloading the below mxml file.

Download Curve Length Demo (Simply click on three points of the canvas to draw the curve )

Advertisements

8 thoughts on “How to find the Curve Length in AS3

  1. cheers for the functions they’re very helpful. I’m using it to calculate the length a sprite travels along beziers. I’m using tweenmax (Greensock) BezierThroughPlugin, what this does with the 3 points is a little different as the middle point (cp in your function) is on the curve not a control point at all. So using your function plots a much shallower curve than the interpolate function. Here’s an image that shows the curve the sprite follows (white line with 3 markers) – the tweenmax curve and the curve that is measured shown by lots of coloured markers (i set the steps to 50):

    http://twitpic.com/8b54iu

    is there a different way to interpolate the curve, or perhaps i can find a function within tweenmax that suits my needs. I’m automatically making a sprite move around the screen like a bee so that each curve joins onto the last with approximately the same angle, so tweenmax is very helpful for that…

  2. Hi amcc, thanks for your interest and comment. If I am understanding you right, what you need to do is interpolate a curve you have? the interpolateCurve function I have provided requires the cp (Control Point) of the curve and this point is not on the curve it is the control point that defines the curve. The first diagram explains this.

    From your image it seems to me that you are using a point on the curve as cp on my function which defines a whole other curve. all you will need to use is the actual control point of the curve to get this right.

    Please let me know if I understood you wrong. I’d try and help

    Best of luck

  3. thats exactly right, I am using points on the curve and am throwing them into your function. A simplified version of my code is below:

    //I make some random points and put them in arrays:
    xA=[xAStart,xASecond,xAThird];
    yA=[yAStart,yASecond,yAThird];

    //then do some tweening:
    TweenMax.to(obj,time,{bezierThrough:[{x:xA[1],y:yA[1]},{x:xA[2],y:yA[2]}]});

    /*
    the white curve in the picture is drawn as so and your curveLength function is used to spit out the lengths, but i’ve modified your function to use my arrays, this is a mistake i think – see below:
    */
    function drawCurve(xA, yA):void
    {
    trace(‘length = ‘ + curveLength ( xA, yA, 50 ));

    holder.graphics.lineStyle(1, 0xFFFFFF, 0.5);
    holder.graphics.moveTo(xA[0], yA[0]);

    var bezierObj:Object=BezierPlugin.parseBeziers({“x”:xA,”y”:yA},true);

    var pointCount:int=0;
    while(pointCount<bezierObj["x"].length)
    {
    holder.graphics.curveTo(bezierObj.x[pointCount][1],bezierObj.y[pointCount][1],bezierObj.x[pointCount][2],bezierObj.y[pointCount][2]);

    addMarker(xA[pointCount], yA[pointCount]);

    pointCount++;
    }
    trace('pointcounnt = ' + pointCount);
    addMarker(xA[xA.length -1], yA[yA.length -1]);
    }

    It looks like the tweenmax BezierPlugin.parseBeziers function might provide the answer to this, im just playing with it now. I've modified the curveLength function like so where xA and yA are arrays of x,y values:
    function curveLength ( xA, yA, f : int ) : Number {

    var p1:Point = new Point(xA[0], yA[0]);
    var p2:Point = new Point(xA[2], yA[2]);
    var cp:Point = new Point(xA[1], yA[1]);

    I think the only valid points here are p1 and p2.
    From the drawCurve function which i've got from the greenSock forums it seems like
    bezierObj.x[pointCount][1] and bezierObj.y[pointCount][1] (see above in the drawCurve function) are the control points. I'll try modifying what i've got and i'll post the whole thing up if you're interested.

    thanks

  4. Ok i’ve figured it out – if you’re using the TweenMax bezier tool to move things, which is very handy….

    xA, and yA are arrays of the points you want to pass through (can be as many as you like i believe. Then do this

            var bezierObj:Object=BezierPlugin.parseBeziers({"x":xA,"y":yA},true);
    
    	var pointCount:int=0;
    	while(pointCount<bezierObj["x"].length)
    	{
    		graphics.curveTo(bezierObj.x[pointCount][1],bezierObj.y[pointCount][1],bezierObj.x[pointCount][2],bezierObj.y[pointCount][2]);
    		
    		pointCount++;
    
                    // here's the curve length calculation
                    var p1 = new Point(bezierObj.x[pointCount][0],bezierObj.y[pointCount][0])
    		var p2 = new Point(bezierObj.x[pointCount][2],bezierObj.y[pointCount][2]);
    		var cp = new Point(bezierObj.x[pointCount][1],bezierObj.y[pointCount][1]);
    		curveLength ( p1, p2, cp, 20 );
    	}
    

    hope this helps someone

Comments are closed.