RK4 Cloth Simulation
Cloth simulation using Runge Kutta numeric integration. Can be extended to Verlet or Euler integration. For greatest accuracy RK4 is the best.
==============================
Usage:
- Drag and move the handles
- Click on the cloth to organically modify the cloth form
- Space: toggle on/off the performance status
/**
* Copyright esimov ( http://wonderfl.net/user/esimov )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/gxjV
*/
package
{
import flash.display.Stage;
import flash.display.DisplayObject;
import flash.display.LoaderInfo;
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;
[ 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;
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;
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);
}
}
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);
}
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
{
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();
canvas.graphics.lineStyle(1, 0x555555);
for (var i : int = 0;i < numRows;i++)
{
for (var j : int = 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);
}
}
}
}
}
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.isFree() || p2.isFree() ))
{
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.isFree())
{
p1.force = p1.force.add(new Vector3D(-distX, -distY, -distZ));
}
if (p2.isFree())
{
p2.force = p2.force.add(new Vector3D(distX, distY, 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.isFree() || p2.isFree() ))
{
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;
var lenght : Number = Math.sqrt(distanceSq);
if (distanceSq < minDistanceSq)
{
distanceSq = minDistanceSq;
}
var force : Number = strength * (p1.mass * p2.mass) / distanceSq;
distX /= lenght;
distY /= lenght;
distZ /= lenght;
distX *= force;
distY *= force;
distZ *= force;
if (p1.isFree())
{
p1.force = p1.force.add(new Vector3D(distX, distY, distZ));
}
if (p2.isFree())
{
p2.force = p2.force.add(new Vector3D(-distX, -distY, -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] = Particle(particles[i]).position.clone();
originalVelV[i] = Particle(particles[i]).velocity.clone();
}
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] = Particle(particles[i]).force.clone();
k1VelV[i] = Particle(particles[i]).velocity.clone();
}
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];
k1Vel.scaleBy(0.5 * t);
p.position = originalPos.add(k1Vel);
originalVel = originalVelV[i];
k1Force = k1ForceV[i];
k1Force.scaleBy(0.5 * t / p.mass);
p.velocity = originalVel.add(k1Force);
}
}
particleSystem.applyForces();
for (i = 0;i < numPart;i++)
{
p = particles[i];
if (!p.fixed)
{
k2ForceV[i] = p.force.clone();
k2VelV[i] = p.velocity.clone();
}
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];
k2Vel.scaleBy(0.5 * t);
p.position = originalPos.add(k2Vel);
originalVel = originalVelV[i];
k2Force = k2ForceV[i];
k2Force.scaleBy(0.5 * t / p.mass);
p.velocity = originalVel.add(k2Force);
}
}
particleSystem.applyForces();
for (i = 0;i < numPart;i++)
{
p = particles[i];
if (!p.fixed)
{
k3ForceV[i] = p.force.clone();
k3VelV[i] = p.velocity.clone();
}
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];
k3Vel.scaleBy(0.5 * t);
p.position = originalPos.add(k2Vel);
originalVel = originalVelV[i];
k3Force = k3ForceV[i];
k3Force.scaleBy(0.5 * t / p.mass);
p.velocity = originalVel.add(k3Force);
}
}
particleSystem.applyForces();
for (i = 0;i < numPart;i++)
{
p = particles[i];
if (!p.fixed)
{
k4ForceV[i] = p.force.clone();
k4VelV[i] = p.velocity.clone();
}
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.scaleBy(2);
var k3VelInt : Vector3D = k3Vel.clone();
k3VelInt.scaleBy(2);
var intVel : Vector3D = k1Vel.add(k2VelInt).add(k3VelInt).add(k4Vel);
intVel.scaleBy(t / 6);
p.position = originalPos.add(intVel);
// velocity
originalVel = originalVelV[i];
k1Force = k1ForceV[i];
k2Force = k2ForceV[i];
k3Force = k3ForceV[i];
k4Force = k4ForceV[i];
var k2ForceInt : Vector3D = k2Force.clone();
k2ForceInt.scaleBy(2);
var k3ForceInt : Vector3D = k3Force.clone();
k3ForceInt.scaleBy(2);
var intForce : Vector3D = k1Force.add(k2ForceInt).add(k3ForceInt).add(k4Force);
intForce.scaleBy(t / 6 * p.mass);
p.velocity = originalVel.add(intForce);
}
}
}
}
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);
}
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.scaleBy(-drag);
particle.force = particle.force.add(vel);
}
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;
}
}
}