Autumn Drizzle simulates a dynamic natural environment with autumn leaves, raindrops, and wind controlled by the mouse. The leaves and the drizzle drops are falling down with gravity, while the leaves are also spinning and "blown away" by the wind. When a raindrop hits a leaf, the leaf would fall down with a quicker speed as absorbing the drop's force. This mini project demonstrates Object-Oriented-Programming and the usage of Arrays in JavaScript.
You can find my code here. There are two classes defined in the project: particle (leaf) and raindrop. Both of them are stored separately in the respective arrays with different limitations on the maximum amount.
let particles = [];
let rainDrops = [];
let NUM_OF_PARTICLES = 400;
let NUM_OF_RAIN = 50;
function setup() {
let canvas = createCanvas(800, 500);
canvas.parent("p5-canvas-container");
angleMode(DEGREES);
noStroke();
// Create all the particles in setup
for (let i = 0; i < NUM_OF_PARTICLES; i++) {
particles.push(new Leaf());
}
for (let i = 0; i < NUM_OF_RAIN; i++) {
rainDrops.push(new RainDrop());
}
}
function draw() {
console.log(particles.length);
background(20, 40, 60);
noStroke();
// iterate backwards to safely remove while looping
for (let i = particles.length - 1; i >= 0; i--) {
let l = particles[i];
let d = dist(mouseX, mouseY, l.x, l.y);
let windRadius = 35;
// if the distance of the mouse and the leaf
// is close enough
// then "blow" the leaf by mouse wind
if (d < windRadius) {
let windX = (mouseX - pmouseX) * 0.1;
let windY = (mouseY - pmouseY) * 0.1;
l.applyWind(windX, windY);
}
for (let j = 0; j < NUM_OF_RAIN; j++) {
let r = rainDrops[j];
let rainDist = dist(l.x, l.y, r.x, r.y);
if (rainDist < l.size) {
// the leaf is hit by the rain
l.applyWind(0, r.ySpeed * 0.05);
}
}
l.update();
l.display();
l.checkBounds();
// remove if marked invisible (fallen off bottom)
if (!l.isVisible) {
particles.splice(i, 1);
particles.push(new Leaf());
}
}
strokeWeight(2);
for (let r of rainDrops) {
r.update();
r.display();
}
}
class Leaf {
constructor(
startX = random(width),
startY = random(-height, 0),
size = random(10, 20),
ySpeed = random(1, 3)
) {
this.x = startX;
this.y = startY;
this.size = size;
this.ySpeed = ySpeed;
this.xSpeed = 0;
this.yBoost = 0; // wind
this.flutterSpeed = random(0.3, 0.8);
this.angle = random(360);
this.rotationSpeed = random(-2, 2);
this.color = color(
random(140, 220),
random(50, 120),
random(20, 40),
random(120, 200)
);
this.isVisible = true; // flag for removal
}
// take force from x and y
applyWind(forceX, forceY) {
this.xSpeed += forceX;
this.yBoost += forceY;
}
update() {
// this.ySpeed is gravity
// this.yBoost is wind force
this.y += this.ySpeed + this.yBoost;
// this.xSpeed is wind force
let flutter = sin(this.angle * this.flutterSpeed) * 0.5;
this.x += this.xSpeed + flutter;
// spinning
this.angle += this.rotationSpeed;
this.xSpeed *= 0.95;
this.yBoost *= 0.95;
}
display() {
if (!this.isVisible) return;
push();
translate(this.x, this.y);
rotate(this.angle);
fill(this.color);
ellipse(0, 0, this.size, this.size * 0.6);
pop();
}
checkBounds() {
// if fallen off bottom, mark for removal
if (this.y > height + this.size) {
this.isVisible = false;
return;
}
// allow continuous falling for left or right overflow
// and appear from the other side
if (this.x > width + this.size) {
this.x = -this.size;
} else if (this.x < -this.size) {
this.x = width + this.size;
}
// allow continuous falling for top overflow
if (this.y < -this.size) {
// keep falling from top
this.ySpeed = random(1, 3);
this.yBoost = 0;
}
}
}
// use lines to draw the RainDrop
class RainDrop {
constructor() {
this.x = random(width);
this.y = random(-200, -100);
this.length = random(10, 20);
this.ySpeed = random(6, 12);
this.color = color(180, 200, 255, random(100,200));
}
update() {
this.y += this.ySpeed;
this.checkBounds();
}
display() {
strokeWeight(2);
stroke(this.color);
line(this.x, this.y, this.x, this.y + this.length);
}
checkBounds() {
if (this.y > height) {
this.y = random(-200, -100); // get back to top
this.x = random(width);
}
}
}
OOP is a coding style to organize code around objects and manage the whole project with multiple objects instead of single function calls. A Class is the blueprint for creating objects that give a set of shared properties and methods that can be reused for each instance. An Instance is a specific, already-created example from one object blueprint. It is an actual item that can be used in other parts of the code with its unique data filled into the model properties.
OOP is really effective for managing complexity, scalability, and reusability, especially when we need multiple instances. Without OOP, I would have to create hundreds of separate arrays for different properties for each leaf in my project, such as one x array, one y array, one y speed array, and so on. When using OOP, all the necessary information of one instance is already encapsulated in one instance, and we need only one array to keep all instances. When I need to update or reinitialize one leaf or one raindrop, I can also directly call one object method for readability and simplicity instead of writing multiple flow control lines and statements.
I created two main objects, defined by the Particle class (Leaf) and the RainDrop class. For each leaf, it has the properties of :
x position
y position
size
y Speed
x Speed by wind
y Boost by wind
flutter speed
rotating angle
rotating speed
color
visible or not
Apart from the constructor, it has the following methods:
applyWind(forceX, forceY): it allows external forces to modify the leaf's speed by xSpeed and yBoost.
update(): it calculates the motion of one leaf
display(): it draws the leaf using push(), translate(), and rotate() to apply the spinning motion.
checkBounds(): it manages the conditions when a leaf reaches the canvas edge
The normal falling down caused by gravity is manipulated by update() and checkBounds() for the life cycle. Force offered by the mouse or raindrops uses applyWind(forceX, forceY) to make speed change available. display() is always called to draw each leaf on the screen. The effect of raindrops is applied in the draw() function instead of being implemented inside the leaf class itself.
For each raindrop, it has the properties of:
x position
y position
length
y Speed
color
And the methods include:
update()
display()
checkBounds()
The motion and changes of raindrops are easy: falling down in update(), when reaching the lower edge, turning back to the beginning. It does not really have a life cycle that allows an instance to be deleted from the array; instead, one instance is only reinitialized after moving out of the canvas with checkBounds().
In fact, there was a challenge when I was trying to implement the interaction of the two classes. Originally, I had 800 leaves and 100 raindrops, and I needed to go over every raindrop during the leaves' loop. The time complexity seems to be O(n^2), which leads to a lack of resources on the webpage, and the frame per second decreased dramatically. I have to decrease the total array length to make the animation smooth. I am curious whether there is a method to improve the interaction between two classes, considering multiple instances in an array.