Blog of roxlu, co-founder of Apollo Media. Contact info[shift+2]apollomedia.nl.

Bouncing particle effect

A snippet which shows how to create a particle system which keep bouncing around a certain point. You can control the forces easily by settings the repel_force and attract_force.

Normally when you create a basic, simple particle system using plain Euler integration you can use something like the snippet below to make sure the velocity gets less over time (aka fake drag).

particle.velocity *= 0.99f;

Though, when you use this it's hard to make your particles bounce forever. To fix this, you remove the fake drag but simply limit the velocity to a certain value, see max_speed in the code.

In short the integration step is:

// ATTRACT FORCES
  float max_speed = 4;
  float max_speed_sq = max_speed * max_speed;
  float speed_sq; 
  float k = 1.0f;
 
  for(size_t i = 0; i < drops.size(); ++i) {
    WaterDrop& d = drops[i];
 
    {
      dir = position - d.position;
      dist = length(dir);
 
      if(dist < 0.01) {
        dist = 0.01;
      }
 
      if(dist > radius) {  /* when the particle is outiside the radius, it's attracted a lot more to the center */
        k = 16.0;
      }
 
      dir /= dist;
      f = k * attract_force * (dist/radius) * dir;
      d.forces += f;
    }
 
    d.forces *= d.inv_mass * dt;
    d.velocity += d.forces * dt;
    d.position += d.velocity;
    d.forces = 0;
 
    // we do not add a fake drag force, but we limit the speed, this will make the 
    // particles bounce forever.
    speed_sq = dot(d.velocity, d.velocity);
    if(speed_sq > max_speed_sq) {
      d.velocity = normalized(d.velocity);
      d.velocity *= max_speed;
    }
  }

We also add a repel force so the particle do not get to close and more importantly to make the particles jump a little bit. I needed this effect for a visualisation I'm working on.

for(size_t i = 0; i < drops.size(); ++i) {
 
    WaterDrop& a = drops[i];
 
    for(size_t j = i + i; j < drops.size(); ++j) {
 
      WaterDrop& b = drops[j];
      dir = b.position - a.position;
      dist_sq = dot(dir, dir);
 
      if(dist_sq > neighbor_dist_sq) {
        continue;
      }
 
      if(dist_sq < 0.01) {
        continue;
      }
 
      dir = normalized(dir);
      f = repel_force *  (1.0 - (1.0 / dist_sq)) * dir;
      a.forces -= f;
      b.forces += f;
    }
  }

See a video of this effect