1 /**
  2  * Constructs a new empty simulation.
  3  *
  4  * @param {array} particles
  5  * @returns {pv.Simulation} a new simulation for the specified particles.
  6  * @see pv.Simulation
  7  */
  8 pv.simulation = function(particles) {
  9   return new pv.Simulation(particles);
 10 };
 11 
 12 /**
 13  * Constructs a new simulation for the specified particles.
 14  *
 15  * @class Represents a particle simulation. Particles are massive points in
 16  * two-dimensional space. Forces can be applied to these particles, causing them
 17  * to move. Constraints can also be applied to restrict particle movement, for
 18  * example, constraining particles to a fixed position, or simulating collision
 19  * between circular particles with area.
 20  *
 21  * <p>The simulation uses <a
 22  * href="http://en.wikipedia.org/wiki/Verlet_integration">Position Verlet</a>
 23  * integration, due to the ease with which <a
 24  * href="http://www.teknikus.dk/tj/gdc2001.htm">geometric constraints</a> can be
 25  * implemented. For each time step, Verlet integration is performed, new forces
 26  * are accumulated, and then constraints are applied.
 27  *
 28  * <p>The simulation makes two simplifying assumptions: all particles are
 29  * equal-mass, and the time step of the simulation is fixed. It would be easy to
 30  * incorporate variable-mass particles as a future enhancement. Variable time
 31  * steps are also possible, but are likely to introduce instability in the
 32  * simulation.
 33  *
 34  * <p>This class can be used directly to simulate particle interaction.
 35  * Alternatively, for network diagrams, see {@link pv.Layout.Force}.
 36  *
 37  * @param {array} particles an array of {@link pv.Particle}s to simulate.
 38  * @see pv.Layout.Force
 39  * @see pv.Force
 40  * @see pv.Constraint
 41  */
 42 pv.Simulation = function(particles) {
 43   for (var i = 0; i < particles.length; i++) this.particle(particles[i]);
 44 };
 45 
 46 /**
 47  * The particles in the simulation. Particles are stored as a linked list; this
 48  * field represents the first particle in the simulation.
 49  *
 50  * @field
 51  * @type pv.Particle
 52  * @name pv.Simulation.prototype.particles
 53  */
 54 
 55 /**
 56  * The forces in the simulation. Forces are stored as a linked list; this field
 57  * represents the first force in the simulation.
 58  *
 59  * @field
 60  * @type pv.Force
 61  * @name pv.Simulation.prototype.forces
 62  */
 63 
 64 /**
 65  * The constraints in the simulation. Constraints are stored as a linked list;
 66  * this field represents the first constraint in the simulation.
 67  *
 68  * @field
 69  * @type pv.Constraint
 70  * @name pv.Simulation.prototype.constraints
 71  */
 72 
 73 /**
 74  * Adds the specified particle to the simulation.
 75  *
 76  * @param {pv.Particle} p the new particle.
 77  * @returns {pv.Simulation} this.
 78  */
 79 pv.Simulation.prototype.particle = function(p) {
 80   p.next = this.particles;
 81   /* Default velocities and forces to zero if unset. */
 82   if (isNaN(p.px)) p.px = p.x;
 83   if (isNaN(p.py)) p.py = p.y;
 84   if (isNaN(p.fx)) p.fx = 0;
 85   if (isNaN(p.fy)) p.fy = 0;
 86   this.particles = p;
 87   return this;
 88 };
 89 
 90 /**
 91  * Adds the specified force to the simulation.
 92  *
 93  * @param {pv.Force} f the new force.
 94  * @returns {pv.Simulation} this.
 95  */
 96 pv.Simulation.prototype.force = function(f) {
 97   f.next = this.forces;
 98   this.forces = f;
 99   return this;
100 };
101 
102 /**
103  * Adds the specified constraint to the simulation.
104  *
105  * @param {pv.Constraint} c the new constraint.
106  * @returns {pv.Simulation} this.
107  */
108 pv.Simulation.prototype.constraint = function(c) {
109   c.next = this.constraints;
110   this.constraints = c;
111   return this;
112 };
113 
114 /**
115  * Apply constraints, and then set the velocities to zero.
116  *
117  * @returns {pv.Simulation} this.
118  */
119 pv.Simulation.prototype.stabilize = function(n) {
120   var c;
121   if (!arguments.length) n = 3; // TODO use cooling schedule
122   for (var i = 0; i < n; i++) {
123     var q = new pv.Quadtree(this.particles);
124     for (c = this.constraints; c; c = c.next) c.apply(this.particles, q);
125   }
126   for (var p = this.particles; p; p = p.next) {
127     p.px = p.x;
128     p.py = p.y;
129   }
130   return this;
131 };
132 
133 /**
134  * Advances the simulation one time-step.
135  */
136 pv.Simulation.prototype.step = function() {
137   var p, f, c;
138 
139   /*
140    * Assumptions:
141    * - The mass (m) of every particles is 1.
142    * - The time step (dt) is 1.
143    */
144 
145   /* Position Verlet integration. */
146   for (p = this.particles; p; p = p.next) {
147     var px = p.px, py = p.py;
148     p.px = p.x;
149     p.py = p.y;
150     p.x += p.vx = ((p.x - px) + p.fx);
151     p.y += p.vy = ((p.y - py) + p.fy);
152   }
153 
154   /* Apply constraints, then accumulate new forces. */
155   var q = new pv.Quadtree(this.particles);
156   for (c = this.constraints; c; c = c.next) c.apply(this.particles, q);
157   for (p = this.particles; p; p = p.next) p.fx = p.fy = 0;
158   for (f = this.forces; f; f = f.next) f.apply(this.particles, q);
159 };
160