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
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
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:
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.
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.
Lets go through this step by step. First, you set up a for-loop that uses the
iterator to go through all the
Particles, one by one, in the order they are sorted in the list.
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
Particle 17 and higher, etc.
Put yet another way, imagine that we have 3
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
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
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 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:
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.
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.
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.
The first factor which determines how much push each
Particle has on the other is the inverse of the distance squared.
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.
To find out p1's contribution to p2's total acceleration, you subtract force divided by p2's mass.
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.
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.