Week 3 Assignment: Generative Artwork using OOP: Screensavers and Physics

Concept and Inspiration

For this assignment, I drew inspiration from the the era of late 1990s and early 2000s. At that time, Windows 98 and Windows XP had some unique screensaveers which I still remember and which evokes a sense of nostalgia in me. These screensavers (link) often featured geometric shapes and repetitive, hypnotic patterns, such as oscillating lines, spirals, and pendulum-like movements.

Fig: Some of the Windows Screensavers

For this, I used robotic arms and “Inverse Kinematics” as my inspiration. From my point of view, I saw this as the perfect oppertunity to blend computational techniques into this visual style. The robot arms represent the pendulums of the past, but with a unique twist. Instead of being merely simple lines, these arms demonstrate the principles of Object-Oriented Programming (OOP), where each pendulum is treated as an independent object, following specific behaviors such as oscillation and length growth. Moreover, inverse kinematics allows each arm to dynamically respond to changing positions, mimicking the flexibility and precision of robotic motion. The result is a digital artwork that blends the nostalgia of retro visuals with the sophistication of modern computational design.

Code Explanation

“Arm” class

This class features a constructor which initializes each pendulum’s amplitude, angle, angle velocity, and length growth. It also has the “update()” function to update the pendulum’s length (amplitude) and oscillation (angle). Using the “display()” function, it calculates the current position of the pendulum and draws a line from the previous position.

// Defining the Arm class
class Arm {
  constructor(amplitude, angle, angleVel, lengthGrowth) {
    this.amplitude = amplitude;      // Initial length of the arm
    this.angle = angle;              // Starting angle
    this.angleVel = angleVel;        // Angular velocity
    this.lengthGrowth = lengthGrowth; // How fast the arm grows in length
  }

  // Method to update the arm's properties (growth and oscillation)
  update() {
    this.amplitude += this.lengthGrowth; // Increase length over time
    this.angle += this.angleVel;         // Update angle for oscillation
  }

  // Method to display the arm
  display(prevX, prevY) {
    let x = sin(this.angle) * this.amplitude;  // Calculate x position
    let y = cos(this.angle) * this.amplitude;  // Calculate y position
    line(prevX, prevY, x, y); // Draw line from previous position to current
    return { x, y };          // Return current x, y for the next arm
  }
}
“setup()” function

The “setup()” function initializes the canvas size and prepares the environment. It disables fills for the shapes and sets default stroke properties. It randomizes the number of pendulum arms (num_arms) and the other arm’s properties, with each arm receiving random values for amplitude, angular velocity, and growth rate. The arms are stored in an array, each represented as an object with properties for oscillation and growth.

function setup() {
  createCanvas(800, 800);
  noFill();
  stroke(255); // Initial stroke color
  strokeWeight(1); // Initial stroke weight

  // Randomize the number of arms between 2 and 10
  num_arms = int(random(2, 10));

  // Initialize the Arm objects with random values
  for (let i = 0; i < num_arms; i++) {
    let amplitude = random(70, 150);
    let angleVel = random(0.01, 0.05);
    let lengthGrowth = random(0.1, 0.5);

    // Create new Arm and push to the arms array
    arms.push(new Arm(amplitude, 0, angleVel, lengthGrowth));
  }

  // Initially set the center to an off-canvas position
  centerX = -1000;
  centerY = -1000;
}
“draw()” function

This function creates a semi-transparent background overlay to maintain the fading trails without fully erasing the canvas. The “rect()” draws a slightly transparent rectangle over the entire canvas, producing the trailing effect. The “translate()” function shifts the origin of the canvas to the clicked point (centerX, centerY), which acts as the center of the pendulum system. A loop iterates over each arm, calculating its new position based on its current angle and amplitude using “Inverse Kinematics.” The arms are drawn as lines connecting from one pendulum to the next, simulating the robot arm movement whos length increases with time.

// Draw Function
function draw() {
  if (hasStarted) {
    fill(0, 10); // Semi-transparent background to maintain trails
    rect(0, 0, width, height);

    // Set the center of the arms to the clicked position
    translate(centerX, centerY);

    let prevX = 0;
    let prevY = 0;

    // Loop through each arm and update and display them
    for (let i = 0; i < arms.length; i++) {
      let arm = arms[i];

      // Update arm properties
      arm.update();

      // Display the arm and update the previous position for the next one
      let newPos = arm.display(prevX, prevY);
      prevX = newPos.x;
      prevY = newPos.y;
    }
  }
}
“mousePressedI()” function

The “mousePressed()” function updates the center of the pendulum system to wherever the user clicks on the canvas (mouseX, mouseY). This triggers the pendulum animation by setting “hasStarted” to true. Upon clicking, it randomizes the stroke color, weight, and number of arms, creating variety and making each user interaction unique. It also reinitializes the pendulum arms with new random values, ensuring a different pattern is generated with every click.

// This function will run when the mouse is pressed
function mousePressed() {
  // Set the new center of the arm system to the clicked location
  centerX = mouseX;
  centerY = mouseY;
  hasStarted = true;

  // Randomize background and stroke properties
  stroke(random(0, 255), random(0, 255), random(0, 255));
  strokeWeight(random(1, 10));

  // Randomize the number of arms between 2 and 6
  num_Arms = int(random(2, 6));

  // Reinitialize the arms array
  arms = [];
  for (let i = 0; i < num_Arms; i++) {
    let amplitude = random(80, 150);
    let angleVel = random(0.01, 0.05);
    let lengthGrowth = random(0.1, 0.5);

    // Create new Arm objects with random values
    arms.push(new Arm(amplitude, 0, angleVel, lengthGrowth));
  }
}

Sketch

Further Improvements which can be made

Smoother Transitions: Currently, the background might change too quickly when clicking. Adding a smooth transition effect between pendulum sets can make the animation more fluid and visually appealing.

Scaling upto 3D space: I had originally though of introducing a responsive 3D canvas using “WEBGL” mode in p5.js, but that was making the idea of user interaction a little complex, so I had to drop that for now.

Damping: Currently, the simulation runs the pendulums until another person clicks it. Introducing damping can be another way to introduce realism to it.

Collission: Various arms when coming in contact with each other change their path/length can be another aspect which can be looked to.

Reflection

This project modernizes the retro screensaver aesthetic using modern programming techniques such as OOP and inverse kinematics, combined with user interactivity. The code is modular, making it easy to add new features or improvements, and the possibilities for further customization and expansion are vast.

Leave a Reply