Day 3 - Art 1

Exactly 42 lines of code

Fluid Particle Shapes

This generative art piece features a dynamic system of particles that transition between various states. The particles move in a dispersing pattern or arrange themselves into geometric shapes, such as circles and squares. The transitions between these states are smooth, with particles gradually shifting toward new positions, creating a fluid and evolving visual experience. The number of particles is adjusted to fit perfectly into the selected grid, and their motion is influenced by random velocities or interpolated target positions.

    // Global variables
    let p = []; // Array to store particle objects
    let s = "particles"; // Current state or shape of the system
    let t = 0; // Interpolation factor for smooth transitions
    let n = 500; // Number of particles
    let w = 400, h = 400; // Canvas dimensions
    let d = 0.05; // Speed multiplier for dispersing motion
    let nextShape = "circle"; // The next shape to transition to
    let canvas; // Canvas element
    
    function setup() {
      // Create the canvas and attach it to a parent HTML element
      canvas = createCanvas(w, h);
      canvas.parent('canvas-container');
    
      // Adjust `n` to the nearest perfect square for clean grid layouts
      n = floor(sqrt(n)) ** 2;
    
      // Initialize particles with random positions and velocities
      for (let i = 0; i < n; i++) {
        p.push({
          x: random(w), // Initial x-coordinate
          y: random(h), // Initial y-coordinate
          vx: random(-1, 1), // Random x-velocity
          vy: random(-1, 1), // Random y-velocity
        });
      }
    
      // Set the initial target positions for the particles
      setTargetPositions();
    }
    
    function draw() {
      // Set the background color to a dark gray
      background(30);
    
      // Increment the interpolation factor for smooth transitions
      t += 1 / 60;
    
      // Reset the transition and switch states when `t` reaches 1
      if (t >= 1) {
        t = 0; // Reset the interpolation factor
        if (s === "particles") {
          // Transition from "particles" to the next shape
          s = nextShape;
          nextShape = (s === "circle") ? "square" : "circle";
        } else {
          // Transition back to "particles" (dispersing)
          s = "particles";
        }
        setTargetPositions(); // Update target positions for the new state
      }
    
      // Update and draw each particle
      p.forEach(e => {
        if (s === "dispersing") {
          // Apply random velocities for the "dispersing" state
          e.x += e.vx * d;
          e.y += e.vy * d;
        } else {
          // Interpolate particle positions towards their target
          e.x = lerp(e.x, e.tx, t);
          e.y = lerp(e.y, e.ty, t);
        }
    
        // Draw the particle as a small white ellipse
        fill(255);
        noStroke();
        ellipse(e.x, e.y, 5);
      });
    }
    
    function setTargetPositions() {
      // Update target positions based on the current state `s`
      if (s === "square") {
        // Arrange particles in a square grid
        let gridSize = sqrt(n); // Number of particles per row/column
        let a = min(w, h) / 2; // Size of the square
        let b = (w - a) / 2; // Horizontal offset to center the square
        let c = (h - a) / 2; // Vertical offset to center the square
    
        p.forEach((e, i) => {
          let r = i % gridSize; // Column index
          let o = floor(i / gridSize); // Row index
          let g = a / gridSize; // Grid cell size
          e.tx = b + r * g; // Target x-coordinate
          e.ty = c + o * g; // Target y-coordinate
        });
      } else if (s === "circle") {
        // Arrange particles in a circular pattern
        let a = min(w, h) / 4; // Radius of the circle
        let b = w / 2; // x-coordinate of the circle center
        let c = h / 2; // y-coordinate of the circle center
        let g = []; // Array to store valid positions on the circle
    
        // Generate points within a bounding square and filter for circle
        for (let x = -a; x <= a; x += 20) {
          for (let y = -a; y <= a; y += 20) {
            if (dist(x, y, 0, 0) <= a) {
              g.push({ x: b + x, y: c + y });
            }
          }
        }
    
        // Assign target positions to particles
        p.forEach((e, i) => {
          e.tx = g[i % g.length].x;
          e.ty = g[i % g.length].y;
        });
      } else {
        // Assign random target positions for the "particles" state
        p.forEach(e => {
          e.tx = random(w);
          e.ty = random(h);
        });
      }
    }