Lesson 19 - Particle Engines

Introduction

A particle system is a technique in game physics, motion graphics,
and computer graphics that uses a large number of very small sprites,
3D models, or other graphic objects to simulate certain kinds of
"fuzzy" phenomena, which are otherwise very hard to reproduce with
conventional rendering techniques - usually highly chaotic systems,
natural phenomena, or processes caused by chemical reactions. The
algorithmic implementation of such particle systems is often referred
to as a particle engine. This lesson is a demonstration of two such
particle engines.

In contrast to the last lesson, this one is fairly complex for two
reasons. First, the support by three.js for particle engines is
relatively primitive so the user has to do a bit more work than for
quadrics. Secondly, the particle engine support in three.js is
relatively simple in that it only support simple image-based sprites.
And those sprites (like the sprites we saw in lesson 13) always face
the camera, so they're not very flexible.

So we'll also implement our own particle engine which will
demonstrate the basic principles. The tradeoff is that while the
three.js particle system can handle a huge number of particles and
still be performant, our own particle engine will bog down with a far
smaller number of particles.

Initialization

We initialize the gfxScene object same as always. We also declare some global vars:

We'll go over what each of these is for as we go along. Next step is
to instantiate the scene, which is pretty standard. We do specify a
floor of size 5x5 tiles so we end up with a 20x20 checkerboard. Then we
call initDemo, which is where it gets interesting.

First, we instantiate the three.js Clock object:

clock = new THREE.Clock(true);

We use the clock to control the degree of motion of the three.js
particle system sprites. Then we create a simple wire-frame box mesh (in
red), just as a visual reference. The box is the same size as the
checkerboard floor.

Let's look at the code in createParticleSystem.
We first set a "constant" that is the number of particles in the
system. Then we allocate a plain geometry that will hold all the
verticies which are the "particles".

var PARTICLE_COUNT = 500;
var particles = new THREE.Geometry();

Then we create the vertices and add them to the particle's geometry.
This loop creates all the vertices in a range of -20 to 20 (the size of
the checkerboard floor) in all directions, centered around the origin.

We make the snowflake white at 40% of its natural size and set it to
be partially transparent so it is blended into the scene. Finally we
create the particle system and return it to be added to the scene.

We start by fetching the "deltaTime", which is the time in seconds
since the animation routine was last called. This is usually about
0.016666 since the animation typically runs around 60 fps 1 second
divided by 60 is ~0.016. One of the advantages of using the delta time
is that it more or less automatically adjusts the rate of descent of
the snowflakes so they fall at the same steady rate since the longer it
has been since the last call to delta time, the farther the snowflakes
drops. Since the routine is called ~60 times a second, each snowflake
falls about 1 unit per second and therefore take about 5-10 seconds to
fall through the lower limit (-10).

Then we simply loop through all the verticies adjust the elevation
by the deltaTime factor. For each vertex we check if they have already
reached the bottom limit. If so, we move the vertex back up near the
top. But note that the z and x coordinates are not changed so each
vertex just cycles up and down in straight line.

Finally, we notify three.js that we have adjust the vertex
positions. Otherwise the mesh would not get updated. And that's it for
the three.js particle system.

Our Own Particle System: The Balls

So now, let's take a look at the significantly more complex
home-brewed particle system. This one consists of a "cannon" poking up
through the checkerboard floor. It fires a steady stream of colored
spheres into the air, randomly altering the speed and direction of the
launched "beach balls". Once launched, the balls are subject to gravity
and fall down to bounce across the floor until they fall off, plunging
into the nether regions.

So let's go through the code - there's a fair amount of it. First,
let's take a look at the beach-balls, as they are quite simple. All the
beach-balls consist of a three.js Sphere, it's radius, and two
vectors holding the location and velocity of the ball. Here is the
constructor:

Most of this is self-explanatory such as velocity and location, the
number of segments on the sphere and the base and delta radius of the
balls. RESTITUTION is the amount that
the ball will rebound each time it bounces. If the ball were perfectly
elastic (always rebounding to the same height), resititution would be
1.0. In this case, it loses 25% of its bounce each time it hits the
floor. So the bouncing looks like this:

The update function for the beach-ball is pretty simple since all it
does is update the location using the current velocity then update the
velocity with the gravitational pull and finally updates the mesh's
position:

The parameters are the scene itself (since the cannon creates
the beach-balls and adds them to the scene). We also pass in the size
of the floor so the cannon knows when the balls have fallen off the
floor. deltaT basically controls how
fast the cannon tries to fire balls. Making the parameter less than 10
has little effect since the code will only launch one ball per frame no
matter how fast the computer. Making it larger will greatly reduce the
number of balls since the cannon won't launch a ball at every frame.
We'll cover this shortly.

Most are self-explanatory, simply providing local storage. The mesh is the cannon itself. The magazine
is what it implies, the magazine in which the balls not currently
launched are stored. Those balls that have been launched are kept in
the active array. When a ball falls off the floor and into the nether regions, it is
moved from the active array into the magazine.
When a new ball is needed and the magazine is empty one is created. If
there is one in the magazine, it is just popped out and re-used. In
this way, we don't waste memory and performance by removing and
re-creating beach-balls. Each ball is created just once. lastT
is the time when we last updated all the balls. As you'll see, we
throttle the updates since there is no sense to updating faster than 60
fps.

The first step is to see if the delay since the last firing is long enough, i.e. longer than deltaT.
If not, we just return. If has been long enough (which is usually the case), we need to fire the cannon. To do this, we first
check if there is already one or more balls in the magazine. If so, we just pop one out of the
magazine. If the magazine is empty (which is only true
in the first few seconds of the demo) we need to allocate a brand new beach ball and add it to the scene.

Then we initialize the trajectory of the beach-ball, i.e. we "fire" the beach-ball. We want the balls to come out the mouth of the cannon of
course, but at different vertical angles and with some random
variation. The geometry of this looks like this:

This is pretty straightforward. The trajectory of the ball is detemined by the muzzle velocity and the angle of the
trajectory. Rho determines both the vertical and horizontal velocities.
Theta determines the direction of the ball. Note that deltaRho
is less than 45 degrees. Otherwise too many of the balls go more or less vertical and end up rolling too slowly
towards the edge of the floor. Finally, we push the newly fired beach ball into the active array.

Updating the Particle System

In updateBalls, we walk through the array of active balls. For each ball, we first
check if the ball is now completely transparent. If so it has fallen off the floor and disappeared. So we move it from
the active array to the magazine to be re-used.

If it is not transparent, we update the ball's velocity and position by calling the ball's update
method. If the ball's height above the floor is less than the radius, we check if the ball is still on the floor.
If it is we bounce it by inverting the vertical (y) velocity less the ball's restitution. Then we place the ball
exactly on the floor.

Alternatively, if the ball is NOT over the floor anymore, then it is not bounced but instead accelerates downward under
the force of gravity. Each time it's transparency is reduced by 2.5% until it finally disappears.