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

Self Sorting Particles

Get Adobe Flash player
by Quasimondo 28 Feb 2011
/**
 * Copyright Quasimondo ( http://wonderfl.net/user/Quasimondo )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/igsU
 */

// forked from Quasimondo's 7000 Particle Stirrable Fluid
// forked from Quasimondo's Stirrable Particle Fluid
// forked from Quasimondo's Particle Fluid (Linked List Optimization)
// forked from shohei909's 流体シミュレーション/Particle Fluidを高速化してみた。
// forked from saharan's 流体シミュレーション/Particle Fluid
/*
* SPH - Smoothed Particle Hydrodynamics
*
* クリック:水を注ぐ
* 
* SPH(もどき)をASで実装してみました。
* 最適化していないので重いです。
* タイムステップを考えていなかったりといろいろ適当な部分がありますので注意。
* カーネルを除く基本的な計算方法は一緒です。
* 

Quasimondo: optimized my linked list method with the neighbor technique used by
Saharan in this sketch: http://wonderfl.net/c/2pg0

In the latest version I optimized the grid with some linked list structure, too
*/
package {
    import com.bit101.components.Label;
    
    import flash.display.*;
    import flash.events.*;
    import flash.filters.*;
    import flash.geom.*;
    import flash.text.*;
    
    import net.hires.debug.Stats;
    [SWF(frameRate = 60)]
    final public class Fluid2 extends Sprite {
        
        public static const SIZE:int = 465;
        public static const GRAVITY:Number = 0;
        public static const RANGE:Number = 15;
        public static const RANGE2:Number = RANGE * RANGE;
        public static const DENSITY:Number = 0.25;
        public static const PRESSURE:Number = 2.2;
        public static const VISCOSITY:Number = 0.05;
        public static const ADHESION:Number = 0.03;
        
        public static const DIV:int = Math.ceil(SIZE / RANGE ); 
        public static const DIV2:int = DIV * DIV;
        public static const INV_DIV:Number = 1 / RANGE;
        
        private var map:Vector.<GridCell>;
        private var img:BitmapData;
        private var imgVec:Vector.<uint>;
        private var first:Particle;
        private var last:Particle;
        private var mouseParticle:Particle;
        private var lastX:int;
        private var lastY:int;
        private var mouse_vx:Number;
        private var mouse_vy:Number;
        private var firstNeighbor:Neighbor;
        
        private var grid:GridCell;
        //private var numParticles:uint;
        // private const blur1:BlurFilter = new BlurFilter(8, 8, 3);
        //private const ct:ColorTransform = new ColorTransform(7,7,16);
        //private const blur2:BlurFilter = new BlurFilter(4, 4, 2);
        //private var count:int;
        private var press:Boolean;
        //private var text:Label;
        
        private const origin:Point = new Point();
        private var rect:Rectangle;
        private var vecSize:int;
        
        public function Fluid2() {
            addEventListener( Event.ADDED_TO_STAGE, initialize );
        }
        
        
        private function initialize( e:Event ):void {
            
            opaqueBackground = 0;
            scrollRect = new Rectangle(0,0,SIZE,SIZE);
            img = new BitmapData(SIZE, SIZE, false, 0);
            vecSize = img.width * img.height ;
            imgVec = new Vector.<uint>();
            rect = img.rect;
            
            addEventListener(Event.ENTER_FRAME, frame);
            addChild( new Bitmap(img) ).opaqueBackground = 0;
            stage.addEventListener(MouseEvent.MOUSE_DOWN, function(e:MouseEvent):void {press = true;});
            stage.addEventListener(MouseEvent.MOUSE_UP, function(e:MouseEvent):void {press = false;});
            stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
            
            stage.tabChildren = false;
            stage.mouseChildren = false;
            stage.quality = "low";
            stage.scaleMode = "noScale";
            stage.align = "TL";
            //stage.addChild( new Stats ).opaqueBackground = 0;
            //text = new Label( stage, 75, 0, "");
            
            //stage.removeChildAt(0)
            
            map = new Vector.<GridCell>(DIV2,true);
            var g:GridCell = grid = map[0] = new GridCell();
            for( var i:int = 1; i < DIV2; i++ ) 
            {
                g = g.next = map[i] = new GridCell();
            }
            
            i = 0;
            for ( var y:int = 0; y < DIV; y++)
            {
                for ( var x:int = 0; x < DIV; x++)
                {
                    if ( x < DIV - 1 ) {
                        map[i].right = map[int(i+1)];
                        if ( y < DIV - 1 )
                        {
                            map[i].downright = map[int(i+1+DIV)];
                        }
                    }
                    if ( y < DIV - 1 )
                    {
                        map[i].down = map[int(i+DIV)];
                        if ( x > 0 )
                        {
                            map[i].downleft = map[int(i-1+DIV)];
                        }
                    }
                    i++;
                    
                }
            }
            
            firstNeighbor = Neighbor.getDummy();
            
            mouseParticle = new Particle( mouseX, mouseY);
            mouseParticle.density = DENSITY * 0.2;
            mouseParticle.pressure = 1;
            addParticles(9000);       
        }
        
        private function frame(e:Event):void 
        {
            
            updateGrid();
            setNeighbors();
            
            mouseParticle.vx = mouse_vx * 3;
            mouseParticle.vy = mouse_vy * 3;
            mouseParticle.density = DENSITY * 0.02;
            
            setPressure();
            
            mouseParticle.pressure = 0.4;
            
            setForce();
            move();
            
            imgVec.length = 0;
            imgVec.length = vecSize;
            draw();
            img.setVector( rect, imgVec );
            
        }
        
        private function onMouseMove(event:MouseEvent):void
        {
            mouseParticle.x = mouseX;
            mouseParticle.y = mouseY;
            mouse_vx = mouseX - lastX;
            mouse_vy = mouseY - lastY;
            lastX = mouseX;
            lastY = mouseY;
            
        }
        
        private function updateGrid():void
        {
            var p:Particle;
            var mp:Vector.<GridCell> = map;
            var g:GridCell = grid;
            while ( g != null )
            {
                g.firstParticle.nextInGrid = null;
                g.lastParticle = g.firstParticle;
                g = g.next;
            }
            
            p = first;
            while ( p != null )
            {
                g = mp[p.gridIndex];
                p.nextInGrid = null;
                g.lastParticle = g.lastParticle.nextInGrid = p;
                p = p.next;
            }
        }
        
        
        
        private function addParticles( count:int ):void 
        {
            var g:GridCell;
            var mp:Vector.<GridCell> = map;
            
            if ( last == null )
            {
                first = new Particle(465 * Math.random(), 465 * Math.random());
                last = first;
                
            } else {
                last = last.next = new Particle(465 * Math.random(), 465 * Math.random());
            }
            
            g = mp[last.gridIndex];
            g.lastParticle = g.lastParticle.nextInGrid = last;
            
            while ( --count > 0 )
            {
                last = last.next = new Particle(465 * Math.random(), 465 * Math.random());
                g = mp[last.gridIndex];
                g.lastParticle = g.lastParticle.nextInGrid = last; 
            }
            
            
            //  text.text = "numParticles: " + numParticles;
        }
        
        private function setNeighbors():void 
        {
            var mp:Vector.<GridCell> = map;
            var a:int, b:int, i:int, j:int, d:Number, d2:Number;
            var r2:Number = RANGE2;
            var m1:Vector.<Particle>, m2:Vector.<Particle>;
            var p1:Particle, p2:Particle;
            
            if ( firstNeighbor.next != null ) Neighbor.recycleNeighbors( firstNeighbor.next );
            firstNeighbor.next = null;
            var n:Neighbor = firstNeighbor;
            var g:GridCell;
            
            
            
            if ( press )
            {
                g = grid;
                while ( g != null )
                {
                    p1 = g.firstParticle.nextInGrid;
                    while ( p1 != null )
                    {
                        if ( ( d = p1.getSquaredDistance( mouseParticle )) < r2 * 2)  n = n.next = Neighbor.getNeighbor( p1, mouseParticle, d * 0.5 );
                        p1 = p1.nextInGrid;
                    }
                    g = g.next;
                }
            }
            
            g = grid;
            while ( g != null )
            {
                p1 = g.firstParticle.nextInGrid;
                while ( p1 != null )
                {
                    p2 = p1.nextInGrid;
                    while ( p2 != null )
                    {        
                        if ( ( d = p1.getSquaredDistance( p2 )) < r2 )  n = n.next = Neighbor.getNeighbor( p1, p2, d  );
                        p2 = p2.nextInGrid;
                    }
                    p1 = p1.nextInGrid;
                }
                g = g.next;
            }
            
            g = grid;
            while ( g != null )
            {
                if ( g.right != null )
                {
                    p1 = g.firstParticle.nextInGrid;
                    while ( p1 != null )
                    {
                        p2 = g.right.firstParticle.nextInGrid;
                        while ( p2 != null )
                        {        
                            if ( ( d = p1.getSquaredDistance( p2 )) < r2 )  n = n.next = Neighbor.getNeighbor( p1, p2, d  );
                            p2 = p2.nextInGrid;
                        }
                        p1 = p1.nextInGrid;
                    }
                    
                }
                
                if ( g.down != null )
                {
                    p1 = g.firstParticle.nextInGrid;
                    while ( p1 != null )
                    {
                        p2 = g.down.firstParticle.nextInGrid;
                        while ( p2 != null )
                        {        
                            if ( ( d = p1.getSquaredDistance( p2 )) < r2 )  n = n.next = Neighbor.getNeighbor( p1, p2, d  );
                            p2 = p2.nextInGrid;
                        }
                        p1 = p1.nextInGrid;
                    }
                    
                    if ( g.downright != null )
                    {
                        p1 = g.firstParticle.nextInGrid;
                        while ( p1 != null )
                        {
                            p2 = g.downright.firstParticle.nextInGrid;
                            while ( p2 != null )
                            {        
                                if ( ( d = p1.getSquaredDistance( p2 )) < r2 )  n = n.next = Neighbor.getNeighbor( p1, p2, d  );
                                p2 = p2.nextInGrid;
                            }
                            p1 = p1.nextInGrid;
                            
                        }
                    }
                    
                    if ( g.downleft != null )
                    {
                        p1 = g.firstParticle.nextInGrid;
                        while ( p1 != null )
                        {
                            p2 = g.downleft.firstParticle.nextInGrid;
                            while ( p2 != null )
                            {        
                                if ( ( d = p1.getSquaredDistance( p2 )) < r2 )  n = n.next = Neighbor.getNeighbor( p1, p2, d  );
                                p2 = p2.nextInGrid;
                            }
                            p1 = p1.nextInGrid;
                        }
                    }
                }
                
                g = g.next;
            }
        }
        
        private function setPressure():void
        {
            var d:Number = DENSITY;
            var p:Particle = first;
            while ( p != null )
            {
                if(p.density < d) p.density = d;
                p.pressure = p.density - d;
                p = p.next;
            }
        }
        
        
        private function setForce():void
        {
            var n:Neighbor = firstNeighbor.next;
            while ( n != null )
            {
                n = n.calcForce();
            }
        }
        
        
        private function move():void 
        {
            var p:Particle = first;
            while ( p != null )
            {
                p = p.move();
            }
        }
        
        private function draw():void 
        {
            var p:Particle = first;
            while ( p != null )
            {
                if ( p.x >= 0 && p.y >= 0 && p.x < 465 && p.y < 465 )
                    imgVec[int(int(p.x) + 465 * int(p.y))] = p.color;
                p = p.next;
            }
        }
    }
}


final class GridCell
{
    public var right:GridCell;
    public var down:GridCell;
    public var downright:GridCell;
    public var downleft:GridCell;
    
    public var next:GridCell;  
    
    public var firstParticle:Particle;
    public var lastParticle:Particle;
    
    public function GridCell()
    {
        firstParticle = new Particle(0,0);
        lastParticle = firstParticle;
    }
    
}




import flash.display.BitmapData;
import flash.geom.Rectangle;



final class Neighbor {
    public var p1:Particle;
    public var p2:Particle;
    public var distance:Number;
    public var nx:Number;
    public var ny:Number;
    public var weight:Number;
    public var next:Neighbor;
    
    private var pressure:Number;
    private var viscosity:Number;
    private var range:Number;
    private var adhesion:Number;
    
    private static var depot:Neighbor;
    private static var depotLast:Neighbor;
    
    
    public static function getDummy( ):Neighbor
    {
        var n:Neighbor;
        if ( depot == null )
        {
            n = new Neighbor();
        } else {
            n = depot;
            depot = depot.next;
            n.next = null;
            if ( depotLast == n ) depotLast = depot;
        }
        
        return n;
    }
    
    
    public static function getNeighbor( p1:Particle, p2:Particle, squaredDistance:Number ):Neighbor
    {
        var n:Neighbor;
        if ( depot == null )
        {
            n = new Neighbor();
        } else {
            n = depot;
            depot = depot.next;
            n.next = null;
            if ( depotLast == n ) depotLast = depot;
        }
        
        n.setParticle( p1, p2, squaredDistance );
        return n;
    }
    
    public static function recycleNeighbors( list:Neighbor ):void
    {
        if ( depot == null ) depot = list;
        else depot.next = list;
        while ( list.next != null )
        {
            list = list.next;
        }
        depotLast = list;
        
    }
    
    public function Neighbor()
    {
        pressure =  Fluid2.PRESSURE;
        viscosity = Fluid2.VISCOSITY;
        range = Fluid2.RANGE;
        adhesion = Fluid2.ADHESION
    }
    
    public function setParticle(p1:Particle, p2:Particle, squaredDistance:Number):void {
        
        this.p1 = p1;
        this.p2 = p2;
        
        distance = Math.sqrt(squaredDistance);
        weight = 1 - distance / range;
        var temp:Number = weight * weight * weight;
        p1.density += temp;
        p2.density += temp;
        nx = p1.x - p2.x;
        ny = p1.y - p2.y;
        temp = 1 / distance;
        nx *= temp;
        ny *= temp;
    }
    
    public function calcForce():Neighbor {
        var pressureWeight:Number = weight * (p1.pressure + p2.pressure) / (p1.density + p2.density) * pressure;
        var viscosityWeight:Number = weight / (p1.density + p2.density) * viscosity;
        p1.fx += nx * pressureWeight;
        p1.fy += ny * pressureWeight;
        p2.fx -= nx * pressureWeight;
        p2.fy -= ny * pressureWeight;
        var rvx:Number = p2.vx - p1.vx;
        var rvy:Number = p2.vy - p1.vy;
        p1.fx += rvx * viscosityWeight;
        p1.fy += rvy * viscosityWeight;
        p2.fx -= rvx * viscosityWeight;
        p2.fy -= rvy * viscosityWeight;
        
        var f:Number = (p1.type == p2.type ? -adhesion : adhesion);
        p1.fx += nx * f;
        p1.fy += ny * f;
        p2.fx -= nx * f;
        p2.fy -= ny * f;
        
        return next;
    }
    
}


final class Particle {
    public var next:Particle;
    public var nextInGrid:Particle;
    
    public var x:Number;    
    public var y:Number;    
    public var vx:Number;    
    public var vy:Number;   
    public var fx:Number; 
    public var fy:Number; 
    
    public var gridIndex:int;
    public var density:Number; 
    public var pressure:Number; 
    
    public var type:int;
    public var color:uint;
    
    private var gravity:Number;
    private var iDiv:Number;
    private var div:int;
    
    private static const colors:Vector.<uint> = Vector.<uint>([0xffff0000,0xff00ff00,0xff0000ff]);
    public function Particle( x:Number, y:Number ) 
    {
        init(x,y);
    }
    
    private function init( x:Number, y:Number ):void
    {
        this.x = x;
        this.y = y;
        density =  Fluid2.DENSITY;
        pressure = 1;
        fx = fy = vx = vy = 0;
        
        
        iDiv = Fluid2.INV_DIV;
        div = Fluid2.DIV;
        gravity = Fluid2.GRAVITY;
        
        var mapX:int = x * iDiv;
        var mapY:int = y * iDiv;
        
        if(mapX < 0) mapX = 0;
        else if(mapX > div - 1) mapX = div - 1;
        if(mapY < 0) mapY = 0;
        else if(mapY > div - 1) mapY = div - 1;   
        
        gridIndex = mapX + div * mapY;   
        
        type = Math.random() * 3;
        color = colors[type];
        
    }
    
    
    public function getSquaredDistance( p:Particle ):Number
    {
        var dx:Number = x - p.x;
        var dy:Number = y - p.y;
        return dx*dx+dy*dy;
    }
    
    
    public function move():Particle 
    {
        x += vx += fx;
        y += vy += fy + gravity;
        fx = fy = density = 0;
        
        var mapX:int = x * iDiv;
        var mapY:int = y * iDiv;
        
        if(x < 5)
        {
            vx += (5 - x) * 0.5 - vx * 0.5;
            if(mapX < 0) mapX = 0;
        } else if(x > 460)
        {
            vx += (460 - x) * 0.5 - vx * 0.5;
            if(mapX > div - 1) mapX = div - 1;
        }
        if(y < 5)
        {
            vy += (5 - y) * 0.5 - vy * 0.5;
            if(mapY < 0) mapY = 0;
        } else if(y > 460)
        {
            vy += (460 - y) * 0.5 - vy * 0.5;
            if(mapY > div - 1) mapY = div - 1;   
        }
        
        gridIndex = mapX + div * mapY;         
        
        return next;
    }
    
    
    
}