Week 3 – Cosmic Drift

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:

  1. 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.
  2. 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

Leave a Reply

Your email address will not be published. Required fields are marked *