Particle Systems

In these notes you will learn:

  • What a particle system is, and how it can be useful.
  • How to create a particle system that simulates an explosion.
  • How particular systems can be used to model a few other situations.

What is a Particle System?

A particle system is a technique often used in games and physics simulations that uses a large number of small animated objects to simulate a larger system. For example, you could use a particle system to simulate rain, snow, sparks, hair, a magical explosion, and so on.

High-end particle systems can be quite impressive. For instance, this video models human forms where the particles are bits of sand. In this video, the particles are modelled as 3D planks, and follow the rules of physics. And this video shows a variety of simulations made a by a particle system tool.

In this note we’ll see three examples of particle systems: an explosion, a water simulation, and program that draws bouncing dots and lines.

Designing an Explosion

Explosions are naturally simulated by particle system. A simple explosion The explosion starts at a given point, and then expands outwards. It consists of many small fragments, of varying size, moving in different directions at different speeds.

We’ll create an explosion particle by modifying this program (which makes random balls bounce around screen):

class Sprite {
  float x;
  float y;
  float dx;
  float dy;

  void update() {
    x += dx;
    y += dy;
  }
}

class BouncingBall extends Sprite {
  float diam;
  color fillColor;

  void render() {
    pushMatrix();

    noStroke();
    fill(fillColor);

    ellipse(x, y, diam, diam);

    popMatrix();
  }

  void update() {
    super.update();

    // hit top?
    if (y - diam / 2 < 0) {
      y = diam / 2;
      dy = -dy;
    }

    // hit bottom?
    if (y + diam / 2 > height) {
      y = height - diam / 2;
      dy = -dy;
    }

    // hit left?
    if (x - diam / 2 < 0) {
      x = diam / 2;
      dx = -dx;
    }

    // hit right?
    if (x + diam / 2 > width) {
      x = width - diam / 2;
      dx = -dx;
    }
  }
}

BouncingBall randomBouncingBall() {
  BouncingBall ball = new BouncingBall();
  ball.x = random(100, 200);
  ball.y = random(100, 200);
  ball.dx = random(-3, 3);
  ball.dy = random(-3, 3);
  ball.diam = random(50, 150);
  ball.fillColor = color(random(255),
                         random(255),
                         random(255));
  return ball;
}

ArrayList<BouncingBall> ballList;  // initially null
final int NUM_BALLS = 10;

void setup() {
  size(500, 500);

  // create the initially empty ArrayList of BouncingBall objects
  ballList = new ArrayList<BouncingBall>();
  int i = 0;
  while (i < NUM_BALLS) {
    ballList.add(randomBouncingBall());
    i += 1;
  }
}

void draw() {
  background(255);

  // render and update all the balls
  for (BouncingBall b : ballList) {
    b.render();
    b.update();
  }
}

The first thing we’ll do is change the names of some the variables, e.g. all occurrences of “ball” are replaced by “particle”:

// ...

class Particle extends Sprite {
  float diam;
  color fillColor;

  void render() {
    pushMatrix();

    noStroke();
    fill(fillColor);

    ellipse(x, y, diam, diam);

    popMatrix();
  }

  void update() {
    super.update();

    // hit top?
    if (y - diam / 2 < 0) {
      y = diam / 2;
      dy = -dy;
    }

    // hit bottom?
    if (y + diam / 2 > height) {
      y = height - diam / 2;
      dy = -dy;
    }

    // hit left?
    if (x - diam / 2 < 0) {
      x = diam / 2;
      dx = -dx;
    }

    // hit right?
    if (x + diam / 2 > width) {
      x = width - diam / 2;
      dx = -dx;
    }
  }
}

Particle randomParticle() {
  Particle p = new Particle();
  p.x = random(100, 200);
  p.y = random(100, 200);
  p.dx = random(-3, 3);
  p.dy = random(-3, 3);
  p.diam = random(50, 150);
  p.fillColor = color(random(255),
                      random(255),
                      random(255));
  return p;
}

ArrayList<Particle> particles;  // initially null
final int NUM_PARTICLES = 10;

void setup() {
  size(500, 500);

  // create the initially empty ArrayList of Particle objects
  particles = new ArrayList<Particle>();
  int i = 0;
  while (i < NUM_PARTICLES) {
    particles.add(randomParticle());
    i += 1;
  }
}

void draw() {
  background(255);

  // render and update all the particles
  for (Particle p : particles) {
    p.render();
    p.update();
  }
}

Next, we’ll make a few modifications to the randomParticle() function:

  • Instead of setting p.x and p.y randomly, all particles will start at (250, 250), the center of the screen.
  • We’ll make the range of particle diameters much smaller, e.g. between 3 and 5.
  • Instead of completely random colors, we’ll make all the particles various shades of red.

Here is the updated function:

Particle randomParticle() {
  Particle p = new Particle();
  p.x = 250;
  p.y = 250;
  p.dx = random(-3, 3);
  p.dy = random(-3, 3);
  p.diam = random(3, 5);
  p.fillColor = color(random(200, 255), 0, 0);
  return p;
}

If you run this in the program given above, and also increase NUM_PARTICLES to, say, 200, then the result should look somewhat like an explosion.

The particles bounce on off the edges, but for this explosion lets say that we want the particles to simply disappear off the screen. To do this we can just delete the update() function in Particle, and instead use the update() function that’s inherited from Sprite.

Another nice change is to make the explosion occur wherever the user clicks the mouse. To do this, we will need to make two modifications:

  1. Remove the while-loop code from the setup() function so there is no explosion as soon as the program starts. Importantly, we do need to keep the ArrayList initialization statement, so setup() becomes this:

    void setup() {
      size(500, 500);
    
      // create the initially empty ArrayList of Particle objects
      particles = new ArrayList<Particle>();
    }
    
  2. Add a mousePressed function (which is called automatically when a mouse button is pressed) that re-initializes the explosion to start at (mouseX, mouseY):

    void mousePressed() {
      particles = new ArrayList<Particle>();
      int i = 0;
      while (i < NUM_PARTICLES) {
        Particle p = randomParticle();
        p.x = mouseX;
        p.y = mouseY;
        particles.add(p);
        i += 1;
      }
    }
    

Here is the new program:

class Sprite {
  float x;
  float y;
  float dx;
  float dy;

  void update() {
    x += dx;
    y += dy;
  }
}

class Particle extends Sprite {
  float diam;
  color fillColor;

  void render() {
    pushMatrix();

    noStroke();
    fill(fillColor);

    ellipse(x, y, diam, diam);

    popMatrix();
  }
}

Particle randomParticle() {
  Particle ball = new Particle();
  ball.x = 250;
  ball.y = 250;
  ball.dx = random(-4, 4);
  ball.dy = random(-4, 4);
  ball.diam = random(3, 5);
  ball.fillColor = color(random(200, 255), 0, 0);
  return ball;
}

ArrayList<Particle> particles;  // initially null
final int NUM_PARTICLES = 10;

void setup() {
  size(500, 500);

  // create the initially empty ArrayList of Particle objects
  particles = new ArrayList<Particle>();
}

void draw() {
  background(255);

  // render and update all the particles
  for (Particle p : particles) {
    p.render();
    p.update();
  }
}

void mousePressed() {
  particles = new ArrayList<Particle>();
  int i = 0;
  while (i < NUM_PARTICLES) {
    Particle p = randomParticle();
    p.x = mouseX;
    p.y = mouseY;
    particles.add(p);
    i += 1;
  }
}

Simulating Water

Simulating water is an interesting and challenging task. Here, we will think of water as consisting of many small blue particles that move according to gravity. A major difference between this particle system and the one for the explosion is that the water will be running continuously. The trick we’ll use to do this is to re-initialize a particle when it goes off the screen to start back at the source of the water.

Each drop of water is a Droplet object. Some extra code has been added to detect when the a drop is off the screen, and to then recycle it:

class Sprite {
  float x;
  float y;
  float dx;
  float dy;

  void update() {
    x += dx;
    y += dy;
  }
}

class Droplet extends Sprite {
  float gravity; // force of gravity on this droplet

  void randomRestart() {
    x = 0;
    y = height - 10;  // height is the height of the screen
    dx = 8 * random(0.24, 0.245);
    dy = 8 * random(-0.45, -0.35);
    gravity = 2 * random(0.005, 0.015);
  }

  void render() {
    noStroke();
    fill(0, 0, 255);
    rect(x, y, 15, 15);
  }

  void update() {
    super.update();

    dy += gravity;

    if (offScreen()) {
      randomRestart();
    }
  }

  boolean offScreen() {
    if (y > height || x > width || x < 0 || y < 0) {
      return true;
    } else {
      return false;
    }
  }
}

ArrayList<Droplet> drops;
int NUM_DROPLETS = 200;

void setup() {
  size(900, 400);

  drops = new ArrayList<Droplet>();  // create empty ArrayList
  int i = 0;
  while (i < NUM_DROPLETS) {  // add NUM_DROPLETS drops to the ArrayList
    Droplet d = new Droplet();
    d.randomRestart();
    drops.add(d);
    ++i;
  }
}

void draw() {
  background(255);
  for (Droplet d : drops) {
    d.render();
    d.update();
  }
}

Dots and Lines

The final example is a program that does not directly simulate a physical process, but instead shows a useful technique for drawing “dots” and “lines”. The idea is that whenever the user clicks the mouse, a dot appears with line connecting it to the previously clicked dot. For fun, the dots bounce up and down due to gravity.

Note

Drawing connected points and lines turns out to be used frequently enough that Processing provides some built-in functions to do it for you: see the documentation for beginShape if you are curious.

Here’s the program:

class Sprite {
  float x;
  float y;
  float dx;
  float dy;

  void update() {
    x += dx;
    y += dy;
  }
}

class Dot extends Sprite {
  float gravity;
  float elasticity;

  void render() {
    fill(255, 0, 0);
    noStroke();
    ellipse(x, y, 10, 10);
  }

  void update() {
    super.update();

    dy += gravity;
    if (y >= height) {  // check if dot has hit the bottom edge
      dy = -(dy * elasticity);
      y = height - 1;
    }
  }
} // class Dot


ArrayList<Dot> dots;  // dots is initially null

void setup() {
  size(500, 500);
  smooth();

  dots = new ArrayList<Dot>(); // make an empty ArrayList of dots
}

void draw() {
  background(255);

  // draw the lines
  Dot prev = null;
  for (Dot d : dots) {
    if (prev != null) {
      stroke(0);
      line(d.x, d.y, prev.x, prev.y);
    }
    prev = d;
  }

  // draw the dots
  for (Dot d : dots) {
    d.render();
    d.update();
  }
}

void mousePressed() {
  Dot d = new Dot();
  d.x = mouseX;
  d.y = mouseY;
  d.gravity = 0.1;
  d.elasticity = 0.8;
  dots.add(d);
}

An important detail occurs in draw(): the variable prev refers to the dot that comes before dot d. However, the very first dot doesn’t have a previous one, and so we need to use an if-statement to check the value of prev. If prev is null, then we don’t draw a line.

Questions

  1. What is a particle system? What are some examples of things that can modelled as a particle system?

  2. What is the value of variable particles after this statement executes?

    ArrayList<Particle> particles;
    
  3. Suppose particles is a variable of type ArrayList<Particle> that has just been created by this statement:

    ArrayList<Particle> particles;
    

    Write a line of code that makes particles refer to a new ArrayList<Particle> object of size 0.

  4. In the draw() function for the dots and lines program, there is this if-statement:

    if (prev != null) {
      stroke(0);
      line(d.x, d.y, prev.x, prev.y);
    }
    

    Why does this test if prev is not null? Would it work if you got rid of the if-statement and wrote this instead?

    stroke(0);
    line(d.x, d.y, prev.x, prev.y);
    

Programming Questions

  1. In the final explosion program, the initial burst of particles is a distinct square-shape. Why is that? To make the burst circular, try modifying how randomParticle sets p.dx and p.dy. Create two new variables, speed and angle, and assign them random values. Then set dy to speed * sin(angle) and dx to speed * cos(angle).
  2. The pre-defined Processing variable frameRate is the approximate number of frames per second that a program is running at. For the water simulation, print the value of frameRate in the upper-left corner of the screen (and update it on each call to draw()) so that it is easy to see what the current frame rate is.
  3. In the water simulation, modify randomRestart so that the size of the droplet is randomly chosen within a min/max range of values of your choosing.
  4. In the water simulation, modify the program so that the source of the water is the location of the mouse pointer. Thus, as you move the pointer around the screen the entire flow of water moves with it.
  5. Modify your answer to the previous question so that if you click on the screen, then the water flow is “pinned” to that location for the rest of the program. That is, the water flow will not follow the mouse after you have clicked a button.
  6. Modify the bouncing dots and lines program so that the dots don’t start moving until the user presses a key. If the user then presses a key while the dots are moving, they should immediately stop where they are and not move again until the user presses another key.