Week 3 — Reading Response

Reading the first chapter of Chris Crawford’s The Art of Interactive Design really made me stop and rethink how I’ve been using the word “interactive.” Honestly, I realized I’ve been throwing that term around for years to describe anything with a screen and a few buttons. Crawford’s metaphor of a conversation — a tight, messy loop of listening, thinking, and speaking—sets a much higher bar than I ever considered. It hit me that most of my “interactions” with technology are actually just reactions. Like his example of dodging a falling branch, I’m often just responding to a trigger rather than engaging in a real exchange. This definitely challenged my assumption that user agency automatically equals interactivity.

However, as much as I appreciate the clarity of his model, I couldn’t help but feel that Crawford is being incredibly restrictive — almost to the point of being a gatekeeper. By insisting that a system must “think” (process data in a complex way) to be interactive, he’s basically dismissive of a huge chunk of digital art and media that I love. I thought about atmospheric games or simple creative tools where the “thinking” is minimal, but the emotional impact is huge. Does a digital instrument not count as interactive just because it doesn’t deliberate before making a sound? This rigid, almost elitist definition feels like it prioritizes computer science logic over the actual human experience of a medium. It makes me wonder if he’s so focused on the “cycle” that he misses the beauty of simple, high-quality reaction.

One passage that really stuck with me was his critique of the “gauchely techie” replacement of human terms with “input, process, and output.” It’s a bit ironic because, while he wants to keep the human element (the “conversation”), his requirements for what qualifies as “thinking” feel very mechanical. This leaves me with a lot of questions: Has my own definition of interactivity been too lazy, or is Crawford’s just too narrow for the modern world? This reading didn’t just give me a new framework; it actually made me more defensive of the “simpler” digital experiences that he seems to look down on, while also making me want to strive for more depth in the things I build myself.

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

Week 2 — Reading Response

In reflecting on Casey Reas’ discussion of chance operations, I found my fundamental assumptions about artistic intent deeply challenged. I’ve always associated “intent” with manual, top-down control, but Reas’ argument for “jitter” or “noise” as a tool to keep a system from becoming “homogeneous” offers a much more interesting path. I am particularly struck by the idea of “homeostasis” within a system — where a work stays dynamic and shifting while maintaining a consistent texture. This shifts my perspective of the computer from an “obedient tool” to a creative collaborator. By utilizing randomness as a “jumping-off point,” I want to see if I can bypass my own aesthetic biases and “logical nonsense,” similar to how Reas and Tal Rosner used dice-rolls to spark compositions for their Chronograph project.

However, Reas’ mention of Rosalind Krauss’s critique of the “grid” leaves me with a lingering, uncomfortable question: if a work is generated through chance, can it still hold onto the “pure feeling” or “humane quality” that artists like Mondrian or Malevich aimed for? If I follow John Cage’s lead and use chance to “remove myself from the activity,” I worry that the work might lose its connection to social discourse and humanity. Yet, I am increasingly drawn to Gerhard Richter’s provocation that chance is “better” than the artist because it can “destroy what I’ve worked out wrong.” This suggests that the “human” element might actually reside in my willingness to let a system disrupt my own ego.

All these connections reinforced my goal to find an “optimum balance” between total randomness and complete control. Moving forward, I want to experiment with defining “minimal structures” or constraints that allow for “surprising visual qualities,” testing whether a system-based approach can actually feel more organic than one born of purely determined geometry.

Week 2 — Art

1.   Sketch and Code

Code

2.   Overview

For this assignment, I created an interactive artwork featuring a grid of neon pink “needles” that respond dynamically to user input. The sketch utilizes vector math, coordinate transformations, and mapped interactive feedback.

3.   Concept

The goal of this work was to explore the concept of a sort of “magnetic field” using geometric shapes. I wanted to create something that felt alive under my curser. The choice of a vibrant pink palette against a deep background was inspired by modern neon-noir visuals as I am somewhat interested in cyberpunk styles, and also my favourite color being pink.

4.   Process and Methods

My process focused on moving from a rigid grid to a fluid, reactive environment. I broke the project down into three main layers:

    • The grid: I used a nested for() loop to populate the canvas. By using a spacing variable, I ensured that the elements were perfectly aligned and easy to adjust.
    • Trigonometric alignment: I used trigonometry to calculate the relationship between each grid point and the mouse to make it interactive. I used atan2() for this to determine the specific angle needed for each shape to face the mouse.
let angle = atan2(mouseY - y, mouseX - x);
    • Visual mapping: I used the dist() function to measure how close each point is to the cursor, then used the map() function to link that distance to the stroke weight, length, and color intensity of the lines.
// Calculate how far each point is from the cursor
let d = dist(mouseX, mouseY, x, y);

// Use the distance (d) to drive the visuals
// Closer to mouse = brighter color and thicker lines
let pinkIntensity = map(d, 0, 400, 225, 50);
let weight = map(d, 0, 400, 5, 1);
let lineLen = map(d, 0, 400, 35, 10);

// Set styles
stroke(255, pinkIntensity, 220); // RGB: Pink variations
5.  Technical Details
    • I implemented push() and pop() within the loops, which allowed me to use translate() to move the origin to each grid point and rotate(). If not for this, every line would rotate around the top-left corner (0,0) of the screen rather than its own center.
    • To achieve the motion blur effect, I set the transparency to be 30 in in the draw() loop, making it so that the previous frames don’t disappear immediately, leaving a trail behind the moving lines.
    • Because the lines are drawn from a negative to positive value relative to the translated center, the rotation occurs perfectly around the midpoint of the line.
line(-lineLen, 0, lineLen, 0);
    • The color is updated dynamically where, as the mouse moves closer, the green channel in RGB increases, shifting the color from a deep purple-pink to a bright, glowing, neon pink.
let pinkIntensity = map(d, 0, 400, 225, 50);
6.  Reflection

This assignment developed my understanding of the transformation matrix (push, pop, translate, rotate). My biggest challenge was getting the rotation to feel natural; initially, the lines were spinning wildly, which was how I found the map() function, which helped me map the distance to the angle. Tiny adjustments to the mapping ranges completely changed the mood of my work.

7.  Resources

Week 1 — Self Portrait

1.   Sketch and Code


Code

2.   Overview

For our first assignment, I developed a sketch that is a non-literal self-portrait. The project features a shark – my favourite animal – swimming through a deep-sea environment. The sketch incorporates procedural animation, gradient rendering, and state-based movement logic.

3.   Concept

My goal of this project was to communicate identity through something other than a human face. I chose a shark because it is my favourite animal. In fact, I own a vast collection of shark themed items in real life, so it felt like the most authentic representation of my personality.

4.   Process and Methods

My process began with translating shark anatomy into geometric shapes:

    • I broke the shark down into a main body (ellipse), a tail (triangles), and facial structure (lines and arcs).
function drawSharkSprite() {
  noStroke();
  fill(160); 
  
  // BODY
  ellipse(85, 60, 150, 50); 
  
  // TAIL
  let tailX = 145;   // Local variable
  triangle(tailX, 45, 185, 60, tailX, 75);  // Connection fin
  triangle(195, 95, 145, 45, 185, 60);      // Bottom tail lobe
  triangle(200, 30, 145, 75, 185, 60);      // Top tail lobe
  
  // FINS
  triangle(90, 15, 65, 40, 95, 40);
  
  // GILLS
  stroke(80); 
  strokeWeight(1);
  noFill();
  for(let i = 0; i < 3; i++) {
    let gx = 65 + (i * 5); // Spacing the gills
    arc(gx, 60, 10, 25, -HALF_PI, HALF_PI);
  }

  // --- HEAD DETAILS ---
  noStroke();
  // Teeth
  fill(255);
  for (let tx = 25; tx < 55; tx += 6) {
    let slantY = map(tx, 25, 55, 72, 65); 
    triangle(tx, slantY, tx + 6, slantY, tx + 3, slantY + 7);
  }
  
  // Slanted Eye
  stroke(0); 
  strokeWeight(3);
  line(25, 58, 35, 52); 
  
  // Slanted Mouth Line
  stroke(166, 58, 55); 
  strokeWeight(2);
  line(22, 72, 58, 65); 
}
    • I studied some p5.js functions to avoid static movement of the shark. Instead of the shark simply sliding across the screen, I used the sin() function to give it a bobbing effect.
// Apply bobbing motion using sin() to the Y translation
translate(shark.x, shark.y + sin(shark.yOff) * 10);
scale(2.0); 
drawSharkSprite();
5.   Technical Details
    • To create depth, I wrote a drawOcean() function that uses a for loop to iterate through the canvas height, and using the lerpColor() function transitions from a darker blue to a lighter blue.
    • For the bobbing motion of the shark, I had to make a sort of mathematical function to make it look like the shark is floating in the water. The y-axis position of the shark is updated in the updateShark() function, which makes it so that the values of the y-offset (animation timer) are altered accordingly to be used in the sine function.
function updateShark() {
  shark.x += shark.vx;
  shark.y += shark.vy;
  shark.yOff += 0.05;    // Increment animation timer

  // Boundary checks
  if (shark.x < 30 || shark.x > 220) shark.vx *= -1;
  if (shark.y < 40 || shark.y > 220) shark.vy *= -1;
}
    • To keep the shark within the visible “ocean” and prevent it from swimming off canvas, I implemented a kind of “bounce” motion with a collision system. Since the shark’s movement is controlled by a y and x velocity, its direction can be reversed by simply flipping the sign of the values if the shark passes a certain x or y value.
    • Rather than drawing each tooth and gill individually, I used loops to ensure that, if the shark’s position changes, the features remain aligned..
    • I used translate() and scale() within the draw() loop to allow the shark to move as a single unit without having to update the (x,y) coordinates for every single shape in the sprite.
6.   Reflection

This project was a significant learning curve in managing coordinates. Initially, my shark would fall apart when it moved because the fins weren’t “attached” to the body’s x and y variables. Learning to use translate() solved this by creating a new local coordinate system for the shark. Also, I had a lot of trouble with the “bounce” mechanism of the shark before properly understanding how the coordinates worked. I am proud of my final result, but I hope that in the future I will be able to complete the coding process quicker now that I’ve better grasped some of the built-in functions and coordinate system.

7.   Resources