package flare.physics { import flash.geom.Rectangle; /** * A physical simulation involving particles, springs, and forces. * Useful for simulating a range of physical effects or layouts. */ public class Simulation { private var _particles:Vector. = new Vector.(); private var _springs:Vector. = new Vector.(); private var _forces:Vector. = new Vector.(); private var _bounds:Rectangle = null; /** The default gravity force for this simulation. */ public function get gravityForce():GravityForce { return _forces[0] as GravityForce; } /** The default n-body force for this simulation. */ public function get nbodyForce():NBodyForce { return _forces[1] as NBodyForce; } /** The default drag force for this simulation. */ public function get dragForce():DragForce { return _forces[2] as DragForce; } /** The default spring force for this simulation. */ public function get springForce():SpringForce { return _forces[3] as SpringForce; } /** Sets a bounding box for particles in this simulation. * Null (the default) indicates no boundaries. */ public function get bounds():Rectangle { return _bounds; } public function set bounds(b:Rectangle):void { if (_bounds == b) return; if (b == null) { _bounds = null; return; } if (_bounds == null) { _bounds = new Rectangle(); } // ensure x is left-most and y is top-most _bounds.x = b.x + (b.width < 0 ? b.width : 0); _bounds.width = (b.width < 0 ? -1 : 1) * b.width; _bounds.y = b.y + (b.width < 0 ? b.height : 0); _bounds.height = (b.height < 0 ? -1 : 1) * b.height; } /** * Creates a new physics simulation. * @param gx the gravitational acceleration along the x dimension * @param gy the gravitational acceleration along the y dimension * @param drag the default drag (viscosity) co-efficient * @param attraction the gravitational attraction (or repulsion, for * negative values) between particles. */ public function Simulation(gx:Number=0, gy:Number=0, drag:Number=0.1, attraction:Number=-5) { _forces.push(new GravityForce(gx, gy)); _forces.push(new NBodyForce(attraction)); _forces.push(new DragForce(drag)); _forces.push(new SpringForce()); } // -- Init Simulation ------------------------------------------------- /** * Adds a custom force to the force simulation. * @param force the force to add */ public function addForce(force:IForce):void { _forces.push(force); } /** * Returns the force at the given index. * @param idx the index of the force to look up * @return the force at the specified index */ public function getForceAt(idx:int):IForce { return _forces[idx]; } /** * Adds a new particle to the simulation. * @param mass the mass (charge) of the particle * @param x the particle's starting x position * @param y the particle's starting y position * @return the added particle */ public function addParticle(mass:Number, x:Number, y:Number):Particle { var p:Particle = getParticle(mass, x, y); _particles.push(p); return p; } /** * Removes a particle from the simulation. Any springs attached to * the particle will also be removed. * @param idx the index of the particle in the particle list * @return true if removed, false otherwise. */ public function removeParticle(idx:uint):Boolean { var p:Particle = _particles[idx]; if (p == null) return false; // remove springs for (var i:uint = _springs.length; --i >= 0; ) { var s:Spring = _springs[i]; if (s.p1 == p || s.p2 == p) removeSpring(i); } // remove from particles reclaimParticle(p); _particles.splice(idx, 1); return true; } /** * Adds a spring to the simulation * @param p1 the first particle attached to the spring * @param p2 the second particle attached to the spring * @param restLength the rest length of the spring * @param tension the tension of the spring * @param damping the damping (friction) co-efficient of the spring * @return the added spring */ public function addSpring(p1:Particle, p2:Particle, restLength:Number, tension:Number, damping:Number):Spring { var s:Spring = getSpring(p1, p2, restLength, tension, damping); p1.degree++; p2.degree++; _springs.push(s); return s; } /** * Removes a spring from the simulation. * @param idx the index of the spring in the spring list * @return true if removed, false otherwise */ public function removeSpring(idx:uint):Boolean { if (idx >= _springs.length) return false; var s:Spring = _springs[idx]; s.p1.degree--; s.p2.degree--; reclaimSpring(s); _springs.splice(idx, 1); return true; } /** * Returns the particle list. This is the same vector instance backing * the simulation, so edit the vector with caution. * @return the particle list as a Vector. object */ public function get particles():Vector. { return _particles; } /** * Returns the spring list. This is the same vector instance backing * the simulation, so edit the vector with caution. * @return the spring list as a Vector. object */ public function get springs():Vector. { return _springs; } // -- Run Simulation -------------------------------------------------- /** * Advance the simulation for the specified time interval. * @param dt the time interval to step the simulation (default 1) */ public function tick(dt:Number=1):void { var p:Particle, s:Spring, i:uint, ax:Number, ay:Number; var dt1:Number = dt/2, dt2:Number = dt*dt/2; // remove springs connected to dead particles for (i=_springs.length; --i>=0;) { s = _springs[i]; if (s.die || s.p1.die || s.p2.die) { s.p1.degree--; s.p2.degree--; reclaimSpring(s); _springs.splice(i, 1); } } // update particles using Verlet integration for (i=_particles.length; --i>=0;) { p = _particles[i]; p.age += dt; if (p.die) { // remove dead particles reclaimParticle(p); _particles.splice(i, 1); } else if (p.fixed) { p.vx = p.vy = 0; } else { ax = p.fx / p.mass; ay = p.fy / p.mass; p.x += p.vx*dt + ax*dt2; p.y += p.vy*dt + ay*dt2; p._vx = p.vx + ax*dt1; p._vy = p.vy + ay*dt1; } } // evaluate the forces eval(); // update particle velocities for (i=_particles.length; --i>=0;) { p = _particles[i]; if (!p.fixed) { ax = dt1 / p.mass; p.vx = p._vx + p.fx * ax; p.vy = p._vy + p.fy * ax; } } // enfore bounds if (_bounds) enforceBounds(); } private function enforceBounds():void { var minX:Number = _bounds.x; var maxX:Number = _bounds.x + _bounds.width; var minY:Number = _bounds.y; var maxY:Number = _bounds.y + _bounds.height; for each (var p:Particle in _particles) { if (p.x < minX) { p.x = minX; p.vx = 0; } else if (p.x > maxX) { p.x = maxX; p.vx = 0; } if (p.y < minY) { p.y = minY; p.vy = 0; } else if (p.y > maxY) { p.y = maxY; p.vy = 0; } } } /** * Evaluates the set of forces in the simulation. */ public function eval():void { var i:uint, p:Particle; // reset forces for (i=_particles.length; --i >= 0; ) { p = _particles[i]; p.fx = p.fy = 0; } // collect forces for (i=0; i<_forces.length; ++i) { IForce(_forces[i]).apply(this); } } // -- Particle Pool --------------------------------------------------- /** The maximum number of items stored in a simulation object pool. */ public static var objectPoolLimit:int = 5000; protected static var _ppool:Vector. = new Vector.(); protected static var _spool:Vector. = new Vector.(); /** * Returns a particle instance, pulling a recycled particle from the * object pool if available. * @param mass the mass (charge) of the particle * @param x the particle's starting x position * @param y the particle's starting y position * @return a particle instance */ protected static function getParticle(mass:Number, x:Number, y:Number):Particle { if (_ppool.length > 0) { var p:Particle = _ppool.pop(); p.init(mass, x, y); return p; } else { return new Particle(mass, x, y); } } /** * Returns a spring instance, pulling a recycled spring from the * object pool if available. * @param p1 the first particle attached to the spring * @param p2 the second particle attached to the spring * @param restLength the rest length of the spring * @param tension the tension of the spring * @param damping the damping (friction) co-efficient of the spring * @return a spring instance */ protected static function getSpring(p1:Particle, p2:Particle, restLength:Number, tension:Number, damping:Number):Spring { if (_spool.length > 0) { var s:Spring = _spool.pop(); s.init(p1, p2, restLength, tension, damping); return s; } else { return new Spring(p1, p2, restLength, tension, damping); } } /** * Reclaims a particle, adding it to the object pool for recycling * @param p the particle to reclaim */ protected static function reclaimParticle(p:Particle):void { if (_ppool.length < objectPoolLimit) { _ppool.push(p); } } /** * Reclaims a spring, adding it to the object pool for recycling * @param s the spring to reclaim */ protected static function reclaimSpring(s:Spring):void { if (_spool.length < objectPoolLimit) { _spool.push(s); } } } // end of class Simulation }