If you're seeing this message, it means we're having trouble loading external resources on our website.

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

Main content

Particle systems with forces

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 (F=MA) 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:  Fg=Gm1m2||r||2r^
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 m1 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=Gm1||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):
Fg=gm2r^
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 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 direction
Repeller 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);

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 vector for force between objects
  var dir = PVector.sub(this.position, p.position); 
  // Calculate distance between objects
  var dist = dir.mag();
  // Keep distance within a reasonable range
  dist = constrain(dist, 1, 100);    
  // Calculate repelling force,
  // inversely proportional to distance squared
  var force = -1 * this.power/ (dist * dist);     
  // Normalize direction vector
  // (discarding distance information)
  dir.normalize();
  // Calculate force vector: direction * magnitude
  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:

Want to join the conversation?

  • leaf red style avatar for user Blaze
    One thing I noticed about the repeller is that it only repels once the "liquid" particles are "inside" it. Is there a way to check if they are hitting the edges then repel?
    (7 votes)
    Default Khan Academy avatar avatar for user
  • leaf green style avatar for user Theodore Chapman
    Oh noes guy says the function eval() can be harmful, can anyone tell me why? Also, is there a better place to put questions like this, that don't relate to any of the lessons?
    (2 votes)
    Default Khan Academy avatar avatar for user
    • spunky sam blue style avatar for user Dalendrion
      For your second question: No, this is the right place to be.

      For your first question: JavaScript can be powerful. eval() allows other people to execute their JavaScript code on your website. That means you're giving those people a lot of power...which can be abused in ways you may not have expected. This opens a path for hackers.
      (12 votes)
  • piceratops tree style avatar for user Andre G
    Was this one hard to understand for anybody or is it just me?
    (6 votes)
    Default Khan Academy avatar avatar for user
  • blobby green style avatar for user daverhine
    angleMode = "radians";

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

    Repeller.prototype.display = function() {
    image(getImage("cute/Rock"), this.position.x, this.position.y-50, 60, 80);
    };

    Repeller.prototype.calculateRepelForce = function(p) {
    var dir = PVector.sub(this.position, p.position);
    var d = dir.mag();
    dir.normalize();
    d = constrain(d, 1, 100);
    var force = -1 * this.power/ (d * d);
    dir.mult(force);
    return dir;
    };

    var Particle = function(position) {
    this.acceleration = new PVector(0, 0);
    this.velocity = new PVector(random(0, 1), random(-1, 0.5));
    this.position = position.get();
    this.timeToLive = 255.0;
    this.mass = 10;
    };

    Particle.prototype.run = function() {
    this.update();
    this.display();
    };

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

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

    Particle.prototype.display = function() {
    noStroke();
    fill(204, 241, 255, 100);
    ellipse(this.position.x, this.position.y, 12, 12);
    };

    Particle.prototype.isDead = function(){
    if (this.timeToLive < 0.0 || this.position.x > width || this.position.y < 0 || this.position.y > height) {
    return true;
    } else {
    return false;
    }
    };


    var ParticleSystem = function(origin, height) {
    this.origin = origin.get();
    this.height = height;
    this.particles = [];
    };

    ParticleSystem.prototype.addParticle = function() {
    for (var i = 0; i < 10; i++) {
    var startPos = new PVector(this.origin.x,
    this.origin.y + random(-this.height/2, this.height/2));
    this.particles.push(new Particle(startPos));
    }
    };

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

    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);
    }
    };

    ParticleSystem.prototype.run = function(){
    for (var i = this.particles.length-1; i >= 0; i--) {
    var p = this.particles[i];
    p.run();
    if (p.isDead()) {
    this.particles.splice(i, 1);
    }
    }
    };

    var repellers = [];

    //for (var i = 0; i<10; i++){

    mousePressed = function(){
    repellers.push(new Repeller(mouseX, mouseY));
    };

    // Set up water pressure, particle system, and repeller
    var pressure = new PVector(0.4, 0);
    var particleSystem = new ParticleSystem(new PVector(0, height/2), 200);
    //var repeller = new Repeller(width/2, height/2);

    draw = function() {

    // Draw ground
    for (var i = 0; i < 10; i++) {
    image(getImage("cute/DirtBlock"), i*95, -50);
    image(getImage("cute/DirtBlock"), i*95, 268);
    }

    // Draw river
    noStroke();
    fill(163, 230, 255);
    rect(0, 86, width, 233);

    for (var i = 0; i < repellers.length; i++) {
    // Update particle system
    particleSystem.applyForce(pressure);
    particleSystem.applyRepeller(repellers[i]);
    repellers[i].display();


    }

    //repeller.display();

    particleSystem.addParticle();
    particleSystem.run();
    };

    so i did the next challenge and it works but it doesn't pass, why?
    (4 votes)
    Default Khan Academy avatar avatar for user
  • old spice man green style avatar for user Elijah Daniels
    Visually, I can see what this.acceleration.mult(0); does, but what is it actually doing?
    (3 votes)
    Default Khan Academy avatar avatar for user
  • old spice man blue style avatar for user peanut butter
    Why, on KA, does some program's text get misaligned? I can see from the tumbnails that they were supposed to be aligned but for me its all cut off?
    (1 vote)
    Default Khan Academy avatar avatar for user
    • old spice man green style avatar for user Bob Lyon
      The easiest explanation is "font mismatch". The author's machine uses a font that your machine does not have, so your machine use its default font. Disappointment ensues.
      In the example you cite, the author is using his default font. We expect defaults to be (nearly) the same, but there is no guarantee of that!
      Scrutinize the word "You" in the thumbnail versus what you see in the running program. The "o" is half of the height of the "Y" in the thumbnail. On my machine, the "o" is 70% or 80% of the height of the "Y". I'm sure this mismatch carries over to the widths too, and thus our disappointment.
      (7 votes)
  • blobby green style avatar for user stan101stan102
    Last example, line 38, I typed this.position = position instead of this.position = position.get(); and got a different result. I think I know why, but I'm not 100% clear. Please explain.
    (3 votes)
    Default Khan Academy avatar avatar for user
    • starky ultimate style avatar for user johnepp
      It's because position.get(); will return the original position variable, but the position variable is being changed in between the point where they first set position and where you changed the code. Just using position will use the current position value, not the original.
      (2 votes)
  • piceratops tree style avatar for user Super Duck
    On the second program in this lesson line 49 it says
    Particle.call(this, position);
    can anyone tell me what .call does
    (2 votes)
    Default Khan Academy avatar avatar for user
  • hopper cool style avatar for user JPhilip
    How would I get a particle to react to another particle? I know how to adjust each particle one-by-one using a for loop. But how could I get an individual particle to react to all other particles? Thanks!
    (2 votes)
    Default Khan Academy avatar avatar for user
    • male robot donald style avatar for user JavaLava
      Probably the best way would be using a nested for loop. First loops through all the particles once. Then do it again twice. Then, if the item in the first loop does not equal the item in the second, react!
      for (var i = 0; i < particles.length; i++) {
      for (var j = 0; j < particles.length; j++) {
      if (particles[i] !== particles[j]) {
      // Do whatever
      }
      }
      }
      (2 votes)
  • piceratops ultimate style avatar for user Isy
    How come the repeller example above doesn't treat the repeller as a solid object that makes things move around it, but instead just randomly scatters some and sightly changes the angle of the rest of the particles.
    (1 vote)
    Default Khan Academy avatar avatar for user