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.
(Click anywhere to create a wave)
Please note that the library hasn’t undergone the rigorous optimisations that everyone is writing articles on, we’ll probably wait till we convert it to FP10.
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")]
public class WaveTest extends Sprite
{
//Math
private var theta:Number = 0;
private var buffer:Array = new Array();
private var renderBuffer:int = 0;
private var k1:Number;
private var k2:Number;
private var k3:Number;
private var path:Path;
//Common
private var spacing:int = 70;
private var waterSegsX:int;
private var waterSegsXreal:int;
private var waterXcenter:int;
private var isPause:Boolean = false;
public function WaveTest(){
path = new Path();
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);
}
private function onMouseClick(e:Event):void{
makeSplash(((stage.stageHeight/2)-mouseY)/2, mouseX/stage.stageWidth);
}
public function makeSplash(impact:Number, fract:Number):void{
var index:int = Math.floor(fract*waterSegsXreal);
if(index==waterSegsXreal)index = waterSegsX;
buffer[renderBuffer][index].y += impact;
}
public function 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;
}
public function populateBuffer():void{
var x:int,y:int,z:int;
for(var i:int = 0 ; i < 2; i++){
var a:Array = new Array();
for(var k:int = 0 ; k < waterSegsXreal; k++)
a.push(new Point(k,0));
buffer[i] = a;
}
}
private function 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] as Point;
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;
}
private function 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 = new BezierPoint((x/(waterSegsXreal-1))*stage.stageWidth,(stage.stageHeight/2)+y);
if(x==0){
point.backwardDistance = 0;
point.backwardAngle = 0;
}else if(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();
}
}
}
}