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

RK4 Cloth (mechanical optimization) with Image

/**
 * Copyright kimo0517 ( http://wonderfl.net/user/kimo0517 )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/8Sel
 */

// forked from yonatan's RK4 Cloth Simulation (mechanical optimization)
// forked from esimov's RK4 Cloth Simulation
package  
{
    import flash.display.Stage;
    import flash.display.DisplayObject;
    import flash.display.LoaderInfo;
    import flash.display.BitmapData;
    import flash.net.URLRequest;
    import flash.display.Loader;
    import flash.system.SecurityDomain;
    import flash.system.ApplicationDomain;
    import flash.system.LoaderContext;
    import flash.system.Security;
    import net.hires.debug.Stats;

    import flash.display.GradientType;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.KeyboardEvent;
    import flash.events.MouseEvent;
    import flash.geom.Matrix;
    import flash.geom.Vector3D;
    import flash.ui.Keyboard;
    import flash.net.FileReference;
    import flash.net.FileFilter;
    import flash.net.URLRequest;
    import flash.display.TriangleCulling;
    import com.bit101.components.PushButton;
    
    import mx.utils.Base64Decoder;

    [ SWF (width=480, height=480, backgroundColor=0xeaeaea, frameRate=60) ]

    /**
     * @author simoe
     */
    public class ClothSimulation extends Sprite
    {    
        private var ps : ParticleSystem;
        private var particles : Vector.<Vector.<Particle>>;
        private var springs : Vector.<Spring>;
        private var dragPoint : Particle;
        private var mouseDown : Boolean = false;
        private var handle1Down:Boolean = false;
        private var handle2Down:Boolean = false;

        private var numRows : Number = 25;
        private var numCols : Number = 25;
        private var offset : Number = 14;

        private var handle1:DisplayObject;
        private var handle2:DisplayObject;
        private var pin1:Sprite;
        private var pin2:Sprite;
        private var canvas : Sprite;
        private var stats : Stats;

        private const STAGE_WIDTH : Number = stage.stageWidth;
        private const STAGE_HEIGHT : Number = stage.stageHeight;
        
        private var btnSelectPic:PushButton;
        private var btnRemove:PushButton;
        
        private var bmd:BitmapData;
        private var _vseg:int;
        private var _hseg:int;
        
        private var vPos:Vector.<Number>;
        private var vIndex:Vector.<int>;
        private var uvtData:Vector.<Number>;
        
        private var fr:FileReference;        

        public function ClothSimulation() : void
        {
            var i : int, j : int;
            var clothWidth : Number = (numRows - 1) * offset;
            var clothHeight : Number = (numCols - 1) * offset;
            var posX : Number = (STAGE_WIDTH - clothWidth) / 2;
            var posY : Number = (STAGE_HEIGHT - clothHeight) / 2;
            
            stage.quality = "medium";
            stats = new Stats();
            particles = new Vector.<Vector.<Particle>>();
            springs = new Vector.<Spring>();
            ps = new ParticleSystem(new Vector3D(0, 0.08, 0), 0.01);
                    
            //switch colums and rows if rows>cols
            if (numRows > numCols)
            {
                var temp : Number = numRows;
                numRows = numCols;
                numCols = temp;
            }
            
            //create a two dimensional vector array
            for (i = 0;i < numRows;i++)
            {
                var vector_rows : Vector.<Particle> = new Vector.<Particle>();
                for (j = 0;j < numCols;j++)
                {    
                    vector_rows.push(ps.makeParticle(0.9, new Vector3D(posX + j * offset, posY + i * offset, 0)));
                }
                
                particles.push(vector_rows);
            }
            
            //create Spring attractors between particles horozontally
            for (i = 0;i < numRows;i++)
            {
                for (j = 0;j < numCols - 1;j++)
                {
                    ps.makeSpring(particles[i][j], particles[i][j + 1], 0.9, 0.2, offset >> 1);
                }
            }
            
            //create Spring attractors between particles vertically
            for (i = 0;i < numRows - 1;i++)
            {
                for (j = 0;j < numCols;j++)
                {
                    ps.makeSpring(particles[i][j], particles[i + 1][j], 0.9, 0.2, offset >> 1);
                }
            }
            
            bmd = null;
            
            _vseg = numRows;
            _hseg = numCols;
            var _vseg_1:int = _vseg - 1;
            var _hseg_1:int = _hseg - 1;
            var v:int;
            var h:int;
            
            vPos = new Vector.<Number>(2*_vseg * _hseg);
            uvtData = new Vector.<Number>(2*_vseg * _hseg);
            vIndex = new Vector.<int>(6 * _vseg_1 * _hseg_1);
            
            for (v = 0; v < _vseg; v++) {
                for (h = 0; h < _hseg; h++) {
                    //x
                    uvtData[2 * (v * _hseg + h)]         = Number(h) / _hseg_1;
                    uvtData[2 * (v * _hseg + h) + 1]     = Number(v) / _vseg_1;
                }
            }
            
            for (v = 0; v < _vseg_1; v ++ ) {
                for (h = 0; h < _hseg_1; h++) {
                    vIndex[6 * (v * _hseg_1 + h)]             = v * _hseg + h;
                    vIndex[6 * (v * _hseg_1 + h) + 1]         = v * _hseg + h+1;
                    vIndex[6 * (v * _hseg_1 + h) + 2]         = (v+1) * _hseg + h;
                    
                    vIndex[6 * (v * _hseg_1 + h) + 3]         = (v+1) * _hseg + h;
                    vIndex[6 * (v * _hseg_1 + h) + 4]         = v * _hseg + h+1;
                    vIndex[6 * (v * _hseg_1 + h) + 5]         = (v+1) * _hseg + h+1;
                }
            }
            
            
            
            createBackground();
            canvas = new Sprite();
            pin1 = new Sprite();
            pin2 = new Sprite();
            
            pin1.x = particles[0][0].position.x - 35;
            pin1.y = particles[0][0].position.y - 25;
            
            pin2.x = particles[0][numRows - 1].position.x - 30;
            pin2.y = particles[0][numCols - 1].position.y - 25;
            
            addChild(canvas);
            canvas.addChild(pin1);
            canvas.addChild(pin2);
            addChild(stats);
                        
            
            
            /*
            Security.loadPolicyFile("http://esimov.zxq.net/crossdomain.xml");
            var context:LoaderContext = new LoaderContext();
            context.checkPolicyFile = true;
            context.applicationDomain = ApplicationDomain.currentDomain;
            context.securityDomain = SecurityDomain.currentDomain;
            var loader1:Loader = new Loader();
            loader1.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadImage1);
            //loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, IOErrorListener);
            loader1.load(new URLRequest("http://esimov.zxq.net/pin_handle.png"));
            
            var loader2:Loader = new Loader();
            loader2.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadImage2);
            //loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, IOErrorListener);
            loader2.load(new URLRequest("http://esimov.zxq.net/pin_handle.png"));
            */
            
            
            particles[0][0].makeFix();
            particles[0][numCols - 1].makeFix();
                    
            addEventListener(MouseEvent.MOUSE_DOWN, onClothPress);
            addEventListener(MouseEvent.MOUSE_UP, onClothRelease);
            addEventListener(KeyboardEvent.KEY_DOWN, keyDownListener);
            
            addEventListener(Event.ENTER_FRAME, renderCloth);

            btnSelectPic = new PushButton(this, 80, 10, "Select Photo", _btnSelectPicClicked);
            btnRemove = new PushButton(this, btnSelectPic.x + btnSelectPic.width + 10, 10, "Unload Photo", _btnUnloadPicClicked);            
            
            var decoder:Base64Decoder = new Base64Decoder;
            decoder.decode("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");
            
            var loader:Loader = new Loader;
            loader.contentLoaderInfo.addEventListener(Event.COMPLETE, _loadBMDComplete);
            loader.loadBytes(decoder.toByteArray());
        }
        
        
        private function _btnSelectPicClicked(e:MouseEvent):void {
            fr = new FileReference();
            fr.addEventListener(Event.SELECT, _startLoadPhoto);
            fr.addEventListener(Event.COMPLETE, _loadPhotoComplete);
            
            var filter:FileFilter = new FileFilter("Images (*.jpg, *.png)", "*.jpg;*.jpeg;*.png", null);
            
            fr.browse([filter]);
            
        }
        
        private function _startLoadPhoto(e:Event):void {
            fr.load();
        }
        
        private function _loadPhotoComplete(e:Event):void {
            var loader:Loader = new Loader();
            loader.contentLoaderInfo.addEventListener(Event.COMPLETE, _loadBMDComplete);
            loader.loadBytes(fr.data);
        }
        
        private function _loadBMDComplete(e:Event):void {
            if (bmd != null) {
                bmd.dispose();
                bmd = null;
            }
            bmd = e.target.content.bitmapData.clone();
        }

        
        private function _btnUnloadPicClicked(e:MouseEvent):void {
            if (bmd != null) {
                bmd.dispose();
                bmd = null;
            }
        }

        private function createBackground() : void 
        {
            var background : Sprite = new Sprite();
            var matrix : Matrix = new Matrix();
            matrix.createGradientBox(STAGE_WIDTH, STAGE_HEIGHT);
            background.graphics.beginGradientFill(GradientType.RADIAL, [0xEFEFEF, 0xEFEFEF, 0xEAEAEA], [1, 1, 1], [0x00, 0x7F, 0xFF], matrix);
            background.graphics.drawRect(0, 0, STAGE_WIDTH, STAGE_HEIGHT);
            background.graphics.endFill();
            addChild(background);
        }

        
        private function onLoadImage1(event:Event):void
        {
            var info:LoaderInfo = event.target as LoaderInfo;
            
            handle1= info.content;
            pin1.addChild(handle1);
            
            pin1.addEventListener(MouseEvent.MOUSE_DOWN, onHandleDown);
            pin1.addEventListener(MouseEvent.MOUSE_UP, onHandleDown);
        }

        private function onLoadImage2(event:Event):void
        {
            var info:LoaderInfo = event.target as LoaderInfo;
            
            handle2 = info.content;
            pin2.addChild(handle2);
            
            pin2.addEventListener(MouseEvent.MOUSE_DOWN, onHandleDown);
            pin2.addEventListener(MouseEvent.MOUSE_UP, onHandleDown);
        }
        
        private function onHandleDown(event:MouseEvent):void
        {
            event.stopPropagation();
            switch (event.type)
            {
                case "mouseDown":
                    if (event.target == pin1)
                    {
                        handle1Down = true;
                        handle2Down = false;
                    } else if (event.target == pin2)
                    {
                        handle1Down = false;
                        handle2Down = true;
                    }
                    break;
                    
                case "mouseUp":
                    handle1Down = false;
                    handle2Down = false;
                    break;
            }
        }

        private function keyDownListener(event : KeyboardEvent) : void
        {
            if (event.keyCode == Keyboard.SPACE)
            {
                stats.visible = !stats.visible;
            }
        }

        private function onClothPress(event : MouseEvent) : void
        {
            mouseDown = true;
            dragPoint = searchDraggingPoint();
            dragPoint.isDragging = true;
        }

        private function onClothRelease(event : MouseEvent) : void
        {
            mouseDown = false;
            dragPoint.isDragging = false;
            dragPoint = undefined;
        }

        
        private function searchDraggingPoint() : Particle
        {
            var target : Particle;
            var lastMinimumDist : Number = Infinity;
            
            for (var i : int = 0;i < numRows;i++)
            {
                for (var j : int = 0;j < numCols;j++)
                {        
                    var particle : Particle = particles[i][j];
                    var mousePos : Vector3D = new Vector3D(mouseX, mouseY, 0);
                    var dist : Number = particle.position.subtract(mousePos).lengthSquared;
                    
                    if (dist < lastMinimumDist)
                    {
                        lastMinimumDist = dist;
                        target = particle; 
                    }
                }
            }
            return target;
        }

        private function renderCloth(event : Event) : void 
        {
            var i:int;
            var j:int;
            particles[0][0].position.x = pin1.x + 35;
            particles[0][0].position.y = pin1.y + 25;
            
            particles[0][numCols - 1].position.x = pin2.x + 30;
            particles[0][numCols - 1].position.y = pin2.y + 25;
            
            if (handle1Down)
            {
                pin1.x = mouseX - 35;
                pin1.y = mouseY - 25;                
            }
            
            if (handle2Down)
            {
                pin2.x = mouseX - 35;
                pin2.y = mouseY - 25;
            }
            
            if(mouseDown)
            {
                dragPoint.position.x = mouseX;
                dragPoint.position.y = mouseY;
                dragPoint.velocity.scaleBy(dragPoint.mass);
            }
            
            ps.applyIntegrator(1);
            ps.clearForces();
            
            canvas.graphics.clear();
            
            if (bmd == null) {
                canvas.graphics.lineStyle(1, 0x555555, 1, false, null, "none");
                
                for (i = 0;i < numRows;i++)
                {
                    for (j = 0;j < numCols - 1;j++)
                    {        
                        canvas.graphics.moveTo(particles[i][j].position.x, particles[i][j].position.y);
                        canvas.graphics.lineTo(particles[i][j + 1].position.x, particles[i][j + 1].position.y);
                    }
                }
                
                for (i = 0;i < numRows - 1;i++)
                {
                    for (j = 0;j < numCols;j++)
                    {        
                        canvas.graphics.moveTo(particles[i][j].position.x, particles[i][j].position.y);
                        canvas.graphics.lineTo(particles[i + 1][j].position.x, particles[i + 1][j].position.y);
                    }
                }
            } else {
                
                
                for (i = 0;i < numRows;i++)
                {
                    for (j = 0;j < numCols;j++)
                    { 
                        vPos[2 * (i * _hseg + j)]         = particles[i][j].position.x; //x
                        vPos[2 * (i * _hseg + j) + 1]     = particles[i][j].position.y; //y
                    }
                }
                
                canvas.graphics.beginBitmapFill(bmd, null, false, true);
                canvas.graphics.drawTriangles(vPos, vIndex, uvtData,TriangleCulling.NONE);
                canvas.graphics.endFill();
            }

        }
    }
}

var s:Number;
var vel:Vector3D = new Vector3D;
var intVel:Vector3D = new Vector3D;
var intForce:Vector3D = new Vector3D;
var k1VelInt:Vector3D = new Vector3D;
var k2VelInt:Vector3D = new Vector3D;
var k3VelInt:Vector3D = new Vector3D;
var k4VelInt:Vector3D = new Vector3D;
var k1ForceInt:Vector3D = new Vector3D;
var k2ForceInt:Vector3D = new Vector3D;
var k3ForceInt:Vector3D = new Vector3D;
var k4ForceInt:Vector3D = new Vector3D;
var k1Vel:Vector3D = new Vector3D;
var k2Vel:Vector3D = new Vector3D;
var k3Vel:Vector3D = new Vector3D;
var k4Vel:Vector3D = new Vector3D;
var k1Force:Vector3D = new Vector3D;
var k2Force:Vector3D = new Vector3D;
var k3Force:Vector3D = new Vector3D;
var k4Force:Vector3D = new Vector3D;

import flash.geom.Vector3D;

internal class Particle 
{
    public var position : Vector3D;
    public var velocity : Vector3D;
    public var force : Vector3D;

    public var mass : Number;
    public var fixed : Boolean;
    public var isDragging : Boolean;

    public static const BOUNCE : String = "bounce";
    public static const WRAP : String = "wrap";

    public function Particle(mass : Number, position : Vector3D) : void 
    {
        this.position = (position) ? position : new Vector3D();
        this.velocity = new Vector3D;
        this.force = new Vector3D;
        this.mass = mass;
            
        isDragging = false; 
        fixed = false;
    }

    public final function distanceBetween(p : Particle) : Number
    {
        return Vector3D.distance(this.position, p.position);
    }

    public final function makeFree() : void
    {
        fixed = false;
    }

    public final function makeFix() : void
    {
        fixed = true;
        this.velocity.x = 0; 
        this.velocity.y = 0; 
        this.velocity.z = 0;
    }

    public final function isFree() : Boolean
    {
        return !fixed;
    }

    public final function isFixed() : Boolean
    {
        return fixed;
    }

    public function setMass(value : Number) : void
    {
        this.mass = value;
    }

    public function reset() : void
    {
        this.position.x = 0; 
        this.position.y = 0; 
        this.position.z = 0;
        this.velocity.y = 0; 
        this.velocity.y = 0; 
        this.velocity.z = 0;
        this.force.x = 0; 
        this.force.y = 0; 
        this.force.z = 0;
        this.mass = 1;
    }

    public function boundCheck(type : String, left : Number, right : Number, top : Number, bottom : Number) : void
    {
        switch (type)
        {
            case Particle.BOUNCE:
                
                if (this.position.x < left)
                {
                    position.x = left + (left - position.x);
                    velocity.x *= -1;
                }
                
                if (this.position.x > right)
                {
                    position.x = right - (position.x - right);
                    velocity.x *= -1;
                }
                
                if (this.position.y < top)
                {
                    position.y = top + (top - position.y);
                    velocity.y *= -1;
                }
                
                if (this.position.y > bottom)
                {
                    position.y = bottom - (position.y - bottom);
                    velocity.y *= -1;
                }
                break;
                
            case Particle.WRAP:
                break;
        }
    }
}

internal interface Integrator
{
    function apply(t : Number) : void;
}

internal class Spring
{
    private var p1 : Particle;
    private var p2 : Particle;
    private var springConst : Number;
    private var on : Boolean;

    private var damping : Number;
    private var restLength : Number;

    public function Spring(p1 : Particle, p2 : Particle, springConst : Number, damping : Number, restLength : Number) : void 
    {
        this.p1 = p1;
        this.p2 = p2;
        this.damping = damping;
        this.restLength = restLength;
        this.springConst = springConst;
            
        this.on = true;
    }

    public final function turnOn() : void
    {
        on = true;
    }

    public final function turnOff() : void
    {
        on = false;
    }

    public final function isOn() : Boolean
    {
        return on;
    }

    public final function isOff() : Boolean
    {
        return !on;
    }

    public final function getDistance(p1 : Particle, p2 : Particle) : Number
    {
        return Vector3D.distance(p1.position, p2.position);
    }

    public final function setDamping(value : Number) : void
    {
        this.damping = value;
    }

    public final function getDamping() : Number
    {
        return damping;
    }

    public final function setRestLength(value : Number) : void
    {
        this.restLength = value;
    }

    public final function getRestLength() : Number
    {
        return this.restLength;
    }

    public function update() : void
    {
        if (on && (!p1.fixed || !p2.fixed ))
        {
            var distX : Number = p2.position.x - p1.position.x;
            var distY : Number = p2.position.y - p1.position.y;
            var distZ : Number = p2.position.z - p1.position.z;
                
            var distSq : Number = Math.sqrt(distX * distX + distY * distY + distZ * distZ);
            if (distSq == 0)
            {
                distX = 0; 
                distY = 0; 
                distZ = 0;
            }
            distX /= distSq;
            distY /= distSq;
            distZ /= distSq;
                
            var springForce : Number = -(distSq - restLength) * springConst;
            var velX : Number = p2.velocity.x - p1.velocity.x;
            var velY : Number = p2.velocity.y - p1.velocity.y;
            var velZ : Number = p2.velocity.z - p1.velocity.z;
                
            var dampingForce : Number = -damping * (distX * velX + distY * velY + distZ * velZ);
            var aggregateForce : Number = springForce + dampingForce;
            distX *= aggregateForce;
            distY *= aggregateForce;
            distZ *= aggregateForce;
                
            if (!p1.fixed) 
            { 
                // p1.force = p1.force.add(new Vector3D(-distX, -distY, -distZ)); 
                p1.force.x -= distX;
                p1.force.y -= distY;
                p1.force.z -= distZ;
            }
            if (!p2.fixed) 
            { 
                // p2.force = p2.force.add(new Vector3D(distX, distY, distZ)); 
                p2.force.x += distX;
                p2.force.y += distY;
                p2.force.z += distZ;
            }
        }
    }
}

internal class Attraction 
{
    private var p1 : Particle;
    private var p2 : Particle;
    private var minDistance : Number;
    private var minDistanceSq : Number;
    private var strength : Number;

    private var on : Boolean;

    public function Attraction(p1 : Particle, p2 : Particle, strength : Number, minDistance : Number) : void 
    {
        this.p1 = p1;
        this.p2 = p2;
        this.minDistance = minDistance;
        this.minDistanceSq = minDistance * minDistance;
        this.strength = strength;
            
        on = true;
    }

    public final function turnOn() : void
    {
        on = true;
    }

    public final function turnOff() : void
    {
        on = false;
    }

    public final function isOn() : Boolean
    {
        return on;
    }

    public final function isOff() : Boolean
    {
        return !on;
    }

    public final function setStrength(value : Number) : void
    {
        this.strength = value;
    }

    public final function getStrength() : Number
    {
        return this.strength;
    }

    public final function setDistance(value : Number) : void
    {
        this.minDistance = value;
        this.minDistanceSq = value * value;
    }

    public final function getDistance() : Number
    {
        return minDistance;
    }

    public function update() : void
    {
        if (on && (!p1.fixed || !p2.fixed ))
        {
            var distX : Number = p2.position.x - p1.position.x;
            var distY : Number = p2.position.y - p1.position.y;
            var distZ : Number = p2.position.z - p1.position.z;
                
            var distanceSq : Number = distX * distX + distY * distY + distZ * distZ;
            if (distanceSq < minDistanceSq) 
            {
                distanceSq = minDistanceSq; 
            }
            var invLength : Number = 1 / Math.sqrt(distanceSq);
                
            var force : Number = strength * (p1.mass * p2.mass) / distanceSq;
            
            distX *= invLength;
            distY *= invLength;
            distZ *= invLength;
            
            distX *= force;
            distY *= force;
            distZ *= force;
                
            if (!p1.fixed) 
            { 
                //p1.force = p1.force.add(new Vector3D(distX, distY, distZ)); 
                p1.force.x += distX;
                p1.force.y += distY;
                p1.force.z += distZ;
            }
            if (!p2.fixed) 
            { 
                //p2.force = p2.force.add(new Vector3D(-distX, -distY, -distZ)); 
                p2.force.x -= distX;
                p2.force.y -= distY;
                p2.force.z -= distZ;
            }
        }
    }
}

internal class RKIntegrator implements Integrator 
{
    private var originalPosV : Vector.<Vector3D>;
    private var originalVelV : Vector.<Vector3D>;
    private var k1VelV : Vector.<Vector3D>;
    private var k1ForceV : Vector.<Vector3D>;
    private var k2VelV : Vector.<Vector3D>;
    private var k2ForceV : Vector.<Vector3D>;
    private var k3VelV : Vector.<Vector3D>;
    private var k3ForceV : Vector.<Vector3D>;
    private var k4VelV : Vector.<Vector3D>;
    private var k4ForceV : Vector.<Vector3D>;

    private var p : Particle;

    private var particleSystem : ParticleSystem;

    public function RKIntegrator(particleSystem : ParticleSystem) : void 
    {
        this.particleSystem = particleSystem;
            
        originalPosV = new Vector.<Vector3D>();
        originalVelV = new Vector.<Vector3D>();
        k1VelV = new Vector.<Vector3D>();
        k1ForceV = new Vector.<Vector3D>();
        k2VelV = new Vector.<Vector3D>();
        k2ForceV = new Vector.<Vector3D>();
        k3VelV = new Vector.<Vector3D>();
        k3ForceV = new Vector.<Vector3D>();
        k4VelV = new Vector.<Vector3D>();
        k4ForceV = new Vector.<Vector3D>();            
    }

    /* INTERFACE esimov.physics.Integrator */

    private function createParticles() : void
    {
        while(particleSystem.particles.length > originalPosV.length)
        {
            originalPosV.push(new Vector3D());
            originalVelV.push(new Vector3D());
            k1VelV.push(new Vector3D());
            k1ForceV.push(new Vector3D());
            k2VelV.push(new Vector3D());
            k2ForceV.push(new Vector3D());
            k3VelV.push(new Vector3D());
            k3ForceV.push(new Vector3D());
            k4VelV.push(new Vector3D());
            k4ForceV.push(new Vector3D());
        }
    }

    public function apply(t : Number) : void
    {
        createParticles();
            
        var numPart : Number = particleSystem.particles.length;
        var particles : Vector.<Particle> = particleSystem.particles;
        var i : int;
            
        var originalPos : Vector3D;
        var originalVel : Vector3D;
        var k1Vel : Vector3D;
        var k1Force : Vector3D;
        var k2Vel : Vector3D;
        var k2Force : Vector3D;
        var k3Vel : Vector3D;
        var k3Force : Vector3D;
        var k4Vel : Vector3D;
        var k4Force : Vector3D;
            
        /**
         * Get initial position and velocity,
         * apply forces and velocity, the result is K1
         */

        for (i = 0;i < numPart;i++)
        {
            if (!Particle(particles[i]).fixed)
            {
                originalPosV[i].x = Particle(particles[i]).position.x;
                originalPosV[i].y = Particle(particles[i]).position.y;
                originalPosV[i].z = Particle(particles[i]).position.z;
                originalVelV[i].x = Particle(particles[i]).velocity.x;
                originalVelV[i].y = Particle(particles[i]).velocity.y;
                originalVelV[i].z = Particle(particles[i]).velocity.z;
            }
                
            Particle(particles[i]).force.x = 0;
            Particle(particles[i]).force.y = 0;
            Particle(particles[i]).force.z = 0;
        }
            
        particleSystem.applyForces();
            
        for (i = 0;i < numPart;i++)
        {
            if (!Particle(particles[i]).fixed)
            {
                k1ForceV[i].x = Particle(particles[i]).force.x;
                k1ForceV[i].y = Particle(particles[i]).force.y;
                k1ForceV[i].z = Particle(particles[i]).force.z;
                k1VelV[i].x = Particle(particles[i]).velocity.x;
                k1VelV[i].y = Particle(particles[i]).velocity.y;
                k1VelV[i].z = Particle(particles[i]).velocity.z;
            }
                
            Particle(particles[i]).force.x = 0;
            Particle(particles[i]).force.y = 0;
            Particle(particles[i]).force.z = 0;
        }
            
        /**
         * Get initial position, K1 velocity
         * apply forces and velocity, the result is K2
         */


        for (i = 0;i < numPart;i++) 
        {
            p = particles[i];

            if (!p.fixed) 
            {    
                originalPos = originalPosV[i];
                k1Vel = k1VelV[i];
                    
                s = (0.5 * t);
                k1Vel.x *= s;
                k1Vel.y *= s;
                k1Vel.z *= s;
                p.position.x = originalPos.x+k1Vel.x;
                p.position.y = originalPos.y+k1Vel.y;
                p.position.z = originalPos.z+k1Vel.z;
                    
                originalVel = originalVelV[i];
                k1Force = k1ForceV[i];
                    
                s = (0.5 * t / p.mass);
                k1Force.x *= s;
                k1Force.y *= s;
                k1Force.z *= s;
                p.velocity.x = originalVel.x+k1Force.x;
                p.velocity.y = originalVel.y+k1Force.y;
                p.velocity.z = originalVel.z+k1Force.z;
            }
        }
            
        particleSystem.applyForces();
            
        for (i = 0;i < numPart;i++)
        {
            p = particles[i];
                
            if (!p.fixed) 
            {
                k2ForceV[i].x = p.force.x;
                k2ForceV[i].y = p.force.y;
                k2ForceV[i].z = p.force.z;
                k2VelV[i].x = p.velocity.x;
                k2VelV[i].y = p.velocity.y;
                k2VelV[i].z = p.velocity.z;
            }
                
            p.force.x = 0; 
            p.force.y = 0; 
            p.force.z = 0;
        }
            
        /**
         * Get initial position, K2 velocity
         * apply forces and velocity, the result is K3
         */

        for (i = 0;i < numPart;i++) 
        {
            p = particles[i];
                
            if (!p.fixed) 
            {    
                originalPos = originalPosV[i];
                k2Vel = k2VelV[i];
                    
                s = (0.5 * t);
                k2Vel.x *= s;
                k2Vel.y *= s;
                k2Vel.z *= s;
                p.position.x = originalPos.x+k2Vel.x;
                p.position.y = originalPos.y+k2Vel.y;
                p.position.z = originalPos.z+k2Vel.z;
                    
                originalVel = originalVelV[i];
                k2Force = k2ForceV[i];
                    
                s = (0.5 * t / p.mass);
                k2Force.x *= s;
                k2Force.y *= s;
                k2Force.z *= s;
                p.velocity.x = originalVel.x+k2Force.x;
                p.velocity.y = originalVel.y+k2Force.y;
                p.velocity.z = originalVel.z+k2Force.z;
            }
        }
            
        particleSystem.applyForces();
            
        for (i = 0;i < numPart;i++)
        {
            p = particles[i];
                
            if (!p.fixed) 
            {
                k3ForceV[i].x = p.force.x;
                k3ForceV[i].y = p.force.y;
                k3ForceV[i].z = p.force.z;
                k3VelV[i].x = p.velocity.x;
                k3VelV[i].y = p.velocity.y;
                k3VelV[i].z = p.velocity.z;
            }
                
            p.force.x = 0; 
            p.force.y = 0; 
            p.force.z = 0;
        }
            
        /**
         * Get initial position, K3 velocity
         * apply forces and velocity, the result is K4
         */

        for (i = 0;i < numPart;i++) 
        {
            p = particles[i];
                
            if (!p.fixed) 
            {    
                originalPos = originalPosV[i];
                k3Vel = k3VelV[i];
                    
                s = (0.5 * t);
                k3Vel.x *= s;
                k3Vel.y *= s;
                k3Vel.z *= s;
                p.position.x = originalPos.x+k2Vel.x;
                p.position.y = originalPos.y+k2Vel.y;
                p.position.z = originalPos.z+k2Vel.z;
                    
                originalVel = originalVelV[i];
                k3Force = k3ForceV[i];
                    
                s = (0.5 * t / p.mass);
                k3Force.x *= s;
                k3Force.y *= s;
                k3Force.z *= s;
                p.velocity.x = originalVel.x+k3Force.x;
                p.velocity.y = originalVel.y+k3Force.y;
                p.velocity.z = originalVel.z+k3Force.z;
            }
        }
            
        particleSystem.applyForces();
            
        for (i = 0;i < numPart;i++)
        {
            p = particles[i];
                
            if (!p.fixed) 
            {
                k4ForceV[i].x = p.force.x;
                k4ForceV[i].y = p.force.y;
                k4ForceV[i].z = p.force.z;
                k4VelV[i].x = p.velocity.x;
                k4VelV[i].y = p.velocity.y;
                k4VelV[i].z = p.velocity.z;
            }
                
            p.force.x = 0; 
            p.force.y = 0; 
            p.force.z = 0;
        }
            
        /**
         * Update initial position and velocity based on intermediate values
         */

        for (i = 0;i < numPart;i++)
        {
            p = particles[i];
                
            if (!p.fixed) 
            {    
                //position

                originalPos = originalPosV[i];
                k1Vel = k1VelV[i];
                k2Vel = k2VelV[i];
                k3Vel = k3VelV[i];
                k4Vel = k4VelV[i];
                  
//                var k2VelInt : Vector3D = k2Vel.clone();
                k2VelInt.x = k2Vel.x;
                k2VelInt.y = k2Vel.y;
                k2VelInt.z = k2Vel.z;
                s = (2);
                k2VelInt.x *= s;
                k2VelInt.y *= s;
                k2VelInt.z *= s;
//                var k3VelInt : Vector3D = k3Vel.clone();
                k3VelInt.x = k3Vel.x;
                k3VelInt.y = k3Vel.y;
                k3VelInt.z = k3Vel.z;
                s = (2);
                k3VelInt.x *= s;
                k3VelInt.y *= s;
                k3VelInt.z *= s;
                //var intVel : Vector3D = k1Vel.add(k2VelInt).add(k3VelInt).add(k4Vel);
                intVel.x = k1Vel.x + k2Vel.x + k3Vel.x + k4Vel.x;
                intVel.y = k1Vel.y + k2Vel.y + k3Vel.y + k4Vel.y;
                intVel.z = k1Vel.z + k2Vel.z + k3Vel.z + k4Vel.z;
                s = (t / 6);
                intVel.x *= s;
                intVel.y *= s;
                intVel.z *= s;
                p.position.x = originalPos.x+intVel.x;
                p.position.y = originalPos.y+intVel.y;
                p.position.z = originalPos.z+intVel.z;
                    
                // velocity

                originalVel = originalVelV[i];
                k1Force = k1ForceV[i];
                k2Force = k2ForceV[i];
                k3Force = k3ForceV[i];
                k4Force = k4ForceV[i];
                    
//                var k2ForceInt : Vector3D = k2Force.clone();
                k2ForceInt.x = k2Force.x;
                k2ForceInt.y = k2Force.y;
                k2ForceInt.z = k2Force.z;
                s = (2);
                k2ForceInt.x *= s;
                k2ForceInt.y *= s;
                k2ForceInt.z *= s;
//                var k3ForceInt : Vector3D = k3Force.clone();
                k3ForceInt.x = k3Force.x;
                k3ForceInt.y = k3Force.y;
                k3ForceInt.z = k3Force.z;
                s = (2);
                k3ForceInt.x *= s;
                k3ForceInt.y *= s;
                k3ForceInt.z *= s;
                //var intForce : Vector3D = k1Force.add(k2ForceInt).add(k3ForceInt).add(k4Force);
                intForce.x = k1Force.x + k2Force.x + k3Force.x + k4Force.x;
                intForce.y = k1Force.y + k2Force.y + k3Force.y + k4Force.y;
                intForce.z = k1Force.z + k2Force.z + k3Force.z + k4Force.z;
                s = (t / 6 * p.mass);
                intForce.x *= s;
                intForce.y *= s;
                intForce.z *= s;
                p.velocity.x = originalVel.x + intForce.x;
                p.velocity.y = originalVel.y + intForce.y;
                p.velocity.z = originalVel.z + intForce.z;
            }
        }
    }
}

internal class ParticleSystem 
{
    internal var particles : Vector.<Particle>;
    internal var springs : Vector.<Spring>;
    internal var attractors : Vector.<Attraction>;

    private var integrator : Integrator;
    private var gravity : Vector3D;
    private var drag : Number;
    private var restLength : Number = 200;

    public static const RK : String = "RUNGE_KUTTA";
    public static const EULER : String = "MODIFIED_EULER";
    public static const VERLET : String = "VERLET";

    public function ParticleSystem(gravity : Vector3D = null, drag : Number = 0.001) : void 
    {
        particles = new Vector.<Particle>();
        springs = new Vector.<Spring>();
        attractors = new Vector.<Attraction>();
            
        this.integrator = new RKIntegrator(this);
        this.gravity = (gravity) ? gravity : new Vector3D();
        this.drag = drag;
    }


    public final function getIntegrator() : Integrator
    {
        return this.integrator;
    }

    public final function applyIntegrator(t : Number = 1) : void
    {
        integrator.apply(t);
    }

    public final function setGravity(value : Number) : void
    {
        this.gravity.scaleBy(value);
    }

    public final function getGravity() : Vector3D
    {
        return this.gravity;
    }

    public final function setDrag(value : Number) : void
    {
        this.drag = value;
    }

    public final function getDrag() : Number
    {
        return drag;
    }

    public final function makeParticle(mass : Number = 1, position : Vector3D = null) : Particle
    {
        var particle : Particle = new Particle(mass, position);
        particle.isDragging = false;
        particles.push(particle);
        return particle;
    }

    public final function makeSpring(p1 : Particle, p2 : Particle, damping : Number, restLenght : Number, springConst : Number) : Spring
    {
        var spring : Spring = new Spring(p1, p2, damping, restLenght, springConst);
        springs.push(spring);
        return spring;
    }

    public final function makeAttractors(p1 : Particle, p2 : Particle, strength : Number, minDist : Number) : Attraction
    {
        var attraction : Attraction = new Attraction(p1, p2, strength, minDist);
        attractors.push(attraction);
        return attraction;
    }

    public function getParticlesLength() : Number
    {
        return particles.length;
    }

    public function getSpringsLength() : Number
    {
        return springs.length;
    }

    public function getAttractorsLength() : Number
    {
        return attractors.length;
    }

    public final function applyForces() : void
    {
        var i : int;
            
        if (gravity.x != 0 || gravity.y != 0 || gravity.z != 0)
        {
            for (i = 0;i < getParticlesLength();i++)
            {
                var particle : Particle = Particle(particles[i]);
                    
                if (!particle.isDragging) 
                {
                     //particle.force = particle.force.add(gravity);
                    particle.force.x += gravity.x;
                    particle.force.y += gravity.y;
                    particle.force.z += gravity.z;
                } 
                else 
                {
                    particle.force = new Vector3D();
                }
            }
        }
            
        for (i = 0;i < getParticlesLength();i++)
        {
            particle = Particle(particles[i]);
            if (!particle.isDragging) 
            {
//                var vel : Vector3D = particle.velocity.clone();
                vel.x = particle.velocity.x;
                vel.y = particle.velocity.y;
                vel.z = particle.velocity.z;
                s = (-drag);
                vel.x *= s;
                vel.y *= s;
                vel.z *= s;
                particle.force.x += vel.x;
                particle.force.y += vel.y;
                particle.force.z += vel.z;
            }
            else 
            {
                particle.velocity = new Vector3D();
            }
        }
            
        for (i = 0;i < getSpringsLength();i++)
        {
            var spring : Spring = Spring(springs[i]);
            spring.update();
        }
            
        for (i = 0;i < getAttractorsLength();i++)
        {
            var attractor : Attraction = Attraction(attractors[i]);
            attractor.update();
        }
    }

    public final function clear() : void
    {
        var i : int;
        for (i = 0;i <= getParticlesLength();i++)
            particles[i] = null;
            
        for (i = 0;i <= getAttractorsLength();i++)
            attractors[i] = null;
            
        for (i = 0;i <= getSpringsLength();i++)
            springs[i] = null;
            
        particles = new Vector.<Particle>();
        attractors = new Vector.<Attraction>();
        springs = new Vector.<Spring>();
    }

    public final function clearForces() : void
    {
        for (var i : int = i;i < getParticlesLength();i++)
        {
            Particle(particles[i]).force.x = 0;
            Particle(particles[i]).force.y = 0;
            Particle(particles[i]).force.z = 0;
        }
    }

    public final function getParticle(index : Number) : Particle
    {
        return particles[index]; 
    }

    public final function getAttractor(index : Number) : Attraction
    {
        return attractors[index];
    }

    public final function getSpring(index : Number) : Spring
    {
        return springs[index];
    }

    public final function removeParticle(n : int) : void
    {
        particles[n] = null;
        particles.splice(n, 1);
    }

    public final function removeSprings(n : int) : void
    {
        springs[n] = null;
        springs.splice(n, 1);
    }

    public final function removeAttractors(n : int) : void
    {
        attractors[n] = null;
        attractors.splice(n, 1);
    } 

    public final function removeParticleByIndex(p : Particle) : Boolean
    {
        var i : int;
        var n : Number = -1;
        for (i = 0;i <= getParticlesLength();i++)
        {
            if (particles[i] == p)
            {
                n = i;
                break;
            }
        }
        if (n != -1)
        {
            particles[n] = null;
            particles.splice(n, 1);
            return true;
        }
            else return false;
    }

    public final function removeSpringByIndex(s : Spring) : Boolean
    {
        var i : int;
        var n : Number = -1;
        for (i = 0;i <= getParticlesLength();i++)
        {
            if (springs[i] == s)
            {
                n = i;
                break;
            }
        }
        if (n != -1)
        {
            springs[n] = null;
            springs.splice(n, 1);
            return true;
        }
            else return false;
    }

    public final function removeAttractorByIndex(a : Attraction) : Boolean
    {
        var i : int;
        var n : Number = -1;
        for (i = 0;i <= getParticlesLength();i++)
        {
            if (attractors[i] == a)
            {
                n = i;
                break;
            }
        }
        if (n != -1)
        {
            attractors[n] = null;
            attractors.splice(n, 1);
            return true;
        }
            else return false;
    }

    public function constraintSolve() : void
    {
        for (var i : int = 0;i < getParticlesLength() - 1;i++)
        {
            var p1 : Particle = particles[i];
            var p2 : Particle = particles[i + 1];
                
            var dx : Number = p2.position.x - p1.position.x;
            var dy : Number = p2.position.y - p1.position.y;
            var delta : Number = Math.sqrt(dx * dx + dy * dy);
            var diff : Number = restLength - delta;
            var offsetX : Number = (diff * dx / delta) * 0.5;
            var offsetY : Number = (diff * dy / delta) * 0.5;
            p1.position.x -= offsetX;
            p1.position.y -= offsetY;
            p2.position.x += offsetX;
            p2.position.y += offsetY;
        }
    }
}