SmokeFX – An experiment with Bezier Curves

Andreas | 2010-08-26 | Flash, Tutorial

Since the dawn of the computer people has always created colourful art using curves. Either they have moved around on the screen or just being drawn with small adjustments creating mesmerizing patterns.
Being inspired by the latest PS3 boot menu I decided to create a nice looking (and dynamic) effect using Bezier curves. Here’s the treat:
ColourCurves


Now, from start, there is a problem here. AS3 doesn’t support more than one control point (via the curveTo() method). To be a able to draw nice continuous curves we need 2 control points (Cubic Bezier).

Luckily I don’t have to worry about that as I could find this great class taking care of all that math I lost out as a very happy but maybe not so focused student. Wonderful! Most of the work done already, let’s put it to use!

I decided to divide the whole effect into 3 classes.

WaveData – Holding all the data about the wave including controlpoint-position. The Class is also controlling the logical animation of the controlpoints + the colour of the wave, using our beloved tweenengine TweenMax.

Wave – Being the DisplayObject that is the actual visual representation of the WaveData (being a SINGLE wave that is)

Canvas – This is the area that will be visual on the screen. All Waves are draw to the canvas and a few pretty effects are added to the canvas. We’ll get to that.

Now let’s get started with the WaveData:

package
{
        import com.greensock.TweenMax;
        import com.greensock.easing.Sine;
        import flash.geom.Point;

        public class WaveData
        {
                public var controlPoints:int = 7
                public var minRadius:Number = 0.1;
                public var maxRadius:Number= 1;
                public var waveAlpha:Number = 0.08
                public var lineWidth:Number = 0;
                public var minSpeed:Number = 5;
                public var maxSpeed:Number = 8;
                public var color:int = 0;
                public var aPoints:Array       
               
                // no constructor needed. Best practise?? Naah, maybe not but hey, I get to write a comment instead of a constructor.
               
               
                // resets the wavedata and starts the animation of the controlpoints and the wavecolour.
                public function reset()
                {
                        var y:Number
                        var p:Point;
                        aPoints = [];
                       
                        // create all controlpoints and place them within the radius. They all starts at x:0
                        for (var i:int = 0;i<controlPoints;i++)
                        {      
                                y = 2*(Math.random()*(maxRadius-minRadius)+minRadius)-maxRadius;
                                p = new Point(0,y);
                                aPoints.push(p);
                                startAnim(p);
                        }
                        animColour();
                }
               
                private function animColour()
                {
                        var newColor = Math.random()*0xffffff;
                        TweenMax.to(this, 9, {hexColors:{color:newColor},onComplete:animColour});
                }
               
                private function startAnim(p:Point)
                {
                        // no, it’s not an optimized way to find the point but it’s a lab, remember? ;)
                        var ip:int = aPoints.indexOf(p);
                        var newY:Number = Math.random()*(maxRadius-minRadius)+minRadius
                        // x is being set "somewhat" around the section where it should be.
                        var newX:Number =  (ip==0)?0:((ip/(controlPoints-1)+(Math.random()*(1/(controlPoints-1))*4))-(2*(1/(controlPoints-1))));
                        // is the direction of the controlpoint going UP or DOWN?
                        if (p.y > 0) newY=-newY;
                        //animate!
                        TweenMax.to(p,(Math.random()*(maxSpeed-minSpeed))+minSpeed,{ease:Sine.easeInOut,y:newY,x:newX,onComplete:startAnim,onCompleteParams:[p]});
                }
        }
}

Pay attention to the fact that the controlpoints are just moving between 1 to -1 . This means that they are not having their physical width in here but will be multiplied later on in the Wave-class So let’s get going. Next class please!

package
{
        import flash.display.Sprite;
        import flash.events.Event;
        import flash.geom.Point;
       
        public class Wave extends Sprite
        {
                public var waveWidth:Number = 650;
                public var waveHeight:Number = 150;
                public var data:WaveData
               
                public function Wave()
                {
                        super();
                        // creates data and starts the animation
                        data = new WaveData();
                        data.reset();
                }
               
               
               
                public function update()
                {
                        // creating real coordinates from Wave-data controlpoints
                        var counter:int = 0;
                        var coord:Array = [];
                        for each(var p:Point in data.aPoints)
                        {
                                coord.push (new Point(p.x*waveWidth,p.y*waveHeight));;
                        }
                       
                        // draw curve
                        graphics.clear();
                        graphics.lineStyle(data.lineWidth,data.color,data.waveAlpha);
                        CubicBezier.curveThroughPoints(graphics,coord);
                }
        }
}

Now the wave is clearly a fully animated wave. If you add this one to the stage you will see a thin line waving around.. but we want more, right? Next class please!

package
{
        import flash.display.Bitmap;
        import flash.display.BitmapData;
        import flash.display.BlendMode;
        import flash.display.Sprite;
        import flash.events.Event;
        import flash.filters.BitmapFilter;
        import flash.filters.BlurFilter;
        import flash.geom.Matrix;
        import flash.geom.Point;
        import flash.geom.Rectangle;
       
        public class Canvas extends Sprite
        {
                private var bmpd:BitmapData
                private var aWaves:Array
                private var w:int
                private var h:int
               
                public function Canvas(w:int = 900,h:int = 450)
                {
                        super();
                       
                        // set the width and height of the canvas
                        this.w = w;
                        this.h = h;
                       
                        aWaves=[];
                       
                        // adding a bitmap to stage
                        bmpd = new BitmapData(w,h,false,0);
                        var bmp:Bitmap = new Bitmap(bmpd);
                        addChild(bmp);
                       
                }
               
                public function addWave(wave:Wave)
                {
                        // adding a wave to the canvas
                        aWaves.push(wave);
                }
               
                public function startDraw()
                {
                        addEventListener(Event.ENTER_FRAME,onTick)
                }
               
                private function onTick(e:Event)
                {
                        // each wave is now drawn onto the canvas using the ADD Blendmode.
                        for each ( var wave:Wave in aWaves)
                        {
                                wave.update();
                                var m:Matrix = new Matrix(1,0,0,1,26,bmpd.height*.5);
                                bmpd.draw(wave,m,null,BlendMode.ADD);
                        }
                       
                        // the canvas is now blurred out giving a blurred AND transitionout effect.
                        var filter:BlurFilter = new BlurFilter(3,3);
                        bmpd.applyFilter(bmpd, new Rectangle(0,0,w,h),new Point(0,0), filter);
                       
                        // the bitmapdata is now scrolled to the right giving a "moving feeling" to the effect.
                        bmpd.scroll(5,0)       
                }
        }
}

Wow, amazing what a few postfilters can do. Just a blur and a pixelpush to the right does the trick. Now let’s put this all together in a Main class and try it out:

package
{
        import flash.display.Sprite;
       
        public class Main extends Sprite
        {
                public function Main()
                {
                        var c:Canvas = new Canvas();
                       
                        // adding a few waves
                        for (var i:int = 0;i < 3;i++)
                        {
                                var w:Wave = new Wave();
                                c.addWave(w);
                        }
                       
                        addChild(c);
                        c.startDraw();
                }
        }
}

Tags: , , , , , , ,


Responses


  1. Nice!



  2. GREAT! :)



  3. You can get a performance boost, if u lock und unlock your bitmapdata before an after drawing ontick.

    bmpd.lock();
    //put waves in it, draw, applyfilter,…
    bmpd.unlock();

    Further optimizations:
    only one instance of BlurFilter is enough.
    Reuse your instances of Rectangle and Matrix.



LEAVE COMMENT