Click Here to see ENDLESS.
DEMO
ENDLESS is a project that emphasizes the infinity of time and the ephemeral nature of existence. The circular particles represent the cyclical nature of life, constantly moving, evolving and fading, yet eventually returning to their origin.
This project involves a particle system and Object-Oriented Programming (OOP), where each circle on the canvas is treated as a particle. These particles are continuously added to and removed from the particle array to generate the birthing and fading effects.
CODE
let NUM_OF_PARTICLES = 50;
let MAX_OF_PARTICLES = 500;
let particles = [];
let currentBaseHue = 0;
let generationTargetCount = 0;
let bgMusic;
function preload() {
bgMusic = loadSound('assets/bg-music.wav');
}
function setup() {
let canvas = createCanvas(800, 500);
canvas.parent("p5-canvas-container");
background(0);
frameRate(30);
angleMode(DEGREES);
ellipseMode(CENTER);
colorMode(HSL, 100);
currentBaseHue = random(100);
bgMusic.loop();
bgMusic.setVolume(0.5);
}
function draw() {
background(0, 0, 0, 4);
translate(width / 2, height / 2);
particles.sort((a, b) => a.y - b.y);
for (let i = particles.length - 1; i >= 0; i--) {
let p = particles[i];
p.update();
p.display();
if (p.isDead()) {
particles.splice(i, 1);
}
}
if (particles.length === 0 && generationTargetCount === 0) {
currentBaseHue = random(100);
generationTargetCount = NUM_OF_PARTICLES;
}
if (generationTargetCount > 0 && particles.length < MAX_OF_PARTICLES) {
particles.push(new Particle(currentBaseHue));
generationTargetCount--;
}
}
class Particle {
constructor(baseHue) {
this.t = random(360);
this.speed = 1.5;
this.a = width / 3;
this.x = 0;
this.y = 0;
this.baseDia = random(10, 60);
this.strokeW = random(0.1, 1);
this.hue = baseHue + random(-7, 7);
if (this.hue < 0) {
this.hue += 100;
} else if (this.hue > 100) {
this.hue -= 100;
}
this.age = 0;
this.maxLifespan = random(150, 250);
this.lifeAlpha = 0.0;
this.perspectiveScale = 1.0;
this.perspectiveLight = 50;
this.perspectiveStroke = 1.0;
this.finalLightness = 0;
}
update() {
this.t += this.speed;
this.age += 1;
this.a = width / 3;
this.updatePosition();
this.updateLifecycle();
this.updatePerspective();
this.finalLightness = this.perspectiveLight * this.lifeAlpha;
}
updatePosition() {
let currentT = this.t;
this.x = this.a * sqrt(2) * cos(currentT) / (pow(sin(currentT), 2) + 1);
this.y = this.a * sqrt(2) * cos(currentT) * sin(currentT) / (pow(sin(currentT), 2) + 1);
}
updateLifecycle() {
let fadeInDuration = this.maxLifespan * 0.2;
let fadeOutStartTime = this.maxLifespan * 0.7;
if (this.age < fadeInDuration) {
this.lifeAlpha = map(this.age, 0, fadeInDuration, 0, 1);
} else if (this.age > fadeOutStartTime) {
this.lifeAlpha = map(this.age, fadeOutStartTime, this.maxLifespan, 1, 0);
} else {
this.lifeAlpha = 1.0;
}
}
updatePerspective() {
let yRange = this.a / 2;
this.perspectiveScale = map(this.y, -yRange, yRange, 0.5, 1.5);
this.perspectiveLight = map(this.y, -yRange, yRange, 50, 80);
this.perspectiveStroke = map(this.y, -yRange, yRange, this.strokeW * 0.4, this.strokeW * 1.3);
}
display() {
push();
translate(this.x, this.y);
noFill();
stroke(this.hue, 80, this.finalLightness);
strokeWeight(this.perspectiveStroke);
let base = this.baseDia * this.perspectiveScale;
circle(0, 0, base * 0.3);
circle(0, 0, base * 0.8);
circle(0, 0, base);
pop();
}
isDead() {
return this.age > this.maxLifespan;
}
}
Reflection Prompts
What is Object-Oriented Programming (OOP), a Class and an Instance of the Class?
Object-Oriented Programming is a programming methodology that we divide what we want to achieve into several components, and each component is called an "object". A Class acts as a blueprint for these objects. It defines two main components:
Attributes, which represents the object's properties.
Methods, a set of functions that define the object's behavior.
Instance is an object created through the Class, in practice we use the new function to construct an instance from the Class.
Discuss the effectiveness of OOP. When is OOP useful? In what way can it be utilized?
OOP is especially useful when we want to create multiple instances with similar properties or movements. In this situation, OOP can save lots of work by using the for loop to create multiple instances, and call methods respectively. Otherwise, we would need very complex if statements to manage different states, or worse, have to write repetitive code for every single object.
Describe the objects you have created. What properties and methods were implemented? What kind of behaviors did you create by manipulating them?
For this project, I have created circular particles as an object. As I have described above, the circular particles represent the cyclical nature of life, constantly moving, evolving and fading, yet eventually returning to their origin.
For the properties of the particle, I have included:
this.t & this.a: two properties that are used to calculate the current this.x and this.y coordinates of the particle.
this.speed: the moving speed on the set trajectory.
this.baseDia: the diameter of the circular particle.
this.strokeW: the stroke weight of the circular particle.
this.hue: the random generated color of the particle.
this.age: how many frames the particle have been generated, which decide whether the particle should fade and be removed from the array or not.
this.maxLifespan: a threshold for the particle to birth and to fade.
And the rest properties are used for either control the alpha or the scale of the particle, which serves the animation of birth and fade.
For the methods:
updatePosition(): This method is used to calculate the coordinates of the particle.
updateLifecycle(): Based on this.age and this.maxLifespan to change the alpha of the particle, gradually decrease alpha to 0 to make the particle fade out from the scene.
updatePerspective(): The trajectory is thicker at the bottom of the canvas and thinner at the top. Therefore, I will use this function to shrink the particle's size as it moves upwards.
The overall design of this project is that each generated particle follows the trajectory of an infinity symbol and has a lifecycle. As it nears the end of its lifecycle, its alpha value decreases, eventually making it invisible. Once its age exceeds its max lifespan, it is removed from the array.
Please share it if there is any challenging and confusing aspect(s) in the OOP concepts and techniques.
The OOP concepts are pretty clear, but the major challenge while implementing it is that I can't predict how many properties I will need ahead of making the constructor. For example, the updatePerspective() function was added afterwards, so the properties related to perspective also had to be added. I then need to make sure that they won't conflict with methods that are already written.
Reference
A p5js code for the infinity symbol trajectory: https://editor.p5js.org/anyue/sketches/ynUmBi1bd
The background music of ENDLESS is sampled from Lens by Frank Ocean.