Tutorial 21: Particle Systems

A Simple Particle Class

Here's a very simple particle class. The most important thing about it is that it represents a "point object" that has a position, a direction of motion and an acceleration (possibly as the result of some forces acting on the particle), and that these are updated in the update() function.

    class Particle {
      PVector location;
      PVector velocity;
      PVector acceleration;
     
      Particle(PVector _location) {
        location = _location.get(); // make a copy of given location using get()
        velocity = new PVector(random(-2.5, 2.5), random(-5, 0));
        acceleration = new PVector(0, 0.1);
      }
     
      void update() {
        velocity.add(acceleration);
        location.add(velocity);
      }
    }

    Here's a sketch that uses the Particle class. You will probably have to restart it to see the particle system in action:

    • Show Sketch
    /** @peep sketchcode */
    int NUM_PARTICLES = 100;
    Particle[] particles;
     
    void setup() {
      size(400, 400);
      PVector centre = new PVector(width/2, height/2, 0);
      particles = new Particle[NUM_PARTICLES];  
      for (int i = 0; i < particles.length; i++) {
        particles[i] = new Particle(centre);
      }
    }
     
    void update() {
      for (int i = 0; i < particles.length; i++) {
        particles[i].update();
      }
    }
     
    void draw() {
      update();
      background(255);
      for (int i = 0; i < particles.length; i++) {
        PVector l = particles[i].location;
        ellipse(l.x, l.y, 20, 20);
        PVector v = particles[i].velocity;
        line(l.x, l.y, l.x + v.x*10, l.y + v.y*10);
      }
    }
     
    class Particle {
      PVector location;
      PVector velocity;
      PVector acceleration;
     
      Particle(PVector _location) {
        location = _location.get(); // make a copy of given location using get()
        velocity = new PVector(random(-2.5, 2.5), random(-5, 0));
        acceleration = new PVector(0, 0.1);
      }
     
      void update() {
        velocity.add(acceleration);
        location.add(velocity);
      }
    }

    The sketch creates an array of particles and initialises each of them to the centre of the sketch. Every time draw() is called, the function update() is called before any drawing is done to ensure that the simulation (the particles) are updated. The drawing code at the moment just provides some useful information about the position and direction of each particle.

    A Particle System Class

    We can simplify the sketch a bit by creating a class to manage a collection of particles. Here's the implementation of a particle system class that manages an array of particles and calls update() on all of the particles whenever the particle system's update() function is called.

      class ParticleSystem {
        PVector location;
        Particle[] particles;
       
        ParticleSystem(PVector _location, int _num_particles) {
          location = _location.get();
          particles = new Particle[_num_particles];
          for (int i = 0; i < particles.length; i++) {
            particles[i] = new Particle(location);
          }
        }
       
        void update() {
          for (int i = 0; i < particles.length; i++) {
            particles[i].update();
          }
        }
      }

      This sketch shows how to use the ParticleSystem class. It looks (and behaves) just like the previous version.

      • Show Sketch
      /** @peep sketchcode */
       
      int NUM_PARTICLES = 100;
      ParticleSystem particle_system;
       
      void setup() {
        size(400, 400);
        PVector centre = new PVector(width/2, height/2, 0);
        particle_system = new ParticleSystem(centre, NUM_PARTICLES);
      }
       
      void update() {
        particle_system.update();
      }
       
      void draw() {
        update();
        background(255);
        for (int i = 0; i < particle_system.particles.length; i++) {
          PVector l = particle_system.particles[i].location;
          ellipse(l.x, l.y, 20, 20);
          PVector v = particle_system.particles[i].velocity;
          line(l.x, l.y, l.x + v.x*10, l.y + v.y*10);
        }
      }
       
      class ParticleSystem {
        PVector location;
        Particle[] particles;
       
        ParticleSystem(PVector _location, int _num_particles) {
          location = _location.get();
          particles = new Particle[_num_particles];
          for (int i = 0; i < particles.length; i++) {
            particles[i] = new Particle(location);
          }
        }
       
        void update() {
          for (int i = 0; i < particles.length; i++) {
            particles[i].update();
          }
        }
      }
       
      class Particle {
        PVector location;
        PVector velocity;
        PVector acceleration;
       
        Particle(PVector _location) {
          location = _location.get(); // make a copy of given location using get()
          velocity = new PVector(random(-2.5, 2.5), random(-5, 0));
          acceleration = new PVector(0, 0.1);
        }
       
        void update() {
          velocity.add(acceleration);
          location.add(velocity);
        }
      }

      Staying Alive!

      One of the problems with the implementation of the particle systems so far is that the particles will never die, even though we only get to see them for a very short period of time. One solution to this problem is to give each particle a "time to live", which acts as a countdown to the particle's death. To detect when a particle has run out of time, we add a dead() function to the Particle class that returns true if the particle is dead. The ParticleSystem class can then remove any dead particles from it's array of particles and create a new one at the particle system's centre.

      • Show Sketch
      /** @peep sketchcode */
      int NUM_PARTICLES = 100;
      ParticleSystem particle_system;
       
      void setup() {
        size(400, 400);
        PVector centre = new PVector(width/2, height/2, 0);
        particle_system = new ParticleSystem(centre, NUM_PARTICLES);
      }
       
      void update() {
        particle_system.update();
      }
       
      void draw() {
        update();
        background(255);
        for (int i = 0; i < particle_system.particles.length; i++) {
          PVector l = particle_system.particles[i].location;
          ellipse(l.x, l.y, 20, 20);
          PVector v = particle_system.particles[i].velocity;
          line(l.x, l.y, l.x + v.x*10, l.y + v.y*10);
        }
      }
       
      class Particle {
        PVector location;
        PVector velocity;
        PVector acceleration;
        float time_to_live;
       
        Particle(PVector _location) {
          location = _location.get(); // make a copy of given location using get()
          velocity = new PVector(random(-2.5, 2.5), random(-5, 0));
          acceleration = new PVector(0, 0.1);
          time_to_live = random(100, 200);
        }
       
        void update() {
          velocity.add(acceleration);
          location.add(velocity);
          time_to_live -= 1;
        }
       
        boolean dead() {
          return (time_to_live <= 0);
        }
      }
       
      class ParticleSystem {
        PVector location;
        Particle[] particles;
       
        ParticleSystem(PVector _location, int _num_particles) {
          location = _location.get();
          particles = new Particle[_num_particles];
          for (int i = 0; i < particles.length; i++) {
            particles[i] = new Particle(location);
          }
        }
       
        void update() {
          for (int i = 0; i < particles.length; i++) {
            particles[i].update();
            if (particles[i].dead()) {
              particles[i] = new Particle(location);
            }
          }
        }
      }

      Dressing Up

      So far, the particles has been drawn using a graphic that is informative but not very exciting. We can start making the particle system look more like something that we're trying to simulate, e.g., smoke, by using images to render each particle. Here's an example, the particle system (the simulation) is exactly the same as before, only the rendering of the particles in the sketch's draw() function has changed, together with the loading of the image.

      • Show Sketch
      /* @pjs preload="/uploads/56/particle.png"; */
      /** @peep sketchcode */
       
      int NUM_PARTICLES = 100;
      ParticleSystem particle_system;
      PImage img;
       
      void setup() {
        size(400, 400);
        PVector centre = new PVector(width/2, height/2, 0);
        particle_system = new ParticleSystem(centre, NUM_PARTICLES);
        img = loadImage("/uploads/56/particle.png");
      }
       
      void update() {
        particle_system.update();
      }
       
      void draw() {
        update();
        background(100);
        imageMode(CENTER);
        for (int i = 0; i < particle_system.particles.length; i++) {
          PVector l = particle_system.particles[i].location;
          image(img, l.x, l.y);
        }
      }
       
      class Particle {
        PVector location;
        PVector velocity;
        PVector acceleration;
        float time_to_live;
       
        Particle(PVector _location) {
          location = _location.get(); // make a copy of given location using get()
          velocity = new PVector(random(-2.5, 2.5), random(-5, 0));
          acceleration = new PVector(0, 0.1);
          time_to_live = random(100, 200);
        }
       
        void update() {
          velocity.add(acceleration);
          location.add(velocity);
          time_to_live -= 1;
        }
       
        boolean dead() {
          return (time_to_live <= 0);
        }
      }
       
      class ParticleSystem {
        PVector location;
        Particle[] particles;
       
        ParticleSystem(PVector _location, int _num_particles) {
          location = _location.get();
          particles = new Particle[_num_particles];
          for (int i = 0; i < particles.length; i++) {
            particles[i] = new Particle(location);
          }
        }
       
        void update() {
          for (int i = 0; i < particles.length; i++) {
            particles[i].update();
            if (particles[i].dead()) {
              particles[i] = new Particle(location);
            }
          }
        }
      }

      Comments

      Nobody has said anything yet.