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;
function setup() {
createCanvas(600, 600);
drop = createDrop();
}
function draw() {
background(220);
fall(drop);
show(drop);
}
function createDrop() {
return {
x: random(width),
y: random(-500, -50),
z: random(0, 20),
len: map(random(0, 20), 0, 20, 10, 20),
yspeed: map(random(0, 20), 0, 20, 1, 20)
};
}
function fall(drop) {
drop.y += drop.yspeed;
if (drop.y > height) {
drop.y = random(-200, -100);
}
}
function show(drop) {
stroke(138, 43, 226);
line(drop.x, drop.y, drop.x, drop.y + drop.len);
}
Stage 2: Setting Up the Canvas with multiple raindrops
let drops = [];
function setup() {
createCanvas(600, 600);
for (let i = 0; i < 100; i++) {
drops.push(createDrop());
}
}
function draw() {
background(220);
for (let drop of drops) {
fall(drop);
show(drop);
}
}
function createDrop() {
return {
x: random(width),
y: random(-500, -50),
z: random(0, 20),
len: map(random(0, 20), 0, 20, 10, 20),
yspeed: map(random(0, 20), 0, 20, 1, 20)
};
}
function fall(drop) {
drop.y += drop.yspeed;
if (drop.y > height) {
drop.y = random(-200, -100);
}
}
function show(drop) {
stroke(138, 43, 226);
line(drop.x, drop.y, drop.x, drop.y + drop.len);
}
Stage 3: Implementing UK and NY Color Schemes
let drops = [];
let colors = {
uk: ['#577455', '#8C3F4D', '#6B8E9B'],
ny: ['#EE8838', '#F4F2EF', '#6B8E9B']
};
function setup() {
createCanvas(600, 600);
for (let i = 0; i < 100; i++) {
drops.push(createDrop());
}
}
function draw() {
background(220);
for (let drop of drops) {
fall(drop);
show(drop);
}
}
function createDrop() {
let type = random() > 0.5 ? 'uk' : 'ny';
return {
x: random(width),
y: random(-500, -50),
z: random(0, 20),
len: map(random(0, 20), 0, 20, 10, 20),
yspeed: map(random(0, 20), 0, 20, 1, 20),
color: random(colors[type])
};
}
function fall(drop) {
drop.y += drop.yspeed;
if (drop.y > height) {
drop.y = random(-200, -100);
}
}
function show(drop) {
stroke(drop.color);
line(drop.x, drop.y, drop.x, drop.y + drop.len);
}
Stage 4: Adding Gravity and Depth
let drops = [];
let colors = {
uk: ['#577455', '#8C3F4D', '#6B8E9B'],
ny: ['#EE8838', '#F4F2EF', '#6B8E9B']
};
function setup() {
createCanvas(600, 600);
for (let i = 0; i < 100; i++) {
drops.push(createDrop());
}
}
function draw() {
background(220);
for (let drop of drops) {
fall(drop);
show(drop);
}
}
function createDrop() {
let type = random() > 0.5 ? 'uk' : 'ny';
return {
x: random(width),
y: random(-500, -50),
z: random(0, 20),
len: map(random(0, 20), 0, 20, 10, 20),
yspeed: map(random(0, 20), 0, 20, 4, 10),
color: random(colors[type])
};
}
function fall(drop) {
drop.y = drop.y + drop.yspeed;
let grav = map(drop.z, 0, 20, 0, 0.2);
drop.yspeed = drop.yspeed + grav;
if (drop.y > height) {
drop.y = random(-200, -100);
drop.yspeed = map(drop.z, 0, 20, 4, 10);
}
}
function show(drop) {
strokeWeight(map(drop.z, 0, 20, 1, 3));
stroke(drop.color);
line(drop.x, drop.y, drop.x, drop.y + drop.len);
}
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'],
ny: ['#EE8838', '#F4F2EF', '#6B8E9B']
};
function setup() {
createCanvas(600, 600);
for (let i = 0; i < 100; i++) {
drops.push(createDrop());
}
}
function draw() {
background(34, 34, 34, 25);
for (let drop of drops) {
fall(drop);
show(drop);
}
fill(244, 242, 239, 45);
stroke(244, 242, 239, 100);
ellipse(width / 2, height / 2, 200, 200);
}
function createDrop() {
let drop = {};
drop.x = random(width);
drop.y = random(-500, -50);
drop.z = random(0, 20);
drop.len = map(drop.z, 0, 20, 10, 20);
drop.yspeed = map(drop.z, 0, 20, 1, 20);
drop.type = random() > 0.5 ? 'uk' : 'ny';
drop.color = color(random(colors[drop.type]));
return drop;
}
function fall(drop) {
let grav = map(drop.z, 0, 20, 0, 0.2);
drop.yspeed += grav;
drop.y += drop.yspeed;
if (drop.y > height) {
drop.y = random(-200, -100);
drop.yspeed = map(drop.z, 0, 20, 4, 10);
}
}
function show(drop) {
strokeWeight(map(drop.z, 0, 20, 1, 3));
stroke(drop.color);
line(drop.x, drop.y, drop.x, drop.y + drop.len);
}
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 = [];
let colors = {
uk: ["#577455", "#8C3F4D", "#6B8E9B"],
ny: ["#EE8838", "#F4F2EF", "#6B8E9B"],
};
function setup() {
createCanvas(600, 600);
for (let i = 0; i < 200; i++) {
drops.push(createDrop());
}
}
function draw() {
background(34, 34, 34, 25);
for (let drop of drops) {
fall(drop);
show(drop);
}
fill(244, 242, 239, 45);
stroke(244, 242, 239, 100);
ellipse(width / 2, height / 2, 200, 200);
}
function createDrop() {
let drop = {};
drop.x = random(width);
drop.y = random(-500, -50);
drop.z = random(0, 20);
drop.len = map(drop.z, 0, 20, 10, 20);
drop.yspeed = map(drop.z, 0, 20, 1, 20);
drop.type = random() > 0.5 ? "uk" : "ny";
drop.color = color(random(colors[drop.type]));
return drop;
}
function fall(drop) {
drop.y += drop.yspeed;
let grav = map(drop.z, 0, 20, 0, 0.2);
drop.yspeed += grav;
if (drop.y > height) {
drop.y = random(-200, -100);
drop.yspeed = map(drop.z, 0, 20, 4, 10);
}
}
function show(drop) {
strokeWeight(map(drop.z, 0, 20, 1, 3));
stroke(drop.color);
line(drop.x, drop.y, drop.x, drop.y + drop.len);
}
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.
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