Personal space

At the end of the last section, we had a nice Particle emitter cursor trail. As you drag the cursor around, you leave a trail of hundreds of moving Particles. Every one of those Particles is responding to its initial starting velocity combined with a hint of Perlin noise. Each Particle does its thing and is oblivious to what any of the Particles are doing. Until now.

We are going to implement a very basic repulsive force to each Particle. Every single Particle will push away every other Particle. We will do this by giving each Particle an acceleration vector called mAcc.

Here is how it will work. From the ParticleController, we will iterate through all the Particles. For each Particle, we check it against all other Particles. If those two Particles are close, they will push each other away more strongly than if the two Particles are on opposite sides of the app window. We add that repulsion to the respective Particles' mAcc vectors.

Once we have iterated through all the Particles, we add the acceleration to the velocity. Then we add the velocity to the position. We decay the velocity. We reset the acceleration. And we repeat.

The abridged version of what each Particle will go through looks like this:

mVel += mAcc;
mLoc += mVel;
mVel *= mDecay;
mAcc.set( 0, 0 );


We are also adding a new variable to represent the Particle's mass which is directly related to the radius. The actual relationship is a matter of personal taste. Once we make use of the mass variable, you might find you like how things behave if your Particles are really massy. I like my Particles a little more floaty.

mMass = mRadius * mRadius * 0.005f;


This formula is not based on anything other than trial and error. I tried setting the mass equal to the radius. Didn't like that. I tried mass equal to radius squared. Didn't like that. I eventually settled on taking a fraction of the radius squared.

Basic Repulsive Force

void ParticleController::repulseParticles() {
for( list<Particle>::iterator p1 = mParticles.begin(); p1 != mParticles.end(); ++p1 ) {
list<Particle>::iterator p2 = p1;
for( ++p2; p2 != mParticles.end(); ++p2 ) {
Vec2f dir = p1->mLoc - p2->mLoc;
float distSqrd = dir.lengthSquared();
if( distSqrd > 0.0f ){
dir.normalize();
float F = 1.0f/distSqrd;
p1->mAcc += dir * ( F / p1->mMass );
p2->mAcc -= dir * ( F / p2->mMass );
}
}
}
}


Lets go through this step by step. First, you set up a for-loop that uses the list iterator to go through all the Particles, one by one, in the order they are sorted in the list.

for( list<Particle>::iterator p1 = mParticles.begin(); p1 != mParticles.end(); ++p1 ){


Next, we create a second iterator that also loops through the Particles, but it starts at one Particle ahead of the first iterator's position. Put another way, if we are on Particle 15 in the first iterator, the second iterator will loop through Particles 16 and higher. Particle 16 will iterate through Particle 17 and higher, etc.

Put yet another way, imagine that we have 3 Particles. Each Particle wants to repel every other Particle. First round, p1 and p2 repel each other, and then p1 and p3 repel each other. Second round, we already handled all of p1's interactions so we move on to p2. Since p2 has already interacted with p1, all that is left is for p2 and p3 to repel each other. That is the logic for the nested iterators.

list<Particle>::iterator p2 = p1;
for( ++p2; p2 != mParticles.end(); ++p2 ) {


Now that we are inside of the second iterator, we are dealing with a single pair of Particles, p1 and p2. We know both of their positions so we can find the vector between them by subtracting p1's position from p2's position. We can then find out how far apart they are by using length().

Vec2f dir = p1->mLoc - p2->mLoc;
float dist = dir.length();


Here is where we run into our first problem. To do this repulsion force, we don't need the distance between the Particles. We need the distance squared. We could just multiply dist by dist and be done with it, but we have another option.

When finding out the length of a vector, you first find out the squared distance, then you take the square root. The code for finding the length() looks like this:

sqrt( x*x + y*y )


You should try to avoid using sqrt() when possible, especially inside of a huge nested loop. It will definitely slow things down as the square root calculation is much more processor-intensive than just adding or multiplying. The good news is there is also a lengthSquared() method we can use.

Vec2f dir = p1->mLoc - p2->mLoc;
float distSqrd = dir.lengthSquared();


Next, we make sure the distance squared is not equal to zero. One of the next steps is to normalize the direction vector and we already know that normalizing a vector with a length of zero is a bad thing.

if( distSqrd > 0.0f ){


Here is the sparkling jewel of our function. First, you go ahead and normalize the direction vector. This leaves you with a vector that has a length of one and can be thought of as an arrow pointing from p2 towards p1.

dir.normalize();


The first factor which determines how much push each Particle has on the other is the inverse of the distance squared.

float F = 1.0f / distSqrd;


Since we already know that force equals mass times acceleration (Newton's 2nd law), we can find p2's contribution to p1's total acceleration by adding the force divided by p1's mass.

p1->mAcc += ( F * dir ) / p1->mMass;


To find out p1's contribution to p2's total acceleration, you subtract force divided by p2's mass.

p2->mAcc -= ( F * dir ) / p2->mMass;


If this all seems confusing to you, worry not. It still confuses me from time to time. With a little bit of practice, this code will start to feel more familiar.

Now we can turn on our repulsion. In our App class, before we tell the ParticleController to update all the Particles, we trigger the ParticleController::repulseParticles() method. We can now run our code.

tutorial_part5_01.png


Every single Particle pushes away its neighbors which causes the Particles to spread out in a really natural manner. Here is a short video of the effect in action.