Concept
For this assignment, I chose to create a particle system where movers orbit multiple attractors while being affected by three competing forces: gravitational attraction, wind turbulence, and mutual repulsion. I was inspired by how celestial bodies orbit in space but also how flocking birds maintain spacing while moving together. The challenge was to balance these three forces so they create beautiful, flowing patterns rather than chaotic scattering or rigid clustering. Each mover has slightly different mass, giving them unique orbital behaviors – some get pulled strongly into tight orbits while others drift lazily at the edges. The combination of ordered attraction and chaotic turbulence creates movement that feels organic and alive, like watching a cosmic dance that’s constantly evolving but never repeating.
Sketch
Code Highlight
In my implementation, a section of my code that I am proud of is below:
//Wind/turbulence using Perlin noise
applyWind(time) {
//perlin noise to create flowing, organic wind
let noiseVal = noise(this.pos.x * 0.01, this.pos.y * 0.01, time * 0.0001);
let angle = noiseVal * TWO_PI * 2;
let wind = createVector(cos(angle), sin(angle));
wind.mult(WIND_STRENGTH);
this.applyForce(wind);
}
This creates the organic turbulence that pushes movers around. Instead of using random() which would make particles jitter randomly, I used 3D Perlin noise (x, y, and time) which gives smooth, flowing forces. By sampling the noise field at the particle’s position and the current time, nearby particles experience similar wind direction, creating swirling currents rather than chaos. The angle conversion means the noise controls which direction the wind blows, creating those beautiful flowing patterns you see in the trails. I’m also proud of this snippet that prevents particle clustering:
//repulsion from another mover
repel(other) {
let force = p5.Vector.sub(this.pos, other.pos);
let distance = force.mag();
//repel if close enough
if (distance < REPULSION_DISTANCE && distance > 0) {
let strength = (this.mass * other.mass) / (distance * distance);
force.setMag(strength);
this.applyForce(force);
}
}
This uses the same inverse-square physics as attraction, but only activates when movers get close. It’s what keeps the particles from all collapsing into a single clump and creates those satisfying spacing patterns in the orbits. It’s a small detail but makes a huge difference in creating visually interesting formations.
Challenges
Force Accumulation Bug
Early in development, I noticed that movers were only responding to one force at a time, even though I was calling applyForce() multiple times per frame. After debugging, I realized the class example’s applyForce() method was replacing acceleration instead of adding to it:
Version from class example:
applyForce(force) {
this.acc = force.div(this.mass);}
My updated version:
//accumulates forces
applyForce(force) {
let f = force.copy();
f.div(this.mass);
this.acc.add(f); //add to acceleration
}
Reflections and Future Improvements
Through this assignment, I discovered that emergent complexity comes from simple rules interacting. No single force creates the beautiful patterns – it’s the tension between attraction (pulling together), repulsion (pushing apart), and turbulence (stirring things up) that generates the endless variety. My biggest challenge was finding the right balance between these forces. Too many attractions and everything collapsed into the attractors. Too much repulsion and particles scattered. Too much wind and chaos took over. The sweet spot where all three forces balanced created the most interesting patterns. My biggest takeaway is that personality comes from variation. Giving each mover a random mass (between 0.5 and 2) meant they all respond differently to the same forces – heavy particles orbit tightly while light ones drift widely. This variation is what makes the system feel alive rather than mechanical. Below are some ideas I would implement in the future for improvement of what I have currently:
-
- Ability to drag attractors to new positions and see how the system responds in real-time.
- Make particles bounce off each other with momentum conservation.
- Slow motion or fast forward to study patterns at different speeds.
- Different colored particles with different masses or charge (attract some, repel others).
With my implementation so far, some features I believe work very well include the Perlin noise wind creating smooth organic turbulence, the speed-based coloring that immediately shows which particles are moving fast, the repulsion preventing unrealistic clustering, and most importantly, the combination of three different forces creating complex emergent behaviors from simple physical rules. The system creates genuinely unpredictable patterns while maintaining a cohesive aesthetic – each session is unique but always visually interesting.