Week 3 — Art

1. Sketch and Code


Code

2. Overview

For this assignment, I moved beyond a simple coordinate grid to a more swarm like idea. The project uses Object-Oriented Programming (OOP) to simulate a collection of 80 independent particles. The artwork transitions between a chaotic “dispersion” state and a focused “magnetic” state based on mouse interaction, utilizing internal memory for each object to draw fluid trails.

3. Concept

My goal was to simulate “digital ink” or bio-luminescent trails. I wanted the movement to feel viscous—as if the particles were swimming through a thick fluid. The pink-on-black aesthetic remains, but the focus shifted from “pointers” to “pathways” to emphasise the beauty of the movement itself rather than the final position.

4. Process and Methods

My process was centered on encapsulation — hiding the complex math inside the object so the main program remains clean.

    • Instead of global variables for x and y, I created a Particle class to manage individual behaviors, positions, and memory.
    • Each particle has its own history array to store previous coordinates, allowing for the rendering of custom “ribbon” shapes rather than relying on screen-wide trails.
    • I used conditional logic to switch the “force” applied to particles.
      • Wander: Driven by perlin noise for smooth, non-linear dispersion.
      • Magnetize: Driven by vector subtraction to seek the cursor when mouseIsPressed.
5. Technical Details
    • For the “brain” of each particle, I used conditional logic to toggle between two distinct mathematical forces. When the mouse is clicked, it employs Vector Subtraction (p5.Vector.sub) to calculate a direct path between the particle’s current position and the cursor. Then, using setMag(0.6), it normalizes this vector to a constant strength, making sure that the magnetic pull is smooth and predictable regardless of distance.
    • Conversely, when the mouse is released, the code samples perlin noise based on the particle’s unique (x, y) coordinates to find a value in a multi-dimensional field. This value is mapped to an angle and converted into a force via p5.Vector.fromAngle. This causes the particles to disperse in organic, flowing motions rather than random lines.
    • Both behaviors conclude by adding these forces to the acceleration vector (this.acc.add) which queues up the physical move that will be executed in the next frame.
// 1. BEHAVIOR SELECTION
if (mouseIsPressed) {
  // --- MAGNETIC MODE ---
  let mouse = createVector(mouseX, mouseY);
  // Subtract current position from mouse position to get the "toward" vector
  let attraction = p5.Vector.sub(mouse, this.pos);
  // Normalizing the pull force to 0.6 keeps the movement constant and non-erratic
  attraction.setMag(0.6);
  this.acc.add(attraction);
} else {
  // --- DISPERSE MODE ---
  // Sampling the noise field based on local coordinates for organic flow
  let n = noise(this.pos.x * 0.006, this.pos.y * 0.006, frameCount * 0.01);
  // Convert the noise value (0 to 1) into a steering angle
  let wander = p5.Vector.fromAngle(n * TWO_PI * 2);
  // Apply a weaker force for a graceful, drifting dispersion
  wander.mult(0.3);
  this.acc.add(wander);
}
    • I implemented a three-tier physics system based on acceleration, velocity, and position. Instead of moving objects by fixed pixels, I applied forces to the acceleration vector to create momentum.
// 2. PHYSICS
this.vel.add(this.acc);          // Acceleration changes Velocity
this.vel.limit(this.maxSpeed);   // Cap speed for smoothness
this.pos.add(this.vel);          // Velocity changes Position
this.vel.mult(0.97);             // Damping (Friction) for a liquid feel
this.acc.mult(0);                // Reset for next frame
    • To create the intricate trails, each particle records its movement. I used push() to add new coordinates and shift() to remove old ones, ensuring the “ribbon” has a constant length without filling up the computer’s memory.
// 3. HISTORY TRACKING (The "Ribbon" Logic)
let v = createVector(this.pos.x, this.pos.y);
this.history.push(v);            // Record position

// If the ribbon gets too long, remove the oldest point
if (this.history.length > this.maxHistory) {
  this.history.shift();          // Remove the tail end
}

this.checkEdges();
6. Reflection

This project served as a major evolution from my previous work, shifting from basic coordinate manipulation to more a sophisticated force-based physics system. My main challenge was mastering the balance between the magnet and wander behaviors; while my earlier projects used simple if statements to bounce objects off walls, this one uses vector math and perlin noise to create a viscous, liquid-like motion. I spent significant time troubleshooting a glitch I was having, where ribbon trails would stretch across the canvas during edge-wrapping, which eventually taught me to “wipe” the object’s internal memory to keep the visuals clean — that was how I came up with the history array inside the Particle class; I was able to move away from robotic, linear paths. Learning to use functions like setMag() for constant pull and drag for fluid friction allowed me to fine-tune the “feel” of the interaction, resulting in a piece that responds to the user with an organic grace that feels more like a living organism than a set of lines.

7. Resources

Leave a Reply