Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
99 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

Particle System, a Brief Design

Let's decide on the few essentials of a particle system. Should we copulate the rendering to the particle system? We could, but they would actually 'kill off' the extensibility, as the particles have to be rendered “That way”. We could provide a Renderer object to the system, and pass the particles and the number of active particles to the Renderer's Render function. However, doing so might involve more function calls, and kill off the chance to optimize the rendering (For example, you might have a bunch of particle system which you could actually batch together, and use a shared vertex buffer and texture)

Next would be the data storage of the particles. There are again, two approaches. The first one would be dynamic memory allocation, where you allocate upon demand. You can either use a link list, or a vector. However, being dynamic, the cost of each allocation calls are high, but this approach scales, limited only by the memory resource of the running machine. But you wouldn't want to be able to scale to this extend, because the CPU would not be able to perform the processing of so many particles, and yet be able to render them at real-time. Thus you would have set a limit cap. Again, this can be determined before run-time. So to speed things up, we simply set aside the memory needed for the maximum number of particles before run-time, on the local heap.

Next we would need to have a way to add particles to the system, as well as process them. Thus we introduce two functions, Emit, which emits N number of particles at a given position, and Update, which process all particles in the system, killing off those which have lived to their max life.

Additional helper functions would be ParticlesCount which gets the current number of particles in used, MaxParticles which is the limit of the system, Clear which clears all particles used, as well as GetParticles, which returns the particles. With GetParticles, you can then perform additional processing (if you wished, though I see no reason why), as well as render the particles.

Handling the Initialization and Processing in an Extensible way

Now we come to the crucial part. How can we design the particle system, so that it can have initialization code as well as processing code plugged in before run-time, yet doesn't require us to recode the particle system every time? The answer, apparently, lies in Policy Pattern. We can narrow down this into two Policy, InitializePolicy, as well as ActionPolicy.

The InitializePolicy interface merely requires the definition of one interface, a function that takes in a particle object and initializes it. In this case, we chose the operator(). The same is required for the ActionPolicy, but they require an additional PrepareAction function. PrepareAction is a one time call during each Update of the particle system, as opposed to the operator(), which gets called for each particle.

Does that mean you need to write a Policy for the different particle system you might have? And function call for each particle seems as expensive as aggregation/composition? Well, the answer will be revealed later on how we can work around it. But for now, here's the listing of the particle system class.

template <size_t size, class InitializePolicy, class ActionPolicy, class ParticleType>
class ParticleGroup {
public : 
InitializePolicy d_InitializePolicy;
  ActionPolicy d_ActionPolicy;

  explicit ParticleGroup() throw():d_nCurrentCount(0) {}
  ~ParticleGroup() throw() {}

  inline void Clear() throw() {
    d_nCurrentCount = 0;
  }
  inline const ParticleType* GetParticles() const throw() {
    if ( ParticlesCount() == 0 ) {
      return 0;
    }
    return d_arrParticles;
  }
  inline const size_t MaxParticles() const throw() {
    return size;
  }
  inline const size_t ParticlesCount() const throw() {
    return d_nCurrentCount;
  }
  void Emit(const size_t& i_nAmount, const Point3D& i_ptPosition) {
    size_t nAmount = i_nAmount;
    //  exceed limit?
    if ( ( ParticlesCount() + nAmount ) > MaxParticles() ) {
      nAmount = MaxParticles() - ParticlesCount();
    }
    if ( nAmount > 0 ) {
      //  create the particles
      size_t nCnt = d_nCurrentCount;
      d_nCurrentCount += nAmount;
      for(; nCnt < d_nCurrentCount; ++nCnt) {
        d_arrParticles[nCnt].d_ptPos = i_ptPosition;
        d_InitializePolicy(d_arrParticles[nCnt]);
      }
    }
}
  void Update() throw() {
    d_ActionPolicy.PrepareAction();
    //  kill off all dead particles
    for(size_t nCnt = 0; nCnt < d_nCurrentCount; ) {
      d_ActionPolicy(d_arrParticles[nCnt]);
      if ( d_arrParticles[nCnt].d_nLife <= 0 ) {
        //  dead, move last particle to this particle
        d_arrParticles[nCnt] = d_arrParticles[d_nCurrentCount - 1];
        //  decrease particle count.
        --d_nCurrentCount;
      } else {
        //  move to next particle
        ++nCnt;
      }
    }
  }
private : 
  ParticleType d_arrParticles[size];
  size_t d_nCurrentCount;
};  //  end of class ParticleGroup




Defining the Initialization Policy and Action Policy

Contents
  Introduction
  Particle System, a Brief Design
  Defining the Initialization Policy and Action Policy
  Sample Usage of a Particle System

  Printable version
  Discuss this article