In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

Sunny Clouds

-/+ to zoom
drag to rotate
Get Adobe Flash player
by zob 20 May 2011
/**
 * Copyright zob ( http://wonderfl.net/user/zob )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/rbwj
 */

package  {
    import flash.display.Sprite;
    import flash.display.MovieClip;
    import flash.events.Event;
    import flash.display.Shape;
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.geom.Matrix3D;
    import flash.geom.PerspectiveProjection;
    import flash.geom.Point;
    import flash.geom.Vector3D;
    import flash.utils.*;
    import flash.geom.Utils3D;
    import flash.display.TriangleCulling;
    import flash.display.Loader;
    import flash.net.URLRequest;
    import flash.system.LoaderContext;
    
    import flash.filters.BlurFilter;
    import flash.filters.GlowFilter;
    import flash.events.MouseEvent;
    import flash.events.KeyboardEvent;
    import net.hires.debug.Stats;
    import com.bit101.components.HSlider;
    import flash.display.Shader;
    import flash.filters.ShaderFilter;
    import flash.utils.ByteArray;
    import flash.geom.Matrix;
    import com.bit101.components.HUISlider;
    
    public class tunnelV02 extends Sprite
    {
        // terrain
        private var W              :int        = 40;//15;
        private var H              :int        = 50;//30;
        private var SW             :Number     = 800;
        private var SH             :Number     = 960;
        private var GW             :Number     = SW/W;
        private var GH             :Number     = SH/H;
        private var MW             :Number     = 80;
        private var MH             :Number     = 120;
        private var TW             :Number     = (MW)/W-1;
        private var TH             :Number     = (MH)/H-1;
        private var TERRIAN_MAP    :BitmapData = new BitmapData(MW, MH, false);
        private var MAP            :BitmapData;
        
        // 3D
        private var viewport       :Shape                 = new Shape();
        private var world          :Matrix3D              = new Matrix3D();
        private var projected      :Vector.<Number>       = new Vector.<Number>;
        private var projection     :PerspectiveProjection = new PerspectiveProjection();
        private var TRANS          :Matrix3D              = new Matrix3D();
        private var VERTS          :Vector.<Number>       = new Vector.<Number>;
        private var INDICES        :Vector.<int>          = new Vector.<int>;
        private var UVTS           :Vector.<Number>       = new Vector.<Number>;
        
        private var offset         :Point  = new Point();
        private var loader         :Loader;
        private var GRAPHIC_URL    :String = "http://assets.wonderfl.net/images/related_images/f/fd/fd61/fd61859a0846c3b23cba2abd8b6ff3d3e1b21b68m";//"ballt.jpg"; //cloud map
        
        
        // interaction
        private var dragging       :Boolean  = false;
        private var drag_mode      :Boolean  = false;
        private var old_mouse      :Point    = new Point();
        private var new_mouse      :Point    = new Point();
        private var trans          :Matrix3D = new Matrix3D();
        private var zoomer         :Number   = 750;
        private var zoomVelocity   :Number   = 0;
        private var zoomIn         :Boolean  = false;
        private var zoomOut        :Boolean  = false;
        
        // settings
        private var baseX   :Number = 15;
        private var baseY   :Number = 30;
        private var Octaves :Number = 8;
        private var speed   :Number = 0.5;
        
        private var baseXHSlider   :HUISlider;
        private var baseYHSlider   :HUISlider;
        private var octavesHSlider :HUISlider;
        private var speedHSlider   :HUISlider;
        
        // sunshine :)
        private var matrix    :Matrix3D = new Matrix3D();
        private var SCALE     :Number       = 4; // scale bitmap, less quality for light, but save processing
        private var pbjLoader :Loader;
        private var shine     :ShaderFilter;
        private var innerMap  :BitmapData   = new BitmapData(stage.stageWidth/SCALE, stage.stageHeight/SCALE, true, 0x0);
        private var light     :Vector3D     = new Vector3D(0,0.1,0);
        private var bitmapM   :Matrix       = new Matrix();
        
        private var fx:VolumetricPointLight;
        private var _t:Number = 0;
        private var _halfWidth:uint;
        private var _halfHeight:uint;

        
        public function tunnelV02()
        {
            loader = new Loader();
            loader.contentLoaderInfo.addEventListener(Event.COMPLETE, init);
            loader.load(new URLRequest(GRAPHIC_URL), new LoaderContext(true));
        }
        
        private function init(e:Event):void
        {
            // constructor code
            var mouse:MovieClip = new MovieClip();
            mouse.scaleX = SCALE;
            mouse.scaleY = SCALE;
            //mouse.addChild(new Bitmap(innerMap));
            
            //mouse.x = (stage.stageWidth-innerMap.width)/2;
            //mouse.y = (stage.stageHeight-innerMap.height)/2;
            viewport.x = stage.stageWidth / 2;
            viewport.y = stage.stageHeight / 2;

            //addChild(viewport);
            //viewport.filters = [shine];  // too lag
            buildPath();
            
            MAP = Bitmap(loader.content).bitmapData;
            bitmapM.translate(stage.stageWidth/2, stage.stageHeight/2);
            bitmapM.scale(1/SCALE, 1/SCALE);
            matrix.appendRotation(30, Vector3D.X_AXIS);
            trans.appendRotation(120, Vector3D.X_AXIS);
            trans.appendRotation(25, Vector3D.Y_AXIS);
            
            var panel:Sprite = new Sprite();
            addChild(mouse);
            addChild(panel);
            addChild(new Stats());
            baseXHSlider = new HUISlider(panel, 80, 20, "baseX", updateBaseX);
            baseXHSlider.value = baseX;
            baseYHSlider = new HUISlider(panel, 80, 35, "baseY", updateBaseY);
            baseYHSlider.value = baseY;
            octavesHSlider = new HUISlider(panel, 80, 50, "Octaves", updateOctaves);
            octavesHSlider.value = (Octaves-1) * 10;
            speedHSlider = new HUISlider(panel, 80, 65, "speed", updateSpeed);
            speedHSlider.value = speed * 10;
            
            mouse.addEventListener(MouseEvent.MOUSE_DOWN, onmouseDown);
            stage.addEventListener(MouseEvent.MOUSE_UP, onmouseUp);
            stage.addEventListener(MouseEvent.MOUSE_MOVE, onmouseMove);
            stage.addEventListener(KeyboardEvent.KEY_DOWN, onkeyDown);
            stage.addEventListener(KeyboardEvent.KEY_UP, onkeyUp);
            stage.addEventListener(Event.ENTER_FRAME, processing);
            loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, init);
            var w:int = stage.stageWidth;
            var h:int = stage.stageHeight;
            _halfWidth = w >> 1;
            _halfHeight = h >> 1;
            //var tempSprite:Sprite = new Sprite();
            //tempSprite.addChild(innerMap);
            fx = new VolumetricPointLight(465, 465, new Bitmap(innerMap),[0xEFBFAA,0xFBECDF,0xffffff, 0xffffff, 0xffffff],[1,0.95,0.6,0.3,0.0]);//, 0x46497c]);//[0x6a4b00,0x000f13,0x0, 0x130013, 0x0]
            mouse.addChild(fx);
            fx.startRendering();
        }
        
        private function updateBaseX(...arg):void
        {
            baseX = baseXHSlider.value;
        }
        
        private function updateBaseY(...arg):void
        {
            baseY = baseYHSlider.value;
        }
        
        private function updateOctaves(...arg):void
        {
            Octaves = 1+(octavesHSlider.value/10)/10*9;
        }
        
        private function updateSpeed(...arg):void
        {
            speed = (speedHSlider.value/10);
        }
        
        private function buildPath():void
        {
            
            var w:int = 0;
            var h:int = 0;
            var xA    :Number = 0;
            var xB    :Number = 0;
            var yA    :Number = 0;
            var yB    :Number = 0;
            var zA    :Number = 0;
            var zB    :Number = 0;
            var index:int = 0;
            var i0:int = 0;
            var i1:int = 0;
            var i2:int = 0;
            var i3:int = 0;
            for(h = 0; h < H; h++)
            {
                for(w = 0; w < W; w++)
                {
                    xA = -SW/2 + w*GW;
                    xB = -SW/2 + (w+1)*GW;
                    zA = -SH/2 + h*GH;
                    zB = -SH/2 + (h+1)*GH;
                    
                    VERTS.push(
                        xA, yA, zB//, //0        //0
                        //xB, yA, zB, //1        //1
                        //xA, yB, zA, //2        //2
                        //xB, yB, zA  //3        //3
                    );
                    
//                    index = (W*h + w) * 4;
//                    i0 = index;
//                    i1 = index +  1;
//                    i2 = index +  2;
//                    i3 = index +  3;
                    if(w+1 < W && h+1 < H)
                    {
                        index = (W*h + w);
                        i0 = (W*h + w);
                        i1 = (W*h + (w+1));
                        i2 = (W*(h+1) + w);
                        i3 = (W*(h+1) + (w+1));
                        
                        INDICES.push( 
                            i0,  i1,  i2, // 3
                            i1,  i3,  i2  // 6
                        );
                    }

                    UVTS.push(
                        ((w)  *TW+1)/MW, ((h)*TH)/MH, 0//,
                        //((w+1)*TW+1)/MW, ((h+1)*TH)/MH, 0, 
                        //((w)  *TW+1)/MW, ((h)  *TH)/MH, 0,
                        //((w+1)*TW+1)/MW, ((h)  *TH)/MH, 0
                    ); // redundant
                }
            }
        }
        
        private function processing(e:Event):void
        {
            
            _t += .01 * Math.random();
            var px:Number = _halfWidth * (1 + Math.cos(4 * _t));
            var py:Number = _halfHeight * (1 + Math.sin(5 * _t));
            //fx.srcX = px;
            //fx.srcY = py;
            if(zoomIn) zoomVelocity+=1;
            if(zoomOut) zoomVelocity-=1;
            zoomVelocity *= 0.98;
            zoomer += zoomVelocity;
            
            TERRIAN_MAP.perlinNoise(baseX,baseY,Octaves,100, false, true, 8, true, [offset, offset]);
            
            var w:int = 0;
            var h:int = 0;
            var q:int = 0;
            var k:int = 0;
            var r:Number = 0;
            var index:int = 0;
            var i0:int = 0;
            var i1:int = 0;
            var i2:int = 0;
            var i3:int = 0;
            
            var VX:Number = 0;
            var VY:Number = 0;
            var VZ:Number = 0;
            var VLEN:Number = 0;
            
            var ViX:Number = 0;
            var ViY:Number = 0;
            var ViZ:Number = 0;
            
            for(h = 0; h < H; h++)
            {
                for(w = 0; w < W; w++)
                {
                    r = TERRIAN_MAP.getPixel((w*TW), (h)*TH) & 0xFF;
                    index = (W*h + w)*3;
                    ViX = VERTS[index];
                    ViY = -130 + 200 * (r/0xFF);
                    ViZ = VERTS[index+2];
                    VERTS[index+1] = ViY;
                    VX = ViX;
                    VY = ViY/Math.abs(ViY) * (ViY * ViY);
                    VZ = ViZ;
                    VLEN = Math.sqrt(VX*VX+VY*VY+VZ*VZ);
                    UVTS[index] = 0.5+VX/VLEN*0.5;
                    UVTS[index+1] = 0.5+VY/VLEN*0.5;
                }
            }
            
            offset.y-=speed;

            world.identity();
            world.append(trans);
            var nv:Vector3D = world.transformVector(light);
            world.appendTranslation(0, 0, zoomer);
            world.append(projection.toMatrix3D());
            
            fx.srcX = nv.x*850+innerMap.width/2;
            fx.srcY = nv.y*850+innerMap.height/2;
            
            var _UVTS:Vector.<Number> = new Vector.<Number>;
            matrix.transformVectors(UVTS,_UVTS);
            
            Utils3D.projectVectors(world, VERTS, projected, _UVTS);
            
            viewport.graphics.clear();
            
            viewport.graphics.beginBitmapFill(MAP, null, false, false);
            viewport.graphics.drawTriangles(projected, INDICES, _UVTS, TriangleCulling.NONE);  //NEGATIVE
            //viewport.graphics.drawTriangles(projected, INDICES, _UVTS, TriangleCulling.NEGATIVE);  //NEGATIVE
            viewport.graphics.endFill();
            
            innerMap.fillRect(innerMap.rect, 0x0);
            innerMap.draw(viewport, bitmapM);
            //innerMap.applyFilter (innerMap, innerMap.rect, innerMap.rect.topLeft, shine);
            //innerMap.threshold(innerMap, innerMap.rect, new Point, ">=", 0xFDF6D3, 0x0, 0xFFFFFF, true);//0xFDE6B3//
        }
        
        private function onmouseDown(e:MouseEvent):void
        {
            old_mouse.x = mouseX;
            old_mouse.y = mouseY;
            dragging = true;
        }
        
        private function onmouseMove(e:MouseEvent):void
        {
            if(dragging)
            {
                
                new_mouse.x = mouseX
                new_mouse.y = mouseY;
                
                var difference:Point = new_mouse.subtract(old_mouse);
                var vector:Vector3D = new Vector3D(difference.x, difference.y, 0);
 
                var rotationAxis:Vector3D = vector.crossProduct(new Vector3D(0,0,1));
                rotationAxis.normalize();
 
                var distance:Number = Point.distance(new_mouse, old_mouse);
                var rotationMatrix:Matrix3D = new Matrix3D();
                rotationMatrix.appendRotation(distance, rotationAxis);
                
                trans.append(rotationMatrix);
 
                old_mouse.x = new_mouse.x;
                old_mouse.y = new_mouse.y;
            }
        }
        
        private function onmouseUp(e:MouseEvent):void
        {
            dragging = false;
        }
        
        private function onkeyDown(e:KeyboardEvent):void
        {
            switch(e.keyCode)
            {
                case 107:
                    zoomIn = true;
                    break;
                case 189:
                    zoomOut = true;
                    break;
            }
            
        }
        
        private function onkeyUp(e:KeyboardEvent):void
        {
            switch(e.keyCode)
            {
                case 107:
                    zoomIn = false;
                    break;
                case 189:
                    zoomOut = false;
                    break;
            }
            
        }
    }
    
}

import flash.display.*;
import flash.events.*;
import flash.filters.*;
import flash.geom.*;

/**
* The EffectContainer class creates a volumetric light effect (also known as crepuscular or "god" rays).
* This is done in 2D with some bitmap processing of an emission object, and optionally an occlusion object.
*/
class EffectContainer extends Sprite {
    /**
    * When true a blur filter is applied to the final effect bitmap (can help when colorIntegrity == true).
    */
    public var blur:Boolean = false;
    /**
    * Selects rendering method; when set to true colors won't be distorted and performance will be
    * a little worse. Also, this might make the final output appear grainier.
    */
    public var colorIntegrity:Boolean = false;
    /**
    * Light intensity.
    */
    public var intensity:Number = 5;
    /**
    * Number of passes applied to buffer. Lower numbers mean lower quality but better performance,
    * anything above 8 is probably overkill.
    */
    public var passes:uint = 6;
    /**
    * Set this to one of the StageQuality constants to use this quality level when drawing bitmaps, or to
    * null to use the current stage quality. Mileage may vary on different platforms and player versions.
    * I think it should only be used when stage.quality is LOW (set this to BEST to get reasonable results).
    */
    public var rasterQuality:String = null;
    /**
    * Final scale of emission. Should always be more than 1.
    */
    public var scale:Number = 4;
    /**
    * Smooth scaling of the effect's final output bitmap.
    */
    public var smoothing:Boolean = true;
    /**
    * Light source x.
    * @default viewport center (set in constructor).
    */
    public var srcX:Number;
    /**
    * Light source y.
    * @default viewport center (set in constructor).
    */
    public var srcY:Number;

    protected var _blurFilter:BlurFilter = new BlurFilter(2, 2);
    protected var _emission:DisplayObject;
    protected var _occlusion:DisplayObject;
    protected var _ct:ColorTransform = new ColorTransform;
    protected var _halve:ColorTransform = new ColorTransform(0.5, 0.5, 0.5);
    protected var _occlusionLoResBmd:BitmapData;
    protected var _occlusionLoResBmp:Bitmap;
    protected var _baseBmd:BitmapData;
    protected var _bufferBmd:BitmapData;
    protected var _lightBmp:Bitmap = new Bitmap;
    protected var _bufferSize:uint = 0x8000;
    protected var _bufferWidth:uint;
    protected var _bufferHeight:uint;
    protected var _viewportWidth:uint;
    protected var _viewportHeight:uint;
    protected var _mtx:Matrix = new Matrix;
    private var cT:ColorTransform =  new ColorTransform(1,1,1,0.96);
    /**
    * Creates a new effect container.
    *
    * @param width Viewport width in pixels.
    * @param height Viewport height in pixels.
    * @param emission A DisplayObject to which the effect will be applied. This object will be
    * added as a child of the container. When applying the effect the object's filters and color
    * matrix is ignored, if you want to use filters or a color matrix put your content in another
    * object and addChild it to this one instead.
    * @param occlusion An optional occlusion object, handled the same way as the emission object.
    */
    public function EffectContainer(width:uint, height:uint, emission:DisplayObject, occlusion:DisplayObject = null) {
        if(!emission) throw(new Error("emission DisplayObject must not be null."));
        addChild(_emission = emission);
        //emission.alpha = 0.3;
           if(occlusion) addChild(_occlusion = occlusion);
        //_occlusion = occlusion;
        setViewportSize(width, height);
        _lightBmp.blendMode = BlendMode.ADD;
        addChild(_lightBmp);
    //    addChild(Bitmap(_baseBmd));
        srcX = width / 2;
        srcY = height / 2;
    }

    /**
    * Sets the container's size. This method recreates internal buffers (slow), do not call this on
    * every frame.
    *
    * @param width Viewport width in pixels
    * @param height Viewport height in pixels
    */
    public function setViewportSize(width:uint, height:uint):void {
        _viewportWidth = width;
        _viewportHeight = height;
        scrollRect = new Rectangle(0, 0, width, height);
        _updateBuffers();
    }

    /**
    * Sets the approximate size (in pixels) of the effect's internal buffers. Smaller number means lower
    * quality and better performance. This method recreates internal buffers (slow), do not call this on
    * every frame.
    *
    * @param size Buffer size in pixels
    */
    public function setBufferSize(size:uint):void {
        _bufferSize = size;
        _updateBuffers();
    }

    protected function _updateBuffers():void {
        var aspect:Number = _viewportWidth / _viewportHeight;
        _bufferHeight = Math.max(1, Math.sqrt(_bufferSize / aspect));
        _bufferWidth  = Math.max(1, _bufferHeight * aspect);
        dispose();
        _baseBmd           = new BitmapData(_bufferWidth, _bufferHeight, false, 0);
        _bufferBmd         = new BitmapData(_bufferWidth, _bufferHeight, false, 0);
        _occlusionLoResBmd = new BitmapData(_bufferWidth, _bufferHeight, true, 0);
        _occlusionLoResBmp = new Bitmap(_occlusionLoResBmd);
    }

    /**
    * Render a single frame.
    *
    * @param e In case you want to make this an event listener.
    */
    public function render(e:Event = null):void {
        var savedQuality:String = stage.quality;
        if(rasterQuality) stage.quality = rasterQuality;
        var mul:Number = colorIntegrity ? intensity : intensity/(1<<passes);
        _ct.redMultiplier = _ct.greenMultiplier = _ct.blueMultiplier = mul;
        //_drawLoResEmission();
        //if(_occlusion) _eraseLoResOcclusion();
        
        _copyMatrix(_emission.transform.matrix, _mtx);
        _mtx.scale(_bufferWidth / _viewportWidth, _bufferHeight / _viewportHeight);
        _baseBmd.fillRect(_baseBmd.rect, 0x0);
        _baseBmd.draw(_occlusion, _mtx);
        _baseBmd.threshold(_baseBmd, _baseBmd.rect, new Point, "<", 0xFDF6D3, 0x0, 0xFFFFFF, true);
        _baseBmd.colorTransform(_baseBmd.rect, _ct);
        
        if(rasterQuality) stage.quality = savedQuality;
        var s:Number = 1 + (scale-1) / (1 << passes);
        var tx:Number = srcX/_viewportWidth*_bufferWidth;
        var ty:Number = srcY/_viewportHeight*_bufferHeight;
        _mtx.identity();
        _mtx.translate(-tx, -ty);
        _mtx.scale(s, s);
        _mtx.translate(tx, ty);
        _lightBmp.bitmapData = _applyEffect(_baseBmd, _bufferBmd, _mtx, passes);
        _lightBmp.width = _viewportWidth;
        _lightBmp.height = _viewportHeight;
        _lightBmp.smoothing = smoothing;
    }

    /**
    * Draws a scaled-down emission on _baseBmd.
    */
    protected function _drawLoResEmission():void {
        _copyMatrix(_emission.transform.matrix, _mtx);
        _mtx.scale(_bufferWidth / _viewportWidth, _bufferHeight / _viewportHeight);
        _baseBmd.fillRect(_baseBmd.rect, 0x0);
        _baseBmd.draw(_emission, _mtx, colorIntegrity ? null : _ct);
    }

    /**
    * Draws a scaled-down occlusion on _occlusionLoResBmd and erases it from _baseBmd.
    */
    protected function _eraseLoResOcclusion():void {
        _occlusionLoResBmd.fillRect(_occlusionLoResBmd.rect, 0x0);
        _copyMatrix(_occlusion.transform.matrix, _mtx);
        _mtx.scale(_bufferWidth / _viewportWidth, _bufferHeight / _viewportHeight);
        _occlusionLoResBmd.draw(_occlusion, _mtx);
        _baseBmd.draw(_occlusionLoResBmp, null, null, BlendMode.ERASE);
    }

    /**
    * Render the effect on every frame until stopRendering is called.
    */
    public function startRendering():void {
        addEventListener(Event.ENTER_FRAME, render);
    }

    /**
    * Stop rendering on every frame.
    */
    public function stopRendering():void {
        removeEventListener(Event.ENTER_FRAME, render);
    }

    /**
    * Low-level workhorse, applies the lighting effect to a bitmap. This function modifies the src and buffer
    * bitmaps and it's mtx argument.
    *
    * @param src The BitmapData to apply the effect on.
    * @param buffer Another BitmapData object for temporary storage. Must be the same size as src.
    * @param mtx Effect matrix.
    * @param passes Number of passes to make.
    * @return A processed BitmapData object (supllied in either src or buffer) with final effect output.
    */
    protected function _applyEffect(src:BitmapData, buffer:BitmapData, mtx:Matrix, passes:uint):BitmapData {
        var tmp:BitmapData;
        while(passes--) {
            if(colorIntegrity) src.colorTransform(src.rect, _halve);
            buffer.copyPixels(src, src.rect, src.rect.topLeft);
            buffer.draw(src, mtx, null, BlendMode.ADD, null, true);
            mtx.concat(mtx);
            tmp = src; src = buffer; buffer = tmp;
        }
        if(colorIntegrity) src.colorTransform(src.rect, _ct);
        if(blur) src.applyFilter(src, src.rect, src.rect.topLeft, _blurFilter);
        return src;
    }

    /**
    * Dispose of all intermediate buffers. After calling this the EffectContainer object will be unusable.
    */
    public function dispose():void {
        if(_baseBmd) _baseBmd.dispose();
        if(_occlusionLoResBmd) _occlusionLoResBmd.dispose();
        if(_bufferBmd) _bufferBmd.dispose();
        _baseBmd = _occlusionLoResBmd = _bufferBmd = _lightBmp.bitmapData = null;
    }

    protected function _copyMatrix(src:Matrix, dst:Matrix):void {
        dst.a = src.a;
        dst.b = src.b;
        dst.c = src.c;
        dst.d = src.d;
        dst.tx = src.tx;
        dst.ty = src.ty;
    }
}

import flash.display.*;
import flash.events.*;
import flash.geom.*;

/**
* VolumetricPointLight creates a simple effect container with a gradient emission pattern.
* The gradient's center is automatically moved to the (srcX, srcY) coordinates
* and it's radius is adjusted to the length of the viewport's diagonal, so if you
* set srcX and srcY to the viewport's center then only half of the gradient colors
* will be used.
*
* <p>This should also perform a little better than EffectContainer.</p>
*/
class VolumetricPointLight extends EffectContainer {
    protected var _colors:Array;
    protected var _alphas:Array;
    protected var _ratios:Array;
    protected var _gradient:Shape = new Shape;
    protected var _gradientMtx:Matrix = new Matrix;
    protected var _gradientBmp:Bitmap = new Bitmap;
    protected var _lastSrcX:Number;
    protected var _lastSrcY:Number;
    protected var _lastIntensity:Number;
    protected var _lastColorIntegrity:Boolean = false;
    protected var _gradientLoResBmd:BitmapData;
    protected var _gradientLoResDirty:Boolean = true;

    /**
    * Creates a new effect container, with an emission created from the supplied color or gradient.
    * The constructor lets you use a shortcut syntax for creating simple single-color gradients.
    * @example The shortcut syntax:
    * <listing>new VolumetricPointLight(800, 600, occlusion, 0xc08040);</listing>
    * @example is equivalent to:
    * <listing>new VolumetricPointLight(800, 600, occlusion, [0xc08040, 0], [1, 1], [0, 255]);</listing>
    *
    * @param width Viewport width in pixels.
    * @param height Viewport height in pixels.
    * @param occlusion An occlusion object, will be overlayed above the lighting gradient and under the light effect bitmap.
    * @param colorOrGradient Either a gradient colors array, or a uint color value.
    * @param alphas Will only be used if colorOrGradient is an array. This will be passed to beginGradientFill.
    *               If not provided alphas will all be 1.
    * @param ratios Will only be used if colorOrGradient is an array. This will be passed to
    *               beginGradientFill. If colorOrGradient is an Array and ratios aren't provided default
    *               ones will be created automatically.
    */
    public function VolumetricPointLight(width:uint, height:uint, occlusion:DisplayObject, colorOrGradient:*, alphas:Array = null, ratios:Array = null) {
        if(colorOrGradient is Array) {
            _colors = colorOrGradient.concat();
            if(!ratios)    _ratios = colorOrGradient.map(function(item:*, i:int, arr:Array):int { return 0x100*i/(colorOrGradient.length+i-1) });
            if(!alphas) _alphas = _colors.map(function(..._):Number { return 1 }); else _alphas = alphas;
        } else {
            _colors = [colorOrGradient, 0];
            _ratios = [0, 255];
        }
        
        super(width, height, _gradientBmp, occlusion);
        if(!occlusion) throw(new Error("An occlusion DisplayObject must be provided."));
        if(!(colorOrGradient is Array || colorOrGradient is uint)) throw(new Error("colorOrGradient must be either an Array or a uint."));
    }

    protected function _drawGradient():void {
        var size:Number = 2 * Math.sqrt(_viewportWidth*_viewportWidth + _viewportHeight*_viewportHeight);
        _gradientMtx.createGradientBox(size, size, 0, -size/2 + srcX, -size/2 + srcY);
        _gradient.graphics.clear();
        _gradient.graphics.beginGradientFill(GradientType.RADIAL, _colors, _alphas, _ratios, _gradientMtx);
        _gradient.graphics.drawRect(0, 0, _viewportWidth, _viewportHeight);
        _gradient.graphics.endFill();
        if(_gradientBmp.bitmapData) _gradientBmp.bitmapData.dispose();
        _gradientBmp.bitmapData = new BitmapData(_viewportWidth, _viewportHeight, true, 0);
        _gradientBmp.bitmapData.draw(_gradient);
    }

    /**
    * Updates the lo-res gradient bitmap if neccesary and copies it to _baseBmd.
    */
    override protected function _drawLoResEmission():void {
        if(_gradientLoResDirty) {
            super._drawLoResEmission();
            _gradientLoResBmd.copyPixels(_baseBmd, _baseBmd.rect, _baseBmd.rect.topLeft);
            _gradientLoResDirty = false;
        } else {
            _baseBmd.copyPixels(_gradientLoResBmd, _baseBmd.rect, _baseBmd.rect.topLeft);
        }
    }

    /** @inheritDoc */
    override protected function _updateBuffers():void {
        super._updateBuffers();
        _gradientLoResBmd = new BitmapData(_bufferWidth, _bufferHeight, false, 0x0);
        _gradientLoResDirty = true;
    }

    /** @inheritDoc */
    override public function setViewportSize(width:uint, height:uint):void {
        super.setViewportSize(width, height);
        _drawGradient();
        _gradientLoResDirty = true;
    }

    /** @inheritDoc */
    override public function render(e:Event = null):void {
        var srcChanged:Boolean = _lastSrcX != srcX || _lastSrcY != srcY;
        if(srcChanged) _drawGradient();
        _gradientLoResDirty ||= srcChanged;
        _gradientLoResDirty ||= (!colorIntegrity && (_lastIntensity != intensity));
        _gradientLoResDirty ||= (_lastColorIntegrity != colorIntegrity);
        _lastSrcX = srcX;
        _lastSrcY = srcY;
        _lastIntensity = intensity;
        _lastColorIntegrity = colorIntegrity;
        super.render(e);
    }
}