Recently we needed to do some animated curves in AS3, and after searching around for some time we found several libraries for easily drawing bezier paths. The problem was they were all very slow, this being a result of them using the lineTo method for drawing (resulting in a curve made up of many tiny straight lines).

The benefits of the lineTo method (over the curveTo method) are that the resulting drawing can be very accurate, and that the maths used is simpler, neither of these were necessary for what we were doing.

We wrote our library with some goals, firstly, that it used the curveTo for all of it’s drawing. We also wanted to be able to construct paths without specifying control points, just the main points that the path passed through and a “tension” to describe the curve that gets drawn between.

The code for the bezier library is part of our Open Source Library

Here’s an example of the kind of thing that can be done easily with the library.

 

Here's the source:

package 
{
       
import flash.display.Sprite;
       
import flash.display.StageAlign;
       
import flash.display.StageScaleMode;
       
import flash.events.Event;
       
import flash.events.MouseEvent;
       
import flash.geom.Point;
       
       
import org.farmcode.bezier.BezierPoint;
       
import org.farmcode.bezier.Path;
       
   
[SWF(backgroundColor="#dfdfdd", frameRate="25", width="700", height="400")]
   
publicclassWaveTestextendsSprite
   
{
                       
//Math
           
privatevar theta:Number=0;
           
privatevar buffer:Array=newArray();
           
privatevar renderBuffer:int=0;
           
privatevar k1:Number;
           
privatevar k2:Number;
           
privatevar k3:Number;
           
           
privatevar path:Path;
                       
                       
//Common
                       
privatevar spacing:int=70;
                       
privatevar waterSegsX:int;
                       
privatevar waterSegsXreal:int;
                       
privatevar waterXcenter:int;
                       
privatevar isPause:Boolean=false;
                       
       
publicfunctionWaveTest(){
                path
=newPath();
                        path
.autoFillTension =0.2;
                       
//path.closed = true;
                       
                        stage
.scaleMode =StageScaleMode.NO_SCALE;
                        stage
.align =StageAlign.TOP_LEFT;
               
                        prepareMath
();
                        populateBuffer
();

                        addEventListener
(Event.ENTER_FRAME, onEnterFrame);
                        stage
.addEventListener(MouseEvent.CLICK, onMouseClick);
                       
       
}
               
privatefunction onMouseClick(e:Event):void{
                        makeSplash
(((stage.stageHeight/2)-mouseY)/2, mouseX/stage.stageWidth);
               
}
               
publicfunction makeSplash(impact:Number, fract:Number):void{
                       
var index:int=Math.floor(fract*waterSegsXreal);
                       
if(index==waterSegsXreal)index = waterSegsX;
                        buffer
[renderBuffer][index].y += impact;
               
}
               
publicfunction prepareMath():void{
                        waterSegsX
=Math.round(stage.stageWidth/spacing);
                        waterSegsXreal
= waterSegsX+1;
                        waterXcenter
= waterSegsX/2;
                       
                       
var weight:Number=10;
                       
var t:Number=0.3;
                       
var speedPerFrequency:Number=6;
                       
var decreaseRate:Number=0.1;

                       
var f1:Number= speedPerFrequency*speedPerFrequency*t*t/(weight*weight);
                       
var f2:Number=1/(decreaseRate*t+2);

                        k1
=(4-8*f1)*f2;
                        k2
=(decreaseRate*t-2)*f2;
                        k3
=2* f1 * f2;
               
}
               
               
publicfunction populateBuffer():void{
                       
var x:int,y:int,z:int;
                       
for(var i:int=0; i <2; i++){
                               
var a:Array=newArray();
                               
for(var k:int=0; k < waterSegsXreal; k++)
                                        a
.push(newPoint(k,0));
                                       
                                buffer
[i]= a;
                       
}
               
}
               
               
privatefunction evaluate():void{
                       
var crnt:Array= buffer[renderBuffer];
                       
var prev:Array= buffer[1-renderBuffer];
                       
for(var i:int=0; i < waterSegsXreal; i++){
                               
var currentP:Point=(Point)(buffer[renderBuffer][i]);
                               
var prevPoint:Point= prev[i]asPoint;
                               
var beforePoint:Point= crnt[i-1];
                               
var afterPoint:Point= crnt[i+1];
                                prevPoint
.y =
                                        k1
*((Point)(crnt[i])).y + k2*prevPoint.y +
                                        k3
*((afterPoint?afterPoint.y:0)+(beforePoint?beforePoint.y:0)+ currentP.y);
                       
}
                        renderBuffer
=1-renderBuffer;
               
}
               

       
privatefunction onEnterFrame(event:Event):void{
           
if(!isPause){
                                theta
+=0.05;
                               
var verCounter:uint=0;
                graphics
.clear();
                graphics
.beginFill(0x111120);
               
                               
var points:Array=[];
                                graphics
.moveTo(0,stage.stageHeight/2);
               
               
for(var x:int=0; x < waterSegsXreal ; x++){
                       
var y:Number= buffer[renderBuffer][x].y;
                       
var point:BezierPoint=newBezierPoint((x/(waterSegsXreal-1))*stage.stageWidth,(stage.stageHeight/2)+y);
                                       
if(x==0){
                                                point
.backwardDistance =0;
                                                point
.backwardAngle =0;
                                       
}elseif(x == waterSegsXreal-1){
                                                point
.forwardDistance =0;
                                                point
.forwardAngle =0;
                                       
}
                                        points
.push(point);
               
}
                               
                                path
.points = points;
                                path
.drawInto(graphics);
                               
                                graphics
.lineTo(stage.stageWidth,stage.stageHeight);
                                graphics
.lineTo(0,stage.stageHeight);
                                graphics
.endFill();
                               
                                evaluate
();
               
                       
}
       
}
   
}
}