Sunday, December 2, 2012

New Approach to Particle Systems

As a combination web and game developer, the HTML5 canvas is something that really interests me. I had dabbled a little in programming for the canvas with Javascript, but it wasn't until I read a fantastic article about common optimizations for the canvas that I really felt inspired to write a small framework for HTML5 games. The engine has some cool features, including a tile-based collisions/physics, animation sprites, input handling, and timing. Future functionality includes sound, resource management, and possibly a AJAX server/client interaction wrapper. This is all implemented using a message based architecture. The engine is a WIP, and the feature I'd like to talk about today is the particle system. I set out to get away from the classic model of particle systems and try something new.

The Classic Model 

Generally, a particle system consists of three parts:

Particle Collection Collection
This contains a list of emitters to be drawn or updated. Also may have any adding/removing emitter logic. Sometimes this component is non existant, like in a highly object oriented architecture, where each object would control its own emitter. Even so, if an object had multiple emitters, it would have to control a list of them like this.

Particle Emitter
This spawns and controls a list of particles.  The emitter has a specified position and time to live. Because it is in charge of spawning particles, it must store the initial state of particles. The emitter must also have the constraints on particle speed, direction, and generation rate.

Particle
This is the final, smallest part of the particle system. It renders itself and also has a time to live. Updating the particle means doing any physics calculations on its position.

The Problems 

The problems with this system is that there is a lot of information passing from component to component that is largely the same. Particles themselves have a lot of data associated with them: position, velocity, acceleration, fade boolean, fade rate, gravity boolean, lifespan, image/animation. The emitter must generate all of this from: range of angle to spawn direction, jitter, and most of the elements listed above. What this usually results in is large constructor and function calls with about 10 parameters each. There are solutions to the classic model, such as using a convention over configuration approach or storing particle information in script files. These are perfectly valid solutions, and I've used the CoC solution in other projects.

My Solution

What I did was eliminate the emitter component. The particles are stored directly in a list, contain their own initial state, constraints, and data. In addition, they have a maxGeneration number and a unique ID. Every time a particle is copied, the new particle's generation is one more than the particle that spawned it, and its ID is the same as the particle that spawned it. If its generation is maxGeneration, it does not copy itself. Essentially, to start a new emitter, you just add a particle configuration object to the list. The particle collection does the rest using a queue and set. Here is some pseudo code:
for each particle in list:
    add (particle.id,particle.initial_params) to set
    update particle
    draw particle
    
for each tuple in set:
    if(tuple.params.generation < tuple.params.maxGeneration)
        make new particle(tuple.id,tuple.params,tuple.params.id+1)
    
A particle object is basically a Javascript object that specifies the behavior of the particle. It relies on resonable defaults, so it can be as big or small as you like: 
var particle1 = { 
  initialState : {x:134,y:575,v:.2,a:0}, 
  ttl : 25, 
  maxGen : 10, 
  image : "particle_red_img", 
  gravity : 0, 
  copy_per_frame : 15, 
  fade:1, 
}


Conclusion 

I think in a Javascript environment, my scheme eliminates a lot of redundant code, which is good for reducing loading times and keeping with JS coding convention. For a language like C with no built in support for sets and variable sized arrays, it is probably best to stick with the classic model. A negative to my new system is that there is some added logic in copying particles and using the set. Like anything else, it all comes down to what makes you a more efficient programmer and what is the best fit for your project.

I would love to hear other solutions or comments about how you handle particle systems.