To practice resistance,
attraction and mass:
In the space background, a planet with a gradient effect using lerp() is created, and particles are moved under the gravity of the planet.
The mass of the particles determines the gravity and air resistance they are subject to, affecting their trajectory.
By adjusting the "tangential force", the particles can eventually rotate around the planet.
The visual effects are optimized, and glow and trails are added.
At Start
Mooooore
First Version:
Simulation of gravitational attraction:
let magnitude = (gravityConstant * this.mass * other.mass) / (distance * distance)
*To make the particles form an orbital motion, centripetal force alone is not enough, tangential force is also needed:
let perpendicular = createVector(-force.y, force.x);
let tangentFactor = map(distance, 30, 400, 0.8, 0.3);
perpendicular.setMag(magnitude * tangentFactor);
*When the particle orbit is not obvious, the gravitational constant and tangential force need to be adjusted repeatedly.
*To make the motion more natural, appropriate resistance is needed: let airResistance = 0.005
*The initial speed of the particles should not be too fast, otherwise, they will be difficult to capture: this.vel = p5.Vector.random2D().mult(random(0.5, 1.5))
*The lifespan should be long enough for the particles to form a stable orbit: this.life = random(2000, 2500)
*Properly reduce C_GRAVITY to prevent particles from being instantly attracted.
📝Just notes: Air resistance
let resistance = p5.Vector.mult(this.vel, -1);
resistance.normalize();
let speed = this.vel.mag();
let magnitude = speed * speed * AIR_RESISTANCE_COEFFICIENT;
resistance.mult(magnitude);
this.applyForce(resistance);
• The square of speed affects the magnitude of air resistance, causing faster particles to experience greater resistance.
• This prevents particles from accelerating infinitely.
3. Attempting tangential force
let perpendicular = createVector(-force.y, force.x);
let tangentFactor = map(distance, 30, 400, 0.8, 0.3);
perpendicular.setMag(magnitude * tangentFactor);
• Attempted to make particles orbit the gravitational center by adding tangential force.
• ❌ However, in this version, the particles still struggled to maintain a stable orbit (completely unnoticeable!!). Particles were also easily attracted directly into the star.
Second Version:
To prevent particles from directly colliding with the planet, repulsion force was added:
if (distance < 60) {
magnitude = -gravityConstant * this.mass * other.mass / (distance * 0.5);
} else {
magnitude = gravityConstant * this.mass * other.mass / (distance * distance);
}
❌ Completely failed, as particles bounced away, which is not physically accurate.
Adjusted the weight of the tangential force to smooth the particle trajectory:
let perpendicular = createVector(-force.y, force.x);
perpendicular.setMag(magnitude * 0.5);
force.add(perpendicular);
Final version!
1. Glow effect
for (let i = 0; i < 5; i++) {
let alpha = map(i, 0, 5, 50, 0);
stroke(this.r, this.g, this.b, alpha);
ellipse(0, 0, this.radius * 2 + i * 10);
• lerp() was used for making the planet's color transition more natural
• map() was used to adjust transparency
• Multiple semi-transparent circles were stacked to enhance the glowing effect
and
Controlled particle spawn rate:
let particleSpawnRate = 0.95
Removed dead particles
Limited particle speed:
this.vel.limit(3)
Code
// *Changing velocity limit can alter particle motion
let attractors = [];
let particles = [];
let airResistance = 0.006;
let gravityConstant = 4.5;
let particleSpawnRate = 0.99;
let stars = []; // bg
function setup() {
createCanvas(600, 600);
// background stars
for (let i = 0; i < 200; i++) {
stars.push({
x: random(width),
y: random(height),
size: random(0.5, 2),
brightness: random(40, 100)
});
}
// initial planets
for (let i = 0; i < 2; i++) {
let x = random(width * 0.3, width * 0.7);
let y = random(height * 0.3, height * 0.7);
let attractor = new Planet(x, y);
attractors.push(attractor);
}
}
function draw() {
background(10, 10, 20,70);
drawStars();
// Update planets
for (let i = 0; i < attractors.length; i++) {
attractors[i].update();
attractors[i].display();
}
// new particles
if (random(1) > particleSpawnRate) {
let x = random(width);
let y = random(height);
particles.push(new Particle(x, y));
}
// Update particles
for (let i = 0; i < particles.length; i++) {
for (let j = 0; j < attractors.length; j++) {
particles[i].attractedTo(attractors[j]);
}
particles[i].applyResistance();
particles[i].update();
particles[i].display();
}
// lifecycle
let aliveParticles = [];
for (let i = 0; i < particles.length; i++) {
if (particles[i].life > 0) {
aliveParticles.push(particles[i]);
}
}
particles = aliveParticles;
}
function drawStars() {
for (let i = 0; i < stars.length; i++) {
push();
noStroke();
fill(255, 255, 255, stars[i].brightness);
ellipse(stars[i].x, stars[i].y, stars[i].size);
pop();
}
}
class Planet {
constructor(x, y) {
this.pos = createVector(x, y);
this.vel = createVector();
this.acc = createVector();
this.mass = 15;
this.radius = 25;
this.rotation = 0;
this.coreColor = {
r: random(200, 255),
g: random(150, 200),
b: random(100, 150)
};
this.outerColor = {
r: random(50, 100),
g: random(50, 100),
b: random(100, 150)
};
this.rings = random() > 0.7;
}
update() {
this.rotation += 0.005;
}
display() {
push();
translate(this.pos.x, this.pos.y);
rotate(this.rotation);
// glow effect
for (let i = 0; i < 5; i++) {
noFill();
let alpha = map(i, 0, 5, 50, 0);
stroke(
this.coreColor.r,
this.coreColor.g,
this.coreColor.b,
alpha
);
strokeWeight(1);
ellipse(0, 0, this.radius * 2 + i * 10);
}
//rings
if (this.rings) {
for (let i = 0; i < 5; i++) {
noFill();
strokeWeight(2);
let alpha = map(i, 0, 5, 100, 20);
stroke(
this.coreColor.r,
this.coreColor.g,
this.coreColor.b,
alpha
);
ellipse(0, 0, this.radius * 3 + i * 15, (this.radius * 3 + i * 15) * 0.3);
}
}
// planet body+gradient
noStroke();
for (let i = 0; i < 15; i++) {
let ratio = i / 14;
let r = lerp(this.coreColor.r, this.outerColor.r, ratio);
let g = lerp(this.coreColor.g, this.outerColor.g, ratio);
let b = lerp(this.coreColor.b, this.outerColor.b, ratio);
fill(r, g, b, 200);
let size = this.radius * (1 - ratio * 0.5);
ellipse(0, 0, size * 2);
}
pop();
}
}
class Particle {
constructor(x, y, isAttractor = false) {
this.pos = createVector(x, y);
this.vel = p5.Vector.random2D().mult(random(0.5, 1.5));
this.acc = createVector();
this.isAttractor = isAttractor;
this.size = random(3, 6);
this.mass = this.size * 0.5;
this.life = 1500;
this.color = {
r: random(200, 255),
g: random(150, 255),
b: random(200, 255)
};
}
attractedTo(other) {
let force = p5.Vector.sub(other.pos, this.pos);
let distance = force.mag();
if (distance < 300 && distance > 30) {
// relate to mass
let magnitude = (gravityConstant * this.mass * other.mass) / (distance * distance);
force.setMag(magnitude);
let perpendicular = createVector(-force.y, force.x);
perpendicular.setMag(magnitude * (0.5 + this.mass * 0.1));
force.add(perpendicular);
this.applyForce(force);
}
}
display() {
push();
translate(this.pos.x, this.pos.y);
// glow effect
for (let i = 0; i < 3; i++) {
noStroke();
let alpha = map(i, 0, 3, 100, 0) * (this.life / 1300);
fill(this.color.r, this.color.g, this.color.b, alpha);
let glowSize = this.size * (1 + i * 0.5);
ellipse(0, 0, glowSize);
}
// particle trail
for (let i = 0; i < 5; i++) {
let alpha = map(i, 0, 5, 100, 10) * (this.life / 1300);
fill(this.color.r, this.color.g, this.color.b, alpha);
let trailX = -i * this.vel.x * 0.8;
let trailY = -i * this.vel.y * 0.8;
ellipse(trailX, trailY, this.size * (1 - i * 0.15));
}
pop();
}
applyResistance() {
let resistance = p5.Vector.mult(this.vel, -1);
resistance.normalize();
let speed = this.vel.mag();
let magnitude = speed * speed * airResistance;
resistance.mult(magnitude);
this.applyForce(resistance);
}
applyForce(force) {
let f = p5.Vector.div(force, this.mass);
this.acc.add(f);
}
update() {
this.vel.add(this.acc);
this.vel.limit(1.3);
this.pos.add(this.vel);
this.acc.mult(0);
this.life--;
}
}
function mousePressed() {
let planet = new Planet(mouseX, mouseY);
attractors.push(planet);
}