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
andp.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:
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 theArrayList
initialization statement, sosetup()
becomes this:void setup() { size(500, 500); // create the initially empty ArrayList of Particle objects particles = new ArrayList<Particle>(); }
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¶
What is a particle system? What are some examples of things that can modelled as a particle system?
What is the value of variable
particles
after this statement executes?ArrayList<Particle> particles;
Suppose
particles
is a variable of typeArrayList<Particle>
that has just been created by this statement:ArrayList<Particle> particles;
Write a line of code that makes
particles
refer to a newArrayList<Particle>
object of size 0.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 notnull
? 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¶
- 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
setsp.dx
andp.dy
. Create two new variables,speed
andangle
, and assign them random values. Then setdy
tospeed * sin(angle)
anddx
tospeed * cos(angle)
. - 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 offrameRate
in the upper-left corner of the screen (and update it on each call todraw()
) so that it is easy to see what the current frame rate is. - 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. - 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.
- 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.
- 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.