preface
Hi, here is CSS and WebGL magic – Alphardex.
In practice before writing The particle effects found some specific implementation has a lot to do with physical knowledge (such as fireworks effects), and then happened to find a book of god – The Nature of Code, it teaches you how to use physics and mathematics knowledge to simulate realize some of The effects of Nature, after reading my soul was completely inspired, The result is this article, which will take you through implementing a physics driven particle effect from scratch.
Here’s a look at the final implementation
Let’s get started!
The preparatory work
Click on the lower right corner of the author’s P5.js template to fork a copy
Creation began
Particles in the class
Draw a dot somewhere, and that’s it
class Particle {
s: p5;
position: p5.Vector; / / position
constructor(s: p5, position = s.createVector(0.0)) {
this.s = s;
this.position = position.copy();
}
/ / show
display() {
this.s.circle(this.position.x, this.position.y, 6);
}
/ / run
run() {
this.display(); }}Copy the code
Particle system class
You draw a few more points, you store them in an array, and you get what’s called a particle system
class ParticleSystem {
s: p5;
particles: Particle[]; // All the particles under the system
origin: p5.Vector; // System origin
constructor(s: p5, origin = s.createVector(0.0)) {
this.s = s;
this.particles = [];
this.origin = origin;
}
// Add a single particle
addParticle() {
const particle = new Particle(this.s, this.origin);
this.particles.push(particle);
}
// Add multiple particles
addParticles(count = 1) {
for (let i = 0; i < count; i++) {
this.addParticle(); }}/ / run
run() {
for (const particle of this.particles) { particle.run(); }}}Copy the code
Create a particle
Draw 100 particles on the picture
const sketch = (s: p5) = > {
let ps: ParticleSystem;
const setup = () = > {
s.createCanvas(s.windowWidth, s.windowHeight);
ps = new ParticleSystem(s, s.createVector(s.width / 2, s.height / 2));
ps.addParticles(100);
};
const draw = () = > {
s.background(0);
s.blendMode(s.ADD);
ps.run();
s.blendMode(s.BLEND);
};
s.setup = setup;
s.draw = draw;
};
Copy the code
All 100 particles are overlapping in the center of the picture. They should be shuffled
Dislocate particles
Just assign random coordinates to all the particles
class ParticleSystem {...// Scramble particle positions
shuffle() {
this.particles.forEach((p) = > {
const x = this.s.random(0.this.s.width);
const y = this.s.random(0.this.s.height);
const randPos = this.s.createVector(x, y); p.position = randPos; }); }}const sketch = (s: p5) = >{...const setup = () = >{... ps.shuffle(); }; };Copy the code
100 particles are scattered all over the picture
Particles roaming
It’s not going to work if they’re all stationary, but if you give them a random velocity, they’re going to move
class Particle {...velocity: p5.Vector; / / speed
constructor(s: p5, position = s.createVector(0.0)){...this.velocity = this.s.createVector(0.0);
}
// Update the status
update() {
this.position.add(this.velocity);
}
/ / run
run() {
this.update();
this.display(); }}class ParticleSystem {...// Let the particles wander randomly
wander() {
this.particles.forEach((p) = > {
const x = this.s.random(-1.1);
const y = this.s.random(-1.1);
const randVel = this.s.createVector(x, y); p.velocity = randVel; }); }}const sketch = (s: p5) = >{...const setup = () = >{... ps.wander(); }; };Copy the code
The particles then float around the screen
Apply a force to the particle
In physics class, the teacher taught us that force can change the motion state of the object. According to niu’s Law, we can apply a force to the object, increase the acceleration of the object, and thus increase the speed of the object
Try to make the particles fall under the influence of gravity
class Particle {...acceleration: p5.Vector; / / acceleration
topSpeed: number; // Speed limit
constructor(s: p5, position = s.createVector(0.0)){...this.acceleration = this.s.createVector(0.0);
this.topSpeed = 12;
}
// Update the status
update() {
this.velocity.add(this.acceleration);
this.velocity.limit(this.topSpeed);
this.position.add(this.velocity);
this.acceleration.mult(0);
}
/ / force f
applyForce(force: p5.Vector) {
// Newton's second law: F = ma
const mass = 1; // Set the mass unit to 1
const acceleration = p5.Vector.div(force, mass);
this.acceleration.add(acceleration); }}class ParticleSystem {...// Apply force to each particle
applyForce(force: p5.Vector) {
this.particles.forEach((p) = >p.applyForce(force)); }}const sketch = (s: p5) = >{...const draw = () = >{... ps.run();const gravity = s.createVector(0.0.05); ps.applyForce(gravity); . }; };Copy the code
Attractor class
Add a New Attractor class that attracts the particles so they all run toward it
class Attractor extends Particle {
attractForceMag: number; // The size of the attraction
radius: number; / / radius
id: number; / / id identification
constructor(s: p5, position = s.createVector(0.0), radius = 16, id = 0) {
super(s, position);
this.attractForceMag = 0.05;
this.radius = radius;
this.id = id;
}
/ / show
display() {
this.s.circle(this.position.x, this.position.y, this.radius * 2);
}
// Apply gravity
applyAttractForce(p: Particle) {
const attractForce = p5.Vector.sub(this.position, p.position);
attractForce.setMag(this.attractForceMag); p.applyForce(attractForce); }}class ParticleSystem {...// Apply attraction to each particle
applyAttractor(attractor: Attractor) {
this.particles.forEach((p) = >attractor.applyAttractForce(p)); }}const sketch = (s: p5) = >{...let attractor: Attractor;
const setup = () = >{... attractor =new Attractor(s, s.createVector(s.width / 2, s.height / 2));
};
const draw = () = >{...// const gravity = s.gravity (0, 0.05);
// ps.applyForce(gravity);attractor.run(); ps.applyAttractor(attractor); . }; };Copy the code
Click Add Attractor
By clicking the mouse, we can dynamically add attractors to the screen
const sketch = (s: p5) = >{...// let attractor: Attractor;
let attractors: Attractor[] = [];
let currentAttractorId = 0;
let mousePos: p5.Vector;
const setup = () = >{...// attractor = new Attractor(s, s.createVector(s.width / 2, s.height / 2));
};
const draw = () = >{ mousePos = s.createVector(s.mouseX, s.mouseY); .// const gravity = s.gravity (0, 0.05);
// ps.applyForce(gravity);
// attractor.run();
// ps.applyAttractor(attractor);
// The attractor attracts particles
attractors.forEach((attractor) = >{ attractor.run(); ps.applyAttractor(attractor); }); . };// Add one attractor for each click
const mousePressed = () = > {
const attractor = new Attractor(s, mousePos, 16, currentAttractorId);
attractors.push(attractor);
currentAttractorId += 1; }; . s.mousePressed = mousePressed; };Copy the code
Attractors attract each other
It would be even more interesting if the attractors could also attract each other
const sketch = (s: p5) = >{...const draw = () = >{...// The action of the attractors towards each other
for (let i = 0; i < attractors.length; i++) {
for (let j = 0; j < attractors.length; j++) {
if(i ! == j) {const attractorA = attractors[j];
const attractorB = attractors[i];
// Attractors attract each otherattractorA.applyAttractForce(attractorB); }}}... }; };Copy the code
The attractor merges as it approaches
But attraction alone is surely not enough. How about trying to get them to merge like a fish eating a fish?
class Attractor extends Particle {
// Are you close to another person
isNearAnother(attractor: Attractor) {
const distAB = p5.Vector.dist(this.position, attractor.position);
const distMin = (this.radius + attractor.radius) * 0.8;
const isNear = distAB < distMin;
return isNear;
}
/ / absorption
absorb(attractor: Attractor) {
this.attractForceMag += attractor.attractForceMag;
this.radius += attractor.radius;
this.velocity = this.s.createVector(0.0); }}const sketch = (s: p5) = >{...const draw = () = >{...// The action of the attractors towards each other
for (let i = 0; i < attractors.length; i++) {
for (let j = 0; j < attractors.length; j++) {
if(i ! == j) {const attractorA = attractors[j];
const attractorB = attractors[i];
// Attractors attract each other
attractorA.applyAttractForce(attractorB);
// If the two attractors are too close together, attractor A absorbs attractor B
if (attractorA.isNearAnother(attractorB)) {
attractorA.absorb(attractorB);
attractors = attractors.filter(
(attractor) = >attractor.id ! == attractorB.id ); }}}}... }; };Copy the code
Notice that the RADIUS on the Attractor was turned down
The suction body collapses after the radius exceeds the limit
What if I accidentally get too much? Let it collapse on the spot. Boom Shakalaka!
class Attractor extends Particle {...isCollasping: boolean; // Is it collapsing
isDead: boolean; // Whether the collapse is complete
static RADIUS_LIMIT = 100; // Radius upper limit.constructor(s: p5, position = s.createVector(0.0), radius = 16, id = 0){...this.isCollasping = false;
this.isDead = false;
}
/ / collapse
collapse() {
this.isCollasping = true;
this.radius *= 0.75;
if (this.radius < 1) {
this.isDead = true; }}}const sketch = (s: p5) = >{...const draw = () = >{...// The attractor attracts particles
attractors.forEach((attractor) = >{...// When an attractor absorbs too many particles, its radius exceeds the limit and it collapses
if (
attractor.radius >= Attractor.RADIUS_LIMIT ||
attractor.isCollasping
) {
attractor.collapse();
if (attractor.isDead) {
attractors = attractors.filter((item) = >item.id ! == attractor.id); }}}); . }; . };Copy the code
Set the gradient background color
The black background is too drab, make it a beautiful gradient
const sketch = (s: p5) = > {
// https://p5js.org/examples/color-linear-gradient.html
// Create gradients
const setGradient = (
x: number,
y: number,
w: number,
h: number,
c1: p5.Color,
c2: p5.Color,
axis: number
) = > {
s.strokeWeight(1);
if (axis === 1) {
// Top to bottom gradient
for (let i = y; i <= y + h; i++) {
let inter = s.map(i, y, y + h, 0.1);
letc = s.lerpColor(c1, c2, inter); s.stroke(c); s.line(x, i, x + w, i); }}else if (axis === 2) {
// Left to right gradient
for (let i = x; i <= x + w; i++) {
let inter = s.map(i, x, x + w, 0.1);
letc = s.lerpColor(c1, c2, inter); s.stroke(c); s.line(i, y, i, y + h); }}};const draw = () = >{... s.background(0);
s.blendMode(s.ADD);
setGradient(
0.0,
s.width,
s.height,
s.color("#2b5876"),
s.color("#4e4376"),
1); ps.run(); . }; };Copy the code
Line trailing effect
A trailing track can be obtained by recording the last position of a particle’s motion and connecting it to the current position
class Particle {...lastPosition: p5.Vector; // The previous position (used to create delayed trailing effects)
constructor(s: p5, position = s.createVector(0.0)){...this.lastPosition = this.s.createVector(0.0);
}
/ / show
display() {
// this.s.circle(this.position.x, this.position.y, 6);
this.s.fill(255);
this.s.stroke(255);
this.s.strokeWeight(2);
this.s.strokeJoin(this.s.ROUND);
this.s.strokeCap(this.s.ROUND);
this.s.line(
this.position.x,
this.position.y,
this.lastPosition.x,
this.lastPosition.y
);
}
// Update the status
update() {
this.lastPosition = this.position.copy();
this.velocity.add(this.acceleration);
this.velocity.limit(this.topSpeed);
this.position.add(this.velocity);
this.acceleration.mult(0); }}const sketch = (s: p5) = >{...const setup = () = > {
const canvas = s.createCanvas(s.windowWidth, s.windowHeight);
// The scene is semi-transparent to make the trailing lines clearer
const ctx = (canvas as any).drawingContext as CanvasRenderingContext2D;
ctx.globalAlpha = 0.5; . }; };Copy the code
Attractor oscillation effect
By using the sine sine function, we can make the size of the attractor oscillate regularly
class Attractor extends Particle {...oscillatingRadius: number; // Wave radius
constructor(s: p5, position = s.createVector(0.0), radius = 16, id = 0){...this.oscillatingRadius = 0;
}
/ / show
display() {
this.s.blendMode(this.s.BLEND);
this.s.stroke(0);
this.s.fill("# 000000");
this.oscillate();
this.s.circle(this.position.x, this.position.y, this.oscillatingRadius * 2);
}
/ / oscillation
oscillate() {
const oscillatingRadius = this.s.map(
this.s.sin(this.s.frameCount / 6) * 2,
-1.1.this.radius * 0.8.this.radius
);
this.oscillatingRadius = oscillatingRadius; }}Copy the code
The project address
Attractor Particle System