let particles = [];
let maxParticlesNormal = 100; // Max particles in normal flow mode
let maxParticlesRush = 500; // Max particles in “rush” mode
let currentMaxParticles = maxParticlesNormal;
let rushMode = false; // State flag: true for “rush”, false for “normal flow”
function setup() {
// Set canvas size, mimicking the original image’s aspect ratio
createCanvas(800, 450);
// Ensure consistent pixel rendering across different displays
pixelDensity(1);
// Initialize some particles for the start of the sketch
for (let i = 0; i < maxParticlesNormal; i++) {
// Particles initially spawn randomly but will adjust to flow direction
particles.push(new Particle(random(width), random(height)));
}
}
function draw() {
// --- 1. Background: Simulating the concrete underpass with a gradient ---
// Darker color for the top (ceiling/shadows)
let c1 = color(30, 30, 30);
// Lighter color for the bottom (ground/more light)
let c2 = color(60, 60, 60);
// Draw a linear gradient from top to bottom
for (let y = 0; y < height; y++) {
let inter = map(y, 0, height, 0, 1);
let c = lerpColor(c1, c2, inter);
stroke(c);
line(0, y, width, y);
}
// --- 2. Manage and draw particles (representing the "rush" or urban flow) ---
currentMaxParticles = rushMode ? maxParticlesRush : maxParticlesNormal;
// Add new particles if the current count is below the mode's maximum
if (particles.length < currentMaxParticles) {
// Add more particles per frame in rush mode to quickly fill the screen
for (let i = 0; i < (rushMode ? 5 : 1); i++) {
let newParticleX, newParticleY;
if (rushMode) {
// In rush mode, particles can spawn randomly across the canvas to give a bursting feel
newParticleX = random(width);
newParticleY = random(height);
} else {
// In normal mode, particles mostly spawn from the right edge, flowing left
newParticleX = width + random(50); // Spawn slightly off-screen to the right
newParticleY = random(random(0, height * 0.8), height); // Spawn lower down for a 'road' feel
}
particles.push(new Particle(newParticleX, newParticleY));
}
}
// Update and display all active particles
for (let i = particles.length - 1; i >= 0; i–) {
let p = particles[i];
p.update();
p.display();
// Remove particles if they are “dead” (lifespan exhausted or off-screen)
if (p.isDead()) {
particles.splice(i, 1);
}
}
// — 3. Draw “CITY RUSH 3” text (prominent, as in the original image) —
textAlign(CENTER, BOTTOM); // Align text to center horizontally, bottom vertically
// Use a bold, strong font to emulate the original. ‘Arial Black’ is a common system font.
textFont(‘Arial Black’);
textSize(95); // Set a fixed text size for prominence
// Create a subtle pulsing/flashing effect on text color when in rush mode
let textColor = rushMode
? color(255, map(sin(frameCount * 0.1), -1, 1, 0, 150), 0) // Flashing red
: color(255, 0, 0); // Solid bright red
fill(textColor);
stroke(0); // Black border for better contrast and pop
strokeWeight(4);
text(“CITY RUSH 3”, width / 2, height – 10); // Position the text at the bottom center
}
// — Particle Class Definition —
class Particle {
constructor(x, y) {
this.pos = createVector(x, y);
// Initial velocity: generally moving left, with some vertical variation
this.vel = createVector(random(-2, -0.5), random(-1, 1));
this.acc = createVector(0, 0); // Particles start with no acceleration
this.lifespan = random(150, 255); // Random initial alpha value (for fading)
this.initialLifespan = this.lifespan; // Store initial lifespan to reset
this.size = random(3, 7); // Size of the square particle
this.baseSpeed = random(0.5, 2); // Base speed for normal mode
this.color = color(255, 255, 255); // Default white color
}
// Method to apply external forces to the particle
applyForce(force) {
this.acc.add(force);
}
update() {
// General flow force: Particles gently drift to the left (like traffic passing)
if (!rushMode) {
let flowForce = createVector(-0.05, 0); // Gentle push to the left
this.applyForce(flowForce);
}
if (rushMode) {
// In “rush” mode, particles are attracted to the mouse, creating a dynamic vortex-like motion
let mouseAttraction = p5.Vector.sub(createVector(mouseX, mouseY), this.pos);
mouseAttraction.normalize(); // Get direction
mouseAttraction.mult(0.2); // Stronger attraction force
this.applyForce(mouseAttraction);
this.lifespan -= 5; // Particles fade out faster in rush mode
this.color = color(255, 100, 100); // Reddish glow in rush mode
this.size = random(4, 9); // Slightly larger in rush mode
this.vel.limit(this.baseSpeed * 4); // Much faster speed limit in rush mode
} else {
// In normal mode, particles fade slower and remain white
this.lifespan -= 1;
this.color = color(255, 255, 255);
this.size = random(2, 6);
this.vel.limit(this.baseSpeed);
}
this.vel.add(this.acc); // Apply acceleration to velocity
this.pos.add(this.vel); // Apply velocity to position
this.acc.mult(0); // Reset acceleration for the next frame
}
display() {
noStroke(); // No border for particles
fill(this.color, this.lifespan); // Set particle color with current transparency
rect(this.pos.x, this.pos.y, this.size, this.size); // Draw a square particle
}
isDead() {
// A particle is “dead” if its lifespan runs out (fully faded)
// or if it moves significantly off-screen, to be recycled.
return this.lifespan < 0 ||
this.pos.x < -this.size * 2 || this.pos.x > width + this.size * 2 ||
this.pos.y < -this.size * 2 || this.pos.y > height + this.size * 2;
}
}
// — Interactive Element: mouseClicked to toggle “rush mode” —
function mouseClicked() {
rushMode = !rushMode; // Toggle the state of rush mode
}