If you're seeing this message, it means we're having trouble loading external resources for Khan Academy.

If you're behind a web filter, please make sure that the domains *.kastatic.org and *.kasandbox.org are unblocked.

So far in this section, we’ve been focusing on structuring our code in an object-oriented way to manage a collection of particles. Maybe you noticed, or maybe you didn’t, but during this process we unwittingly took a couple steps backward from where we were in previous sections. Let’s examine the constructor of our simple Particle object:

var Particle = function(position) {
  this.acceleration = new PVector(0, 0.05);
  this.velocity = new PVector(random(-1, 1), random(-1, 0));
  this.position = new PVector(position.x, position.y);
  this.timeToLive = 255.0;
};

And now let’s look at the update() method:

Particle.prototype.update = function(){
  this.velocity.add(this.acceleration);
  this.position.add(this.velocity);
  this.timeToLive -= 2;
};

Notice that our acceleration is constant, it's never set beyond the constructor. A much better framework would be to follow Newton’s second law (\vec{F} = M\vec{A}) and incorporate the force accumulation algorithm we worked so hard on in the Forces section.

The first step is to add in an applyForce() method. (Remember, we need to make a copy of the PVector before we divide it by mass.)

Particle.prototype.applyForce = function(force) {
  var f = force.get();
  f.div(this.mass);
  this.acceleration.add(f);
};

Once we have this, we can add in one more line of code to clear the acceleration at the end of update().

Particle.prototype.update = function() {
  this.velocity.add(this.acceleration);
  this.position.add(this.velocity);
  this.acceleration.mult(0);
  this.timeToLive -= 2.0;
};

And thus, we have a Particle object that can have force applied to it. Now, where should we call the applyForce() function? Where in the code is it appropriate to apply a force to a particle? The truth of the matter is that there’s no right or wrong answer; it really depends on the exact functionality and goals of a particular program. Still, we can create a generic situation that would likely apply to most cases and craft a model for applying forces to individual particles in a system.

Adding wind

Let’s consider the following goal: Apply a force globally every time through draw() to all particles. We’ll start with applying a simple wind-like force that pushes the particles to the right:

var wind = new PVector(0.4, 0);

We said it should always be applied, i.e. in draw(), so let’s take a look at our draw() function as it stands.

draw = function() {
  background(168, 255, 156);
  particleSystem.addParticle();
  particleSystem.run();
};

Well, it seems that we have a small problem. applyForce() is a method written inside the Particle object, but we don’t have any reference to the individual particles themselves, only the ParticleSystem object: the variable particleSystem.

Since we want all particles to receive the force, however, we can decide to apply the force to the particle system and let it manage applying the force to all the individual particles.

draw = function() {
  background(168, 255, 156);
  particleSystem.applyForce(wind);
  particleSystem.addParticle();
  particleSystem.run();
};

Of course, if we call a new function on the ParticleSystem object in draw(), well, we have to write that function in the ParticleSystem object. Let’s describe the job that function needs to perform: receive a force as a PVector and apply that force to all the particles.

Now in code:

ParticleSystem.prototype.applyForce = function(f){
  for(var i = 0; i < this.particles.length; i++){
    this.particles[i].applyForce(f);
  }
};

It almost seems silly to write this function. What we’re saying is “apply a force to a particle system so that the system can apply that force to all of the individual particles.” Nevertheless, it’s really quite reasonable. After all, the ParticleSystem object is in charge of managing the particles, so if we want to talk to the particles, we’ve got to talk to them through their manager.

Here it is, all together. Play around with the wind force and see how it affects the particle movement, and notice how particles of different mass respond differently. Think about why that is.

Adding gravity

Now let's apply a more complex force, gravity, which is different from wind because it varies based on the mass of the objects its applied to.

Let's recall the equation for calculating the force of gravity between two masses:  \vec{F_g} = \frac{Gm_1m_2}{||r||^2} \hat{r}

Remember that when we're modeling the force of gravity on earth, the force exerted by the earth overwhelms all other gravitational forces, so the only equation we're dealing with is to compute the force of gravity between the earth and the object.G and m_1 are the same for every particle, and r(the radius from the earth) is basically the same (since earth's radius is so large compared to how little the particles move away from it), so we typically simplify those as simply g, the constant for gravity on earth:

g = \frac{Gm_1}{||r||^2}

Now, the force of gravity is just some constant g, times the mass of the particles, multiplied by a unit vector in the direction of the force (which will always be down):

\vec{F_g} = g m_2 \hat{r}

In code, that means that we will need to apply a different gravity force to each particle based on its mass. How can we do that? We can't re-use the existing applyForce function, because it expects the same force for each particle. We might consider passing a parameter to it that instructs applyForce to multiply by the mass, but let's leave that function alone and create a new function, applyGravity, which calculates force based on a global constant vector:


// A constant down vector, declared at the top
var gravity = new PVector(0, 0.2);
ParticleSystem.prototype.applyGravity = function() {
    for(var i = 0; i < this.particles.length; i++) {
        var particleG = gravity.get();
        particleG.mult(this.particles[i].mass);
        this.particles[i].applyForce(particleG);
    }
};

Now, if we've done this correctly, all of our particles should fall at the same rate in the simulation below. That's because the force of gravity is based on multiplying the mass, but the acceleration is based on dividing by the mass, so in the end, the mass doesn't have an effect. It might seem silly to go through that much effort to not have an effect, but its important once we start combining multiple different forces.

Adding repellers

What if we wanted to take this example one step farther and add a repeller object that pushes particles away as they get close? It would be similar to the attractor object we created earlier, only pushing in the opposite direction. Once again, like gravity, we must calculate a different force for each particle, but in the case of the repeller, the difference is that the the calculation isn't based on mass, it's based on distance. For gravity, all of our force vectors had the same direction, but for the repeller, all the force vectors will have different directions:

Gravity force: all the vectors have the same directionRepeller force: all the direction vectors are different

Because the calculation of a repeller force is a little more complex than the calculation of gravity (and we might ultimately want multiple repellers!), we will solve this problem by incorporating a new Repeller object into our simple particle system plus gravity example. We’re going to need two major additions to our code:

  1. A Repeller object (declared, initialized, and displayed).
  2. A function that passes the Repeller object into the ParticleSystem so that it can apply a force to each particle object.
var particleSystem = new ParticleSystem(new PVector(width/2, 50));
var repeller = new Repeller(width/2-20, height/2);
var gravity = new PVector(0, 0.1);

var draw = function() {
  background(214, 255, 171);

  // Apply gravity force to all Particles
  particleSystem.applyForce(gravity);
  particleSystem.applyRepeller(repeller);
  repeller.display();
  particleSystem.addParticle();
  particleSystem.run();
};

Making a displayable Repeller object is easy; it’s a duplicate of the Attractor object that we created earlier:

var Repeller = function(x, y) {
  this.position = new PVector(x, y);
};

Repeller.prototype.display = function() {
  stroke(255);
  strokeWeight(2);
  fill(127);
  ellipse(this.position.x, this.position.y, 32, 32);
};

The more difficult question is, how do we write the applyRepeller() method? Instead of passing a PVector into a function like we do with applyForce(), we’re going to instead pass a Repeller object into applyRepeller() and ask that function to do the work of calculating the force between the repeller and all particles:


ParticleSystem.prototype.applyRepeller = function(r) {
  for(var i = 0; i < this.particles.length; i++){
    var p = this.particles[i];
    var force = r.calculateRepelForce(p);
    p.applyForce(force);
  }
};

The big difference here is that a new force is calculated for each particle, because, as we saw before, the force is different depending on properties of each particle in relation to the repeller. We calculate that force using the calculateRepelForce function, which is the inverse of the calculateAttractionForce function from our Attractors.

Repeller.prototype.calculateRepelForce = function(p) {
  // Calculate direction of force
  var dir = PVector.sub(this.position, p.position); 
  // Distance between objects
  var d = dir.mag();
  // Normalize direction vector (distance doesn't matter here, we just want this vector for direction)
  dir.normalize();
  // Keep distance within a reasonable range
  d = constrain(d, 1, 100);    
  // Repelling force is inversely proportional to distance
  var force = -1 * this.power/ (d * d);     
  // Get force vector --> magnitude * direction
  dir.mult(force);                                  
  return dir;
};

Notice how throughout this entire process of adding a repeller to the environment, we’ve never once considered editing the Particle object itself. A particle doesn’t actually have to know anything about the details of its environment; it simply needs to manage its location, velocity, and acceleration, as well as have the ability to receive an external force and act on it.

So we can now look at this example in its entirety. Try changing the power of the forces acting on the particles - the gravity and the repeller - and see how that changes them: