Coding a Gravitational Particle System from scratch.
Particle systems are kind of wonderful because there’s so often a surprising amount of complexity and beauty that arises from these very simple rule sets. They went from being just two separate words in my vocabulary to a pretty central part of my life; In 2015 I was selected for the Curitiba International Biennial’s University Circuit. In my application I tried to describe my work with this analogy:
The process for extracting creations such as this from the sketch involves long periods of observation and free-association before the randomly-generated imagery, searching for meaning in chaos like someone who watches clouds go by.
And it’s also been the topic for a few of the workshops I’ve given, which I find works well precisely because of they way in which the simplicity delivers a big payoff.
And so that’s the plan for this tutorial. We’re going to start from the very basics but move quite quickly from there, if you’re an absolute beginner take your time in between each step of the tutorial to build a solid comprehension of each element of the code. Remember, the Processing reference is your best friend here, and I’d be ashamed to tell you how much time of my life could’ve been better spent had I gotten acquainted with it sooner.
The basic idea for this kind of particle system occurred to me very naturally. One of the first demos I was shown when I started learning processing was a simple bouncing ball sketch. It’s a good demo because it starts getting you used to managing state with variables as well as giving you a straightforward look at how animation works: Through small incremental change.
Our next step from here is to begin smartening up our code a bit. We’ll begin using Processing’s PVector class which will simplify working with the coordinates, and we’ll clean up our expressions with the multiply-assign and add-assign operators.
And now we’ve got a solid basis for what we’ll be calling our particle. To go from the single particle to a particle *system* we’ll have to replicate this logic multiple times. I’ll spare you the more painful steps of my journey here, (there was a lot of “Pvector pos1, pos2, pos3, pos4…” -type stuff going on) and skip straight to arrays.
At this point we have a decision to make: We could create arrays for all the different variables that make up the particles. This is the Object of Arrays approach. The alternative is to encapsulate the Particle’s variables into a class and create an Array of Objects:
We’ll be sticking with the more traditional form within Object-Oriented Programming, the Array of Objects approach, so we’ll finally be writing our Particle class:
And that’s it! The particle system is up and running. Now it’s a matter of giving the particles more interesting behaviors than just bouncing. We’ll start with inelastic collisions, the kind where they stick together instead of bouncing off one another. If you want to see some elastic ones, I recommend checking out the fisica library!
You can paste this inside your draw() function, at the end:
A little aside regarding those nested for() loops:
If we ran two full loops inside each other we’d end up colliding each pair of particles twice, not to mention it would try to collide particles with themselves. This can be visualized in the following way:
And next we’ll implement gravity! A few things are going to change about the structure of program: We’re going to swap the array for an ArrayList, which is a dynamic set of objects. This is going to make it more feasible for us to add and remove objects. I was already “removing” objects with the inelastic collisions by flipping them from “alive” to “dead”, but this ends up being a more limited way to go about it.
I also switched the wall collisions with a system that “warps” the particles between the top and bottom edges, and between the right and left as well.
This has a few collateral effects throughout the code, which I’ll cover as we get to them.
So here is the method which implements gravity:
Probably not as complex as you thought, huh!? All we’re doing is making a new vector to represent the acceleration of gravity, which we then add to the particle’s acceleration at the end. The strength of this vector — or magnitude — is gravity according to Newton’s equation, which you might remember from high school ( or, if you’re not there yet, you have that to look forward to! ), but we cut out the particle’s own mass, because, again, this is the acceleration, not the force. We then rotate it in the right direction (that’s what the atan2() function is doing: Finding the angle of ‘Particle p’ from the perspective of the local particle), add it, and that’s it!
You’ll notice that this exact method is not being used in the final program, rather it’s the gravitate_in_torus() method. Well, (you probably guessed it) this is because we’re in this strange doughnut-shaped universe now, and that means the force of gravity has to warp along the same paths the particles do. So the method is quite a bit more complex, but you’ll see that, fundamentally, it still works the same.
And besides that, other changes are only cosmetic / for fun. Particles have a color value, and these mix when they collide. You can press B to flip through 3 different modes: no tracks (what we’ve had so far), fading tracks, and permanent tracks. And finally, you can click particles to pop them! ( “testing” this feature was about 80% of the time it took to write this article. Really, try it. )
So this is it! There’s additional code and math on the GitHub repository, including the setups I created to make this nice solar system.
If you’re wondering where to go from here, I recommend you check out the awesome Flocking demo by Daniel Shiffman (On the processing IDE, Files > Examples > Topics > Simulate > Flocking). You’ll notice that at the heart of that system, as well as the one we explored in this article, is simple vector addition. Here’s an idea: Mix the flockers with these gravitational particles and simulate some spaceships flying through a chaotic asteroid field!
So if you’ve gotten this far, I’m really glad, and I do hope it was informative and that you had fun!