Simulating Water with a Particle System

In these notes you will learn:

  • About particle systems and their many uses.
  • How to model a drop of water as an object.
  • How to model a flow of water as an ArrayList of objects.

Introduction

One of the things that’s easy to do in object-oriented programming is create many copies of an object. Here, we’ll see how to simulate a stream of water by using hundreds of small rectangles each representing one droplet of water. This is an example of a particle system, and they are often used to simulate things like water, smoke, fire, clouds, and so on.

Note

Particle systems can be used to create many different kinds of special effects. For instance, if you search for “particle system” on YouTube you’ll find impressive demos like this.

What is Water?

Roughly speaking, water consists of many small particles called “drops of water”. Each droplet has a position and velocity, and so we can write this class:

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 render() {
    noStroke();
    fill(0, 0, 255);
    rect(x, y, 15, 15);
  }

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

One Drop of Water

Now that we have the Droplet class, lets think about the path a single drop of water takes when it is shot out of a hose. Imagibne the droplet starting on the left side of the screen and moving towards the right at an (initially) upward angle. As it moves, gravity is constantly pulling it down towards the bottom of the screen. Thus the particle traces a curve across the screen: it starts low, goes high, and then goes low again.

Note

If this were a physics course we could sharpen this description to the point where a particle’s path could be described mathematically. However, we are not interested in the exact equations of movement here. All we want is to simulate particle movement well enough to look good for whatever application we are using it for.

So lets get this working in code:

// ... Droplet class as above ...

Droplet drop;

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

  drop = new Droplet();
  drop.x = 10;
  drop.y = 290;
  drop.dx = 2.86;
  drop.dy = -1.30;
  drop.gravity = 0.01;
}

void draw() {
  background(255);
  drop.render();
  drop.update();
}

Run this program and you’ll see a small blue square fly across the screen following a curved trajectory.

Multiple Particles

To simulate a flow of water out of a hose, we need to make some modifications:

  • We need to add many more droplets — enough to make it look somewhat like a stream of water.

  • We’ll add some randomness to the starting position and velocity of the droplets so that they spread out a bit. If we don’t do this then all the particles will follow exactly the same path.

  • We will want to “recycle” water droplets. With so many Droplet objects being animated at once, our program might start to slow down or eat up a lot of memory.

    So we’ll re-use droplets once they have flown off the screen and are no longer visible. This is a common trick, and it gives the illusion of a continuous flow of water while using a fixed amount of memory.

Here’s the modified program:

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() {
    x += dx;
    y += dy;
    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();
  }
}

Notice the following:

  • The constant NUM_DROPLETS is how many droplets of water will be in the simulation. Play around with this value to see the results you get.

    Keep in mind that the bigger the value of NUM_DROPLETS, the more time and memory the program takes to run.

  • The randomRestart function in Droplet re-sets the droplet to be at the left side of the screen. Its velocity and gravity are then set randomly.

  • The offScreen function returns true if the droplet is off the screen, and false if it is on the screen.

  • The update function calls randomRestart whenever it goes off the screen. In this way particles that are no longer visible get re-used in the animation.

The results of this program are quite interesting: it creates an effect that is, at least, suggestive of water. It’s far from perfect, though, and you would need to play around with it some more to get it looking just right.

Programming Questions

  1. The pre-defined Processing variable frameRate is the approximate number of frames per second that a program is running at. 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.
  2. Modify randomRestart so that the size of the droplet is randomly chosen within a min/max range of values of your choosing.
  3. 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.
  4. 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.