Nostalgia to Code: Cozy Rain // ICM W4

Sep 30, 2024

Introduction

In this week's ICM class, we were asked to explore with creating a sketch with patterns. The prompt encouraged us to observe patterns both in our surroundings and within our thoughts and feelings. As I was thinking about this assignment, my mind wandered to the rhythmic patter of rain outside my window, evoking a deep sense of nostalgia for my time in the UK(where it rained - all the time). This was the idea i built for my assignment: "Cozy Rain."

Concept Overview

The concept for "Cozy Rain" started because I wanted to capture the essence of rainfall patterns I've experienced in two distinct environments: the UK and New York. I wanted to create a visual representation that not only mimicked the physical pattern of falling raindrops but also embodied the emotional associations I have with rain in these two places.

I envisioned warm, muted colors representing the cozy nostalgia of UK rain(maybe this is because I associated Sepia with the past?), contrasting with cooler tones symbolizing the urban drizzle of New York. At the center of this rainy tableau, I imagined a circular form representing myself - this is a half cooked idea but what is interesting was that I realized I often visualize myself and emotions as spherical, with feelings intensifying at the core and dissipating at the edges. even in my first sketch for the self portrait I created a sketch with concentric circles.

Sketches:

Before diving into code, I started with some quick sketches to visualize my concept.

In this initial sketch, I roughly outlined:

  • A canvas with a muted dark background to represent the weather when it rains

  • different coloured raindrops for how I experienced rain in each city NY & Leicester

  • A central circle representing the self (half baked idea)

  • Notes on potential colors and interactions

Foundations

Stage 1: Setting Up the Canvas and Basic Raindrop

let drop; // Variable to store a single raindrop

function setup() {
  createCanvas(600, 600); 

  // Initialize the drop using the createDrop function
  drop = createDrop(); 
}

function draw() {
  background(220); // Set the background color to light grey

  // Update and display the drop
  fall(drop); // Make the drop fall
  show(drop); // Display the drop on the screen
}

// Function to create a new raindrop with properties
function createDrop() {
  return {
    x: random(width), // Random horizontal position
    y: random(-500, -50), // Random starting vertical position
    z: random(0, 20), // Random depth value for perspective
    len: map(random(0, 20), 0, 20, 10, 20), // Length of the drop based on depth
    yspeed: map(random(0, 20), 0, 20, 1, 20) // Speed of the drop based on depth
  };
}

// Function to update the position of the raindrop
function fall(drop) {
  drop.y += drop.yspeed; // Increment the drop's y position
  if (drop.y > height) {
    // Reset the drop to a random position above the canvas when it goes off-screen
    drop.y = random(-200, -100);
  }
}

// Function to display the raindrop
function show(drop) {
  stroke(138, 43, 226); // Set the stroke color (purple)
  line(drop.x, drop.y, drop.x, drop.y + drop.len); // Draw the drop
}

Stage 2: Setting Up the Canvas with multiple raindrops

let drops = []; // Empty array to store multiple raindrops

function setup() {
  createCanvas(600, 600); 

  // Create 100 raindrops and store them in the drops array
  for (let i = 0; i < 100; i++) {
    drops.push(createDrop()); // Push a new drop into the array
  }
}

function draw() {
  background(220); // Light grey background to make the raindrops stand out

  // Loop through each raindrop in the array
  for (let drop of drops) {
    fall(drop); // Update the position of the drop
    show(drop); // Display the drop on the screen
  }
}

// Function to create a new raindrop with properties
function createDrop() {
  return {
    x: random(width), // Random x position
    y: random(-500, -50), // Random starting y position
    z: random(0, 20), // Random depth value
    len: map(random(0, 20), 0, 20, 10, 20), // Length of the drop based on depth
    yspeed: map(random(0, 20), 0, 20, 1, 20) // Speed of the drop based on depth
  };
}

// Function to update the position of the raindrop
function fall(drop) {
  drop.y += drop.yspeed; // Increment the drop's y position
  if (drop.y > height) {
    // Reset the drop to a random position above the canvas
    drop.y = random(-200, -100);
  }
}

// Function to display the raindrop
function show(drop) {
  stroke(138, 43, 226); // Set the stroke color (purple)
  line(drop.x, drop.y, drop.x, drop.y + drop.len); // Draw the drop
}

Stage 3: Implementing UK and NY Color Schemes

let drops = []; // Array to store multiple raindrops
let colors = {
  uk: ['#577455', '#8C3F4D', '#6B8E9B'], // Muted green, Burgundy, Desaturated blue-gray
  ny: ['#EE8838', '#F4F2EF', '#6B8E9B']  // Warm orange, Soft off-white, Desaturated blue-gray
};

function setup() {
  createCanvas(600, 600); // Create a 600x600 pixel canvas

  // Create 100 raindrops and store them in the drops array
  for (let i = 0; i < 100; i++) {
    drops.push(createDrop()); // Push a new drop into the array
  }
}

function draw() {
  background(220); // Light grey background to make the raindrops stand out

  // Loop through each raindrop in the array, and then for that drop apply the fall and show functions 
  for (let drop of drops) {
    fall(drop); // Update the position of the drop
    show(drop); // Display the drop on the screen
  }
}

// Function to create a new raindrop with properties
function createDrop() {
  // Determine the type of drop (either 'uk' or 'ny') based on a random value
  let type = random() > 0.5 ? 'uk' : 'ny';
  // Create a drop object with properties including a random color from the chosen scheme
  return {
    x: random(width), // Random x position
    y: random(-500, -50), // Random starting y position
    z: random(0, 20), // Random depth value for perspective
    len: map(random(0, 20), 0, 20, 10, 20), // Length of the drop based on depth
    yspeed: map(random(0, 20), 0, 20, 1, 20), // Speed of the drop based on depth
    color: random(colors[type]) // Random color from the chosen scheme
  };
}

// Function to update the position of the raindrop
function fall(drop) {
  drop.y += drop.yspeed; // Increment the drop's y position
  if (drop.y > height) {
    // Reset the drop to a random position above the canvas when it goes off-screen
    drop.y = random(-200, -100);
  }
}

// Function to display the raindrop
function show(drop) {
  stroke(drop.color); // Set the stroke color to the drop's color
  line(drop.x, drop.y, drop.x, drop.y + drop.len); // Draw the drop
}

Stage 4: Adding Gravity and Depth

let drops = []; // Array to store multiple raindrops
let colors = {
  uk: ['#577455', '#8C3F4D', '#6B8E9B'], // Muted green, Burgundy, Desaturated blue-gray
  ny: ['#EE8838', '#F4F2EF', '#6B8E9B']  // Warm orange, Soft off-white, Desaturated blue-gray
};

function setup() {
  createCanvas(600, 600); 

  // Generate multiple raindrops using the createDrop function
  for (let i = 0; i < 100; i++) {
    drops.push(createDrop());
  }
}

function draw() {
  background(220); // Light gray background

  // Loop through all drops and make them fall and show
  for (let drop of drops) {
    fall(drop); // Apply gravity and move the drop
    show(drop); // Display the drop on the screen
  }
}

// Function to create a single raindrop with depth, speed, and color
function createDrop() {
  let type = random() > 0.5 ? 'uk' : 'ny'; // Randomly pick color scheme (uk or ny)
  return {
    x: random(width),        // Random x position across the canvas
    y: random(-500, -50),    // Random starting y position above the canvas
    z: random(0, 20),        // Depth value, controls speed and size
    len: map(random(0, 20), 0, 20, 10, 20), // Drop length based on depth
    yspeed: map(random(0, 20), 0, 20, 4, 10), // Initial falling speed
    color: random(colors[type])  // Random color from selected scheme
  };
}

// Function to make the raindrop fall with gravity
function fall(drop) {
  drop.y = drop.y + drop.yspeed; // Increase the y position based on speed
  let grav = map(drop.z, 0, 20, 0, 0.2); // Add gravity based on depth
  drop.yspeed = drop.yspeed + grav; // Update the speed with gravity
  
  // If the drop moves off the bottom, reset it above the canvas
  if (drop.y > height) {
    drop.y = random(-200, -100); // Reset y position above the canvas
    drop.yspeed = map(drop.z, 0, 20, 4, 10); // Reset speed for the new drop
  }
}

// Function to display the raindrop with stroke weight based on depth
function show(drop) {
  strokeWeight(map(drop.z, 0, 20, 1, 3)); // Stroke thickness based on depth (closer = thicker)
  stroke(drop.color); // Set the stroke color of the raindrop
  line(drop.x, drop.y, drop.x, drop.y + drop.len); // Draw the raindrop as a vertical line
}

This stage added depth to the rain, with drops in the "foreground" falling faster and appearing larger. The gravity effect made the rain feel more dynamic and natural. Here is how it works:

createDrop(): Creates an individual raindrop object with properties like x, y, z (depth), length (len), speed (yspeed), and a color selected randomly from the uk or ny schemes.

  • fall(drop): This function makes the raindrop fall and applies gravity based on the drop's depth (z). When the drop reaches the bottom of the canvas, it resets to a random position above the canvas.

show(drop): This function visually displays the raindrop. It adjusts the stroke weight to make drops that are "closer" appear thicker, simulating depth.

Again I will say a lot of this math I outsourced to Claude - by prompting it to help me find a way to make depth a part of the sketch without adding a Z-dimension to my sketch and making it 3d. It suggested the use of optical illusions and by sharing my code and asking it it to implement that into my existing code base - I was able to create the depth effect.

Stage 5: Adding myself (half-baked)

let drops = [];
let colors = {
  uk: ['#577455', '#8C3F4D', '#6B8E9B'], // Muted green, Burgundy, Desaturated blue-gray
  ny: ['#EE8838', '#F4F2EF', '#6B8E9B']  // Warm orange, Soft off-white, Desaturated blue-gray
};

function setup() {
  createCanvas(600, 600);
  
  // Create 100 raindrops
  for (let i = 0; i < 100; i++) {
    drops.push(createDrop());
  }
}

function draw() {
  background(34, 34, 34, 25);  // Dark background with slight transparency to blend the scene

  // Update and display each raindrop
  for (let drop of drops) {
    fall(drop);
    show(drop);
  }

  // Draw the circular self-portrait in the center of the canvas
  fill(244, 242, 239, 45);  // Soft off-white with transparency
  stroke(244, 242, 239, 100);  // Slightly stronger outline for the circle
  ellipse(width / 2, height / 2, 200, 200);  // Draw the circle at the center of the canvas
}

// Create a single raindrop with depth, color, and speed
function createDrop() {
  let drop = {};
  drop.x = random(width);  // Random horizontal position
  drop.y = random(-500, -50);  // Start above the canvas
  drop.z = random(0, 20);  // Simulating depth
  drop.len = map(drop.z, 0, 20, 10, 20);  // Length based on depth
  drop.yspeed = map(drop.z, 0, 20, 1, 20);  // Speed based on depth
  drop.type = random() > 0.5 ? 'uk' : 'ny';  // Choose between 'uk' or 'ny' color scheme
  drop.color = color(random(colors[drop.type]));  // Assign random color from the scheme
  return drop;
}

// Update the drop's position and apply gravity based on depth
function fall(drop) {
  let grav = map(drop.z, 0, 20, 0, 0.2);  // Gravity increases with depth
  drop.yspeed += grav;  // Speed increases due to gravity
  drop.y += drop.yspeed;  // Move the drop down

  // Reset if it goes off the canvas
  if (drop.y > height) {
    drop.y = random(-200, -100);  // Reset above canvas
    drop.yspeed = map(drop.z, 0, 20, 4, 10);  // Reset speed based on depth
  }
}

// Display the raindrop on the canvas
function show(drop) {
  strokeWeight(map(drop.z, 0, 20, 1, 3));  // Thicker lines for 'closer' drops
  stroke(drop.color);  // Set the color of the drop
  line(drop.x, drop.y, drop.x, drop.y + drop.len);  // Draw the raindrop
}

Final Pseudo Code

Here's a high-level overview of how the final sketch works:

Initialize canvas and raindrop array

Function setup():
    Create canvas
    Populate raindrop array with initial properties

Function draw():
    Clear background with slight transparency
    For each raindrop in array:
        Update raindrop position
        Draw raindrop
    Draw semi-transparent self circle in center

Function createRaindrop():
    Return object with random position, speed, size, and UK/NY type

Function updateRaindrop(raindrop):
    Update vertical position
    Apply gravity effect
    If raindrop is off-screen:
        Reset to top with new random properties

Function drawRaindrop(raindrop):
    Set stroke weight based on raindrop size
    Set color based on UK/NY type
    Draw line representing raindrop

The concept was to create a dynamic, ever-changing rainy scene that represents the blending of memories and experiences from two different places. The semi-transparent background creates a trailing effect, symbolizing how memories linger and blur over time. The central circle represents the self, observing and being influenced by these experiences, its transparency allowing the "memories" (raindrops) to pass through and affect it(half-baked, I was trying to not anthropomorphise so for the lack of a better idea I settled for a circle).

Final Code & Sketch:

Here's the complete, final sketch & code for the "Cozy Rain" sketch:

let drops = []; // Array to hold all raindrops
let colors = {
  uk: ["#577455", "#8C3F4D", "#6B8E9B"], // Muted green, Burgundy, Desaturated blue-gray
  ny: ["#EE8838", "#F4F2EF", "#6B8E9B"], // Warm orange, Soft off-white, Desaturated blue-gray
};

function setup() {
  createCanvas(600, 600);
  
  // Create 200 raindrops and store them in the drops array
  for (let i = 0; i < 200; i++) {
    drops.push(createDrop()); // Use a function instead of class constructor
  }
}

function draw() {
  background(34, 34, 34, 25); // Dark background with slight transparency

  // Update and display each raindrop
  for (let drop of drops) {
    fall(drop); // Update position based on gravity
    show(drop); // Display the raindrop on the canvas
  }

  // Draw circular self-portrait in the center of the canvas
  fill(244, 242, 239, 45);  // Soft off-white for the circle with transparency
  stroke(244, 242, 239, 100); // Soft off-white for the circle outline
  ellipse(width / 2, height / 2, 200, 200); // Draw the circle at the center
}

// Function to create a single raindrop with all necessary properties
function createDrop() {
  let drop = {}; // Create an empty object to represent a drop
  drop.x = random(width); // Random x-position on the canvas
  drop.y = random(-500, -50); // Random starting y-position above the canvas
  drop.z = random(0, 20); // Depth value (used for speed, length, etc.)
  drop.len = map(drop.z, 0, 20, 10, 20); // Length of the raindrop based on depth
  drop.yspeed = map(drop.z, 0, 20, 1, 20); // Speed of the raindrop based on depth
  drop.type = random() > 0.5 ? "uk" : "ny"; // Randomly choose between UK or NY color schemes
  drop.color = color(random(colors[drop.type])); // Set the color from the chosen scheme
  return drop; // Return the drop object
}

// Function to simulate the fall of the raindrop
function fall(drop) {
  drop.y += drop.yspeed; // Update the y-position by adding the current speed
  let grav = map(drop.z, 0, 20, 0, 0.2); // Gravity increases with depth
  drop.yspeed += grav; // Accelerate the drop based on gravity

  // If the drop reaches the bottom of the canvas, reset its position to the top
  if (drop.y > height) {
    drop.y = random(-200, -100); // Reset y-position above the canvas
    drop.yspeed = map(drop.z, 0, 20, 4, 10); // Reset speed based on depth
  }
}

// Function to display the raindrop on the canvas
function show(drop) {
  strokeWeight(map(drop.z, 0, 20, 1, 3)); // Adjust the thickness of the raindrop based on depth
  stroke(drop.color); // Set the stroke color to the raindrop's assigned color
  line(drop.x, drop.y, drop.x, drop.y + drop.len); // Draw the raindrop as a vertical line
}

To Be Continued…

As I was wrapping up this project, Nik introduced the concept of classes in programming. This opened up an new possibility for "Cozy Rain". I realized that by refactoring the code to use a Raindrop class instead of separate functions, I could potentially make the code more efficient and organized.

A Raindrop class could encapsulate all raindrop behaviors in one neat package. Pictured below is Nik teaching me about classes not the actual refactored code.

// code that he used to explain classes to me probably
//  incoherent nonsense if you were not there 
class Rectangle {
  constructor(width, height) {
    this.x = width * height;
    this.y = 0;
    this.secretIWontUse = "a";
    console.log(x);
  }

  scaleRectangle(amount) {
    this.x = amount * this.x;
    this.y = amount * this.y;
  }

}

function constructor(width, height) {
  let x = width * height;
  console.log(x);
}

let a = {dad: 3, mom: this.dad + 1};
a.dad = 3;

let myRectangle = new Rectangle(2, 4);
myRectangle.secretIWontUse;
myRectangle.scaleRectangle(4);
console.log(myRectangle)

console.log(myRectangle);
// {}
References
  1. Nik's Blog
  2. Claude by Anthropic
  3. Ellen for being such a great teacher

©2019-2025 SURYA NARREDDI.

©2019-2025 SURYA NARREDDI.