Inspiration:
The idea behind the project came to me after visiting my uncle over the weekend. I sat with him for a while and heard his stories of watching the stars in the sky between his trips around the different emirates, so in turn, I wanted to create the natural beauty of space.
Concept:
Invisible attractors act as gravitational forces, pulling particles toward them like stars or planets pulling in celestial bodies. Adding turbulence (a random force similar to cosmic wind) makes the movement less predictable, introducing a layer of chaos and randomness. The result is a balance between order and disorder, where the attractors constantly influence the movers but never settle into perfect orbits.
Sketch:
Code Structure
The code is structured into different components to keep it organized and easy to manage:
Attractors: These are invisible points on the canvas that exert a gravitational pull on the movers. They are positioned randomly at the start and never move. Their forces influence the movers’ paths.
Movers: These are the visible particles that move based on the forces applied by the attractors and the random turbulence. They leave a fading trail behind them as they move, creating beautiful, fluid patterns.
Turbulence: This force adds an element of unpredictability by introducing small random pushes to the movers, making their motion more organic.
Wrapping Around Edges: When movers reach the canvas’s edge, they reappear on the opposite side, creating the feeling of an infinite space.
Full Code:
let movers = []; let attractors = []; let turbulenceForce = 0.01; // Strength of random force (simulating cosmic wind) function setup() { createCanvas(800, 600); background(0); // Create invisible attractors that represent stars or celestial bodies for (let i = 0; i < 4; i++) { attractors.push(createVector(random(width), random(height))); // Each attractor is placed randomly on the canvas } // Creates visible movers, which represent comets or particles moving around space for (let i = 0; i < 100; i++) { movers.push(new Mover(random(width), random(height))); // Starts each mover at a random position } } function draw() { // Creates a fading background effect by drawing a transparent black rectangle over the canvas // This keeps a slight trail behind the movers, making the motion smoother and more fluid fill(0, 20); // Black rectangles with some transparency rect(0, 0, width, height); // For every mover, apply forces from each attractor and turbulence, then update and display for (let mover of movers) { for (let attractor of attractors) { mover.applyAttraction(attractor); // Apply gravitational attraction from the invisible attractors } mover.applyTurbulence(); // Add a random force to create unpredictable motion (like cosmic wind) mover.update(); // Update the mover's position based on the forces acting on it mover.display(); // Draw the mover on the screen } } // The Mover class defines the behavior and properties of each particle (mover) class Mover { constructor(x, y) { this.position = createVector(x, y); // Mover's position (starts at the given x and y) this.velocity = createVector(); // Mover's speed and direction (starts still, no velocity) this.acceleration = createVector(); // The force applied to the mover to change its motion this.maxSpeed = 3; // Limit the maximum speed to keep the movement smooth this.color = color(255, 255, 0); // Initial color is yellow (R, G, B = 255, 255, 0) } // Function to apply attraction from an invisible attractor (representing stars or planets) applyAttraction(attractor) { // Calculate the direction and distance between the mover and the attractor let force = p5.Vector.sub(attractor, this.position); let distance = constrain(force.mag(), 20, 150); // Limits the distance to avoid extreme attraction forces force.normalize(); // Normalize the force vector to get direction only, without affecting magnitude force.mult(1 / (distance * distance)); // Apply the inverse-square law (force decreases as distance increases) this.acceleration.add(force); // Add the attraction force to the mover's acceleration } // Function to add a random force (like a cosmic wind) to make the motion unpredictable applyTurbulence() { // Create a small random force in any direction let turbulence = createVector(random(-turbulenceForce, turbulenceForce), random(-turbulenceForce, turbulenceForce)); this.acceleration.add(turbulence); // Add this random force to the mover's acceleration } // Updates the mover's position based on its current velocity and acceleration update() { this.velocity.add(this.acceleration); // Add the acceleration to the velocity (changing the speed/direction) this.velocity.limit(this.maxSpeed); // Make sure the velocity doesn't exceed the maximum speed this.position.add(this.velocity); // Update the position based on the velocity this.acceleration.mult(0); // Reset acceleration after each frame so forces don't accumulate // Changes the color of the mover based on its speed (faster movers turn blue, slower ones stay yellow) let speed = this.velocity.mag(); // Calculates how fast the mover is going let r = map(speed, 0, this.maxSpeed, 255, 0); // Red decreases as speed increases let g = map(speed, 0, this.maxSpeed, 255, 0); // Green decreases as speed increases let b = map(speed, 0, this.maxSpeed, 0, 255); // Blue increases as speed increases this.color = color(r, g, b); // The color will change from yellow to blue as the speed changes // Wrapping around the edges of the screen if (this.position.x > width) this.position.x = 0; // If mover goes off the right edge, wrap to left if (this.position.x < 0) this.position.x = width; // If mover goes off the left edge, wrap to right if (this.position.y > height) this.position.y = 0; // If mover goes off the bottom, wrap to top if (this.position.y < 0) this.position.y = height; // If mover goes off the top, wrap to bottom } // Function to display (draw) the mover on the screen display() { stroke(this.color); // Set the stroke (outline) color of the mover based on its current color strokeWeight(2); // Set the thickness of the mover's stroke point(this.position.x, this.position.y); // Draw the mover as a small point on the screen } }
Key Feature in the Code
// Change the color of the mover based on its speed (faster movers turn blue, slower ones stay yellow) let speed = this.velocity.mag(); // Calculate how fast the mover is going let r = map(speed, 0, this.maxSpeed, 255, 0); // Red decreases as speed increases let g = map(speed, 0, this.maxSpeed, 255, 0); // Green decreases as speed increases let b = map(speed, 0, this.maxSpeed, 0, 255); // Blue increases as speed increases this.color = color(r, g, b); // Transition from yellow to blue based on speed
As movers speed up, they change from yellow (slow) to blue (fast), creating a clear and beautiful visual representation of their velocity. This color change adds another layer of artistic depth to the simulation, making the movers look like celestial objects glowing as they move faster.
Challenges and Solutions
One of the challenges was balancing the forces so that movers didn’t either get stuck near the attractors or fly off the canvas too quickly. Tuning the strength of both attraction and turbulence forces was the solution. I also implemented the edge wrapping mechanism to prevent movers from disappearing off-screen, which greatly improved the overall flow of the design.
The most rewarding part of the project was seeing how the movers interacted with the invisible attractors and how the random turbulence affected their paths. This gave the final design an organic, flowing feel—something I had aimed for from the beginning.
The only thing I am still unclear about accomplishing is the color change due to my colorblindness; I have zero clue if that function works. But hopefully, it looks as beautiful to you as it does to me.
Future Improvements:
- Interactivity: In the future, I would like to add interactivity by allowing users to click and place new attractors on the canvas in real time, changing the flow of the movers.
- Sound Design: Pairing the visual elements with generative ambient sounds based on the movement and speed of the movers would create an immersive experience.
Resource:
https://p5js.org/reference/#/p5/map