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

Dynamic 2D Shadows

Inspired by:
http://www.catalinzima.com/2010/07/my-technique-for-the-shader-based-dynamic-2d-shadows/
/**
 * Copyright cjcat2266 ( http://wonderfl.net/user/cjcat2266 )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/foPH
 */

package 
{
    import com.bit101.components.PushButton;
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.Shape;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.filters.BlurFilter;
    import flash.geom.Matrix;
    import flash.geom.Point;
    
    /**
     * Inspired by:
     * http://www.catalinzima.com/2010/07/my-technique-for-the-shader-based-dynamic-2d-shadows/
     */
    public class Main extends Sprite 
    {
        
        private const PI                 :Number = Math.PI;
        private const TWO_PI             :Number = 2 * Math.PI;
        private const HALF_PI            :Number = 0.5 * Math.PI;
        private const QUARTER_PI         :Number = 0.25 * Math.PI;
        
        private const NUM_OBSTACLES      :int    = 10;
        private const OBSTACLE_SIZE      :Number = 50;
        private const OBSTACLE_SIZE_VAR  :Number = 15;
        private const LIGHT_RADIUS       :Number = 100;
        
        private const BLUR               :Number = 2;
        private const DIST_BLUR_FACTOR   :Number = 0.25;
        private const DIST_BLUR_THRESHOLD:Number = 27;
        
        private var resultBMP            :Bitmap;
        private var dataBMP              :Bitmap;
        private var obstacles            :Sprite;
        private var result               :BitmapData;
        private var resultBlurTemp       :BitmapData;
        private var obstacleMap          :BitmapData;
        private var distanceMap          :BitmapData;
        private var reducedDistanceMap   :BitmapData;
        
        public function Main() 
        {
            init();
            stage.addEventListener(MouseEvent.MOUSE_MOVE, mainLoop);
        }
        
        private function init():void 
        {
            
            result             = new BitmapData(150, 150, false, 0xFFFFFF);
            resultBlurTemp     = new BitmapData(150, 150, false, 0xFFFFFF);
            obstacleMap        = new BitmapData(150, 150, false, 0xFFFFFF);
            distanceMap        = new BitmapData(100, 100, false, 0xFFFFFF);
            reducedDistanceMap = new BitmapData(  1, 100, false, 0xFFFFFF);
            
            dataBMP = new Bitmap();
            addChild(dataBMP);
            
            resultBMP = new Bitmap(result);
            resultBMP.scaleX =  stage.stageWidth / result.width;
            resultBMP.scaleY =  stage.stageHeight / result.height;
            addChild(resultBMP);
            
            obstacles = new Sprite();
            //addChild(obstacles);
            
            new PushButton(this, 10, 10, "Regenerate Obstacles", regenerateObstacles);
            new PushButton(this, 10, 30, "Result",          showResult);
            new PushButton(this, 10, 50, "Obstacle Map",    showObstacleMap);
            new PushButton(this, 10, 70, "Distance Map",    showDistanceMap);
            new PushButton(this, 10, 90, "Reduced Distance Map",    showReducedDistanceMap);
            
            regenerateObstacles();
        }
        
        private function mainLoop(e:Event = null):void 
        {
            result.lock();
            distanceMap.lock();
            reducedDistanceMap.lock();
            
            //pre-processing
            updateDistanceMap();
            reduceDistanceMap();
            
            //rendering
            result.fillRect(result.rect, 0xFFFFFF);
            renderLight();
            blurLight();
            renderObstacles();
            
            result.unlock();
            distanceMap.unlock();
            reducedDistanceMap.unlock();
        }
        
        private function updateDistanceMap():void 
        {
            distanceMap.fillRect(distanceMap.rect, 0xFFFFFF);
            
            var mx:Number = stage.mouseX * obstacleMap.width / stage.stageWidth;
            var my:Number = stage.mouseY * obstacleMap.height / stage.stageHeight;
            
            for (var j:int = 0; j < distanceMap.height; ++j)
            {
                for (var i:int = 0; i < distanceMap.width; ++i)
                {
                    //polar to cartesian
                    var r:Number = LIGHT_RADIUS * (i / distanceMap.width);
                    var t:Number = TWO_PI * (j/ distanceMap.height);
                    var cx:Number = r * Math.cos(t) + mx;
                    var cy:Number = r * Math.sin(t) + my;
                    
                    if 
                    (
                        cx >= 0 && cx <= obstacleMap.width 
                        && cy >= 0 && cy <= obstacleMap.height
                    )
                    {
                        var dx:Number = cx - mx;
                        var dy:Number = cy - my;
                        var dist:Number = Math.sqrt(dx * dx + dy * dy);
                        
                        if (dist > LIGHT_RADIUS)
                        {
                            //out of light radius
                            distanceMap.setPixel(i, j, 0xFFFFFF);
                        }
                        else
                        {
                            //draw distance color
                            var channel:uint = 0xFF * (dist / LIGHT_RADIUS);
                            var color:uint = 0;
                            color |= channel << 16;
                            color |= channel << 8;
                            color |= channel;
                            distanceMap.setPixel
                            (
                                i, j, 
                                (obstacleMap.getPixel(cx, cy) < 0xFFFFFF)
                                ?(color):(0xFFFFFF)
                            );
                        }
                    }
                }
            }
        }
        
        private function reduceDistanceMap():void 
        {
            reducedDistanceMap.fillRect(reducedDistanceMap.rect, 0xFFFFFF);
            for (var t:int = 0; t < reducedDistanceMap.height; ++t)
            {
                for (var i:int = 0; i < distanceMap.width; ++i)
                {
                    var color:uint = distanceMap.getPixel(i, t);
                    if (color != 0xFFFFFF)
                    {
                        reducedDistanceMap.setPixel(0, t, color);
                        break;
                    }
                }
            }
        }
        
        private function renderLight():void 
        {
            var mx:Number = stage.mouseX * result.width / stage.stageWidth;
            var my:Number = stage.mouseY * result.height / stage.stageHeight;
            for (var j:int = 0; j < result.width; ++j)
            {
                for (var i:int = 0; i < result.height; ++i)
                {
                    var dx:Number = i - mx;
                    var dy:Number = j - my;
                    var dist:Number = Math.sqrt(dx * dx + dy * dy);
                    var obstacleDist:Number;
                    var color:uint;
                    var channel:uint;
                    var distFactor:Number = Math.atan2(dy, dx);
                    if (distFactor < 0) distFactor += TWO_PI;
                    distFactor /= TWO_PI;
                    
                    obstacleDist = 
                        LIGHT_RADIUS
                        * reducedDistanceMap.getPixel
                        (0, reducedDistanceMap.height * distFactor);
                        
                    obstacleDist /= 0xFFFFFF;
                    
                    if (dist < obstacleDist)
                    {
                        channel = 0xFF * (1 - dist / LIGHT_RADIUS);
                        color = 0;
                        color |= channel << 16;
                        color |= channel << 8;
                        color |= channel;
                    }
                    else
                    {
                        color = 0x000000;
                    }
                    
                    result.setPixel(i, j, color);
                }
            }
        }
        
        private function blurLight():void
        {
            var mx:Number = stage.mouseX * result.width / stage.stageWidth;
            var my:Number = stage.mouseY * result.height / stage.stageHeight;
            
            resultBlurTemp.copyPixels(result, result.rect, new Point(0, 0));
            
            //distance-based blur
            for (var j:int = 0; j < result.height; ++j)
            {
                for (var i:int = 0; i < result.width; ++i)
                {
                    var dx:Number = i - mx;
                    var dy:Number = j - my;
                    var d:Number = 
                        DIST_BLUR_FACTOR 
                        * (Math.sqrt(dx * dx + dy * dy) - DIST_BLUR_THRESHOLD);
                        
                    d = (d < 0)?(0):(d);
                    var channel:uint = 
                        (
                            4 * (result.getPixel(i, j) & 0xFF)
                            + 2 * (result.getPixel(i - d / 2, j - d / 2) & 0xFF)
                            + 2 * (result.getPixel(i + d / 2, j - d / 2) & 0xFF)
                            + 2 * (result.getPixel(i + d / 2, j + d / 2) & 0xFF)
                            + 2 * (result.getPixel(i - d / 2, j + d / 2) & 0xFF)
                            + (result.getPixel(i - d, j) & 0xFF)
                            + (result.getPixel(i, j - d) & 0xFF)
                            + (result.getPixel(i + d, j) & 0xFF)
                            + (result.getPixel(i, j + d) & 0xFF)
                        ) / 16;
                    var color:uint = 0;
                    color |= channel << 16;
                    color |= channel << 8;
                    color |= channel;
                    
                    resultBlurTemp.setPixel(i, j, color);
                }
            }
            
            var temp:BitmapData = result;
            result = resultBlurTemp;
            resultBlurTemp = temp;
            resultBMP.bitmapData = result;
            
            //global blur
            result.applyFilter
            (
                result, result.rect, new Point(0, 0), 
                new BlurFilter(BLUR, BLUR, 2)
            );
        }
        
        private function renderObstacles():void 
        {
            result.draw
            (
                obstacles, 
                new Matrix
                (
                    result.width / stage.stageWidth, 0, 
                    0, result.height / stage.stageHeight
                )
            );
        }
        
        private function regenerateObstacles(e:Event = null):void
        {
            while (obstacles.numChildren) obstacles.removeChildAt(0);
            
            for (var i:int = 0; i < NUM_OBSTACLES; ++i)
            {
                var shape:Shape = new Shape();
                var size:Number = 
                    OBSTACLE_SIZE 
                    + OBSTACLE_SIZE_VAR * (2 * (0.5 - Math.random()));
                
                shape.graphics.beginFill(0x808080, 1);
                shape.graphics.drawRect( -0.5 * size, -0.5 * size, size, size);
                shape.rotation = 360 * Math.random();
                shape.x = stage.stageWidth * Math.random();
                shape.y = stage.stageHeight * Math.random();
                obstacles.addChild(shape);
            }
            
            obstacleMap.lock();
            
            obstacleMap.fillRect(obstacleMap.rect, 0xFFFFFF);
            obstacleMap.draw
            (
                obstacles, 
                new Matrix
                (
                    obstacleMap.width / stage.stageWidth, 0, 
                    0, obstacleMap.height / stage.stageHeight
                )
            );
            obstacleMap.threshold
            (
                obstacleMap, obstacleMap.rect, new Point(0, 0), "<", 
                0xFF0000, 0x000000, 0xFF0000, true
            );
            
            obstacleMap.unlock();
            
            //update
            mainLoop();
        }
        
        private function showResult(e:Event = null):void
        {
            resultBMP.visible = true;
            obstacles.visible = true;
            dataBMP.visible = false;
        }
        
        private function showObstacleMap(e:Event = null):void
        {
            resultBMP.visible = false;
            obstacles.visible = false;
            dataBMP.visible = true;
            
            dataBMP.bitmapData = obstacleMap;
            dataBMP.scaleX = stage.stageWidth / obstacleMap.width;
            dataBMP.scaleY = stage.stageHeight / obstacleMap.height;
        }
        
        private function showDistanceMap(e:Event = null):void
        {
            resultBMP.visible = false;
            obstacles.visible = false;
            dataBMP.visible = true;
            
            dataBMP.bitmapData = distanceMap;
            dataBMP.scaleX = stage.stageWidth / distanceMap.width;
            dataBMP.scaleY = stage.stageHeight / distanceMap.height;
        }
        
        private function showReducedDistanceMap(e:Event = null):void
        {
            resultBMP.visible = false;
            obstacles.visible = false;
            dataBMP.visible = true;
            
            dataBMP.bitmapData = reducedDistanceMap;
            dataBMP.scaleX = stage.stageWidth / reducedDistanceMap.width;
            dataBMP.scaleY = stage.stageHeight / reducedDistanceMap.height;
        }
    }
}