Week 3 – Generative Artwork

Concept

I wanted to take our in-class example of making a car class/element and use the car as a pen to scribble over the canvas. The point of this type of generative art was to create an unending and random pattern, while giving the viewer an essence of cyclicalness as the cars seemed to rush towards the right end of the canvas, and then reemerge from the other side only to repaint over their own scribbles.

Moreover, I wanted to add meaningful interactivity in the form of terrain creation, where the user can drag their pointer on the canvas to create a muddy region. This muddy region slows down the cars horizontally while maintaining their constant aggressive back-n-forth vertically. This jitter zone eventually populates with ink much faster than the rest of the canvas, and, although the mud is eventually enveloped by the scribbles, the texture of that region remains visibly distinct and dense.

Adding the telemetry HUD showing real-time average speed was a fun experiment too, giving immediate visual feedback as soon as you trap cars in your mud pits.

Code that I’m particularly proud of

 /**
   * Steers agent away from nearby cars to maintain safe personal space.
   */
  applySeparation(nearbyCars) {
    let desiredSeparation = 14;
    let steerX = 0;
    let steerY = 0;
    let neighborCount = 0;

    for (let other of nearbyCars) {
      if (other === this) continue;

      let dx = this.posX - other.posX;
      let dy = this.posY - other.posY;
      let distance = sqrt(dx * dx + dy * dy);

      // Apply repulsive force inversely proportional to distance
      if (distance > 0 && distance < desiredSeparation) {
        steerX += (dx / distance) * (desiredSeparation - distance);
        steerY += (dy / distance) * (desiredSeparation - distance);
        neighborCount++;
      }
    }

    if (neighborCount > 0) {
      this.posX += (steerX / neighborCount) * 0.04;
      this.posY += (steerY / neighborCount) * 0.04;
    }
  }
}

Why: When spawning the initial cars in a grid, I noticed that often times overlaps would happen. I started with 900 cars (30×30) initially and would use per-car seperation logic, that would reduce FPS by a lot. To reduce this calculation, I decided to implement a solution called spatial grid updating. It immidiately boosted the FPS and it future proof.

Embedded sketch

How Was This Made

This project is built around an autonomous agent model combined with an off-screen persistent canvas:

    1. Persistent Trails via createGraphics: Normally, calling background() in draw() clears every prior frame. To allow cars to act as drawing pens, I created an isolated rendering layer named trailLayer using p5’s createGraphics(). The car chassis, tires, mud, and HUD are redrawn continuously on the main canvas, while each car leaves permanent line strokes on trailLayer beneath them.
    2. Organic Movement & Drawing: Instead of moving in rigid straight lines, each car is given a small randomized horizontal step and an erratic vertical offset (random(-4, 4)) on every frame. This converts pure vehicle physics into an energetic, hand-drawn scribble texture.
    3. Interactive Mud Physics: The terrainPatches array records coordinates clicked or dragged by the user. On each update tick, each car performs a radius check against active terrain circles. When an intersection is detected, currentSpeed is cut to one-third of its maximum speed, concentrating the scribbles into dense clusters.
    4. Color Scheme: The color palette was curated based on vintage Bugatti racing liveries (French Racing Blue, Rouge Garance, Deep Navy, Atalante Yellow, and Obsidian Black Carbon) rendered against an off-white, parchment-toned canvas (#E4D5B7) to evoke technical drafting or blueprint paper.

References & Inspiration:

    • Craig Reynolds’ Boids Algorithm: The separation logic in applySeparation() is directly adapted from Reynolds’ classic steering behavior rules for autonomous flocking agents.
    • Spatial Hash Grids: The SpatialGrid bucket implementation was referenced from standard 2D broadphase collision detection patterns used in real-time game engines.
    • In-Class Vehicle Class: The starting chassis and wheel coordinate offsets were built out from our foundational class OOP exercise.

Problems Encountered & Solutions:

    • The Screen-Wrap Laser Bug: When cars reached the right edge and wrapped around to x = -10, line(prevX, prevY, this.posX, this.posY) drew a jarring, full-width horizontal stroke across the screen. I fixed this by adding a conditional gate (if (this.posX >= prevX)) so trails are strictly drawn while vehicles travel forward.
    • Input Flooding on Mouse Drag: Dragging the cursor across the canvas pushed hundreds of identical terrain coordinates into memory every second. I added an addTerrainPatch() helper with a minSpacing threshold of 8 pixels, ensuring clean, evenly spaced patches without choking the update loop.

Future Improvements:

Currently, there’s only one type of terrain (mud), I would like to introduce a few more, such as a zero friction zone (ice patch) that boosts horizontal velocity over the zone. Additionally, the canvas currently gets too full, and the user has no way of canvas clearing. I could add a small corner button that clears the trails, or a long press that slowly clears out trails from bottom up. Another experiment would be adding sounds to each car based on spatial circumstances.

Leave a Reply