To practice sin, cos, noise:
The particles are connected by springs to form a grid-like structure. When the mouse is pressed, the particles are attracted and grow larger, creating an elastic effect that pulls the clouds.
Through the lerpColor function, the color of the particles changes from dark to light according to the radius, creating a visual feedback of cloudy days and lightning.
When the mouse is released, lightning is generated, simulating the appearance of lightning in the clouds. The lightning is composed of random particles and disappears after a short time.⚡️⚡️⚡️
Code
let cols, rows;
let particles = [];
let springs = [];
let lightnings = [];
let k = 0.2;
let spacing, baseR, maxR;
let wasPressed = false;
let neighbors = [[1, 0],[1, 1],[0, 1],[-1, 0],[-1, -1],[0, -1],[1, -1],[-1, 1]];
function setup() {
createCanvas(600, 400);
if (width < height) {
spacing = width * 0.8;
cols = floor(width / spacing) + 1;
rows = floor(height / spacing) + 2;
} else {
spacing = height * 0.19;
cols = floor(width / spacing) + 2;
rows = floor(height / spacing) + 1;
}
baseR = spacing * 0.6;
maxR = spacing * 0.5;
for (let j = 0; j < rows; j++) {
for (let i = 0; i < cols; i++) {
particles.push(new Particle(i * spacing, j * spacing, i, j));
//fix? the boundary
if (i === 0 || j === 0 || i === cols - 1 || j === rows - 1) {
let index = i + j * cols;
particles[index].locked = true;
}
if (i != 0) {
let a = particles[i + j * cols];
let b = particles[i - 1 + j * cols];
springs.push(new Spring(k, spacing, a, b));
if (j != 0) {
let c = particles[i + j * cols];
let d = particles[i + (j - 1) * cols];
springs.push(new Spring(k, spacing, c, d));
}
}
}
}
}
function draw() {
background(100, 100);
for (let sp of springs) {
sp.update();
}
for (let p of particles) {
if (!p.locked) {
p.show();
p.update();
p.applyAttRep();
}
}
// lightening update
for (let i = lightnings.length - 1; i >= 0; i--) {
lightnings[i].show();
lightnings[i].update();
if (lightnings[i].isDead()) {
lightnings.splice(i, 1);
}
}
if (mouseIsPressed) {
let active;
for (let p of particles) {
let distance = dist(p.pos.x, p.pos.y, mouseX, mouseY);
if (distance < p.radius) {
active = p;
p.active = true;
}
}
if (active) {
active.vel.set(0, 0);
active.targetR = maxR;
}
wasPressed = true;
} else {
if (wasPressed) {
for (let i = 0; i < 4; i++) {
lightnings.push(new SimpleLightning(mouseX, mouseY));
}
wasPressed = false;
}
for (let p of particles) {
p.active = false;
p.targetR = baseR;
}
}
}
class SimpleLightning {
constructor(x, y) {
this.x = x + random(-30, 30);
this.y = y + random(-30, 30);
this.size = random(20, 35);
this.lifetime = random(7,35);
this.rotation = random(TWO_PI);
this.points = this.generatePoints(); //?!?!?!?
}
generatePoints() {
let points = [];
points.push(createVector(-this.size/2, -this.size/2));
points.push(createVector(0, -this.size/3));
points.push(createVector(-this.size/3, 0));
points.push(createVector(0, this.size/3));
points.push(createVector(this.size/2, this.size/2));
return points;
}
update() {
this.lifetime--;
}
show() {
push();
translate(this.x, this.y);
rotate(this.rotation);
stroke(255, map(this.lifetime, 20, 0, 255, 0));
strokeWeight(2);
noFill();
// lightening shape
beginShape();
for (let p of this.points) {
vertex(p.x, p.y);
}
endShape();
pop();
}
isDead() {
return this.lifetime <= 0;
}
}
class Particle {
constructor(x, y, ix, iy) {
this.index = {
x: ix,
y: iy
};
this.pos = createVector(x, y);
this.vel = createVector();
this.acc = createVector();
this.mass = 1;
this.locked = false;
this.active = false;
this.radius = baseR;
this.targetR = baseR;
this.col = color(0);
}
applyForce(force) {
let f = force.copy();
f.div(this.mass);
this.acc.add(f);
}
update() {
this.vel.add(this.acc);
this.pos.add(this.vel);
this.acc.mult(0);
this.vel.mult(0.98); // damping
this.radius += 0.1 * (this.targetR - this.radius);
this.col = lerpColor(color(80), color(200), (this.radius - baseR) / (maxR - baseR));
}
applyAttRep() {
for (let nei of neighbors) {
let target = particles[this.index.x + nei[0] + (this.index.y + nei[1]) * cols];
if (target.active === true) {
let force = p5.Vector.sub(this.pos, target.pos);
let d = force.mag();
force.normalize();
let fs = force.copy().mult((target.radius - baseR) / (d * 0.28));
this.acc.add(fs);
this.targetR = baseR * map(d, 0, spacing, 0, 0.5);
this.col = lerpColor(color(250), color(0), this.radius / baseR);
}
}
}
show() {
fill(this.col);
noStroke();
ellipse(this.pos.x, this.pos.y, this.radius * 2);
}
}
class Spring {
constructor(k, rL, a, b) {
this.k = k;
this.restLength = rL;
this.a = a;
this.b = b;
}
update() {
let force = p5.Vector.sub(this.a.pos, this.b.pos);
let x = force.mag() - this.restLength;
force.normalize();
force.mult(this.k * x);
this.b.applyForce(force);
force.mult(-1);
this.a.applyForce(force);
}
show() {
stroke(200);
strokeWeight(2);
noFill();
line(this.a.pos.x, this.a.pos.y, this.b.pos.x, this.b.pos.y);
}
}