p5.disableFriendlyErrors = true;
let N_PARTICLES = 50;
let particles = [];
let grid;
function setup() {
createCanvas(windowWidth, windowHeight);
grid = new Grid(windowWidth, windowHeight, 22);
for (let n = 0; n < N_PARTICLES; n++) {
let particle = new Particle(new createVector(random(windowWidth), random(windowHeight)))
particles.push(particle);
grid.addParticle(particle);
}
}
function draw() {
background(220);
if (mouseIsPressed) {
for(let n = 0; n < 5; n++){
let particle = new Particle(new createVector(mouseX+random(-1,1), mouseY+random(-1,1)))
particles.push(particle);
grid.addParticle(particle);
}
}
textSize(30)
textAlign(LEFT, TOP)
let numCollisionChecks = 0
let startneigh = performance.now();
for (let p of particles) {
let neighbors = grid.getNeighbors(p)
numCollisionChecks += p.checkCollision(neighbors)
p.updateState();
}
let endneigh = performance.now();
let coll = `${numCollisionChecks} collisions computed in ${ endneigh - startneigh} ms`
let startup = Date.now();
for (let p of particles) {
p.checkEdges();
grid.removeParticle(p)
grid.addParticle(p)
}
let endup = Date.now();
let up = 'Grid updated in ' + round(endup - startup, 2) + ' ms'
noFill()
let startd = Date.now();
for (let p of particles) {
p.display();
}
let endd = Date.now();
let dr = 'Draw in ' + round(endd - startd, 2) + ' ms'
fill(0)
text('Particles: ' + particles.length, 10, 10)
text(coll, 10, 50);
text(up, 10, 90);
text(dr, 10, 130);
}
class Particle {
constructor(pos) {
this.pos = pos;
this.velocity = createVector(random(-1.25, 1.25), random(-1.25, 1.25));
this.acceleration = createVector(0, 0);
this.mass = random(3, 10)
this.radius = this.mass;
this.maxSpeed = 10;
}
updateState(newPos) {
this.velocity.add(this.acceleration);
this.velocity.limit(this.maxSpeed); // Limit the particle's speed
this.pos.add(this.velocity);
}
checkEdges() {
if (this.pos.x - this.radius < 0) {
this.pos.x = this.radius; // Prevent from leaving the canvas from the left side
this.velocity.x *= -1;
} else if (this.pos.x + this.radius > width) {
this.pos.x = width - this.radius; // Prevent from leaving the canvas from the right side
this.velocity.x *= -1;
}
if (this.pos.y - this.radius < 0) {
this.pos.y = this.radius; // Prevent from leaving the canvas from the top
this.velocity.y *= -1;
} else if (this.pos.y + this.radius > height) {
this.pos.y = height - this.radius; // Prevent from leaving the canvas from the bottom
this.velocity.y *= -1;
}
}
checkCollision(otherParticles) {
let counter = 0
for (let other of otherParticles) {
if (this != other) {
let distance = this.pos.dist(other.pos);
let minDistance = this.radius + other.radius + 1;
if (distance <= minDistance) {
// Calculate collision response
let normal = p5.Vector.sub(other.pos, this.pos).normalize();
let relativeVelocity = p5.Vector.sub(other.velocity, this.velocity);
let impulse = p5.Vector.mult(normal, 2 * p5.Vector.dot(relativeVelocity, normal) / 2);
// Apply repulsion force to prevent sticking
let repulsion = p5.Vector.mult(normal, minDistance - distance + 2).mult(1);
// Update velocities
this.velocity.add(p5.Vector.div(impulse, this.mass));
other.velocity.sub(p5.Vector.div(impulse, other.mass));
// Apply repulsion force
this.pos.sub(p5.Vector.div(repulsion, this.mass));
other.pos.add(p5.Vector.div(repulsion, other.mass));
}
counter++
}
}
return counter
}
display() {
ellipse(this.pos.x, this.pos.y, this.radius * 2);
}
}
/*
The lookup grid
*/
class Grid {
constructor(i, t, s) {
(this.cellSize = s),
(this.numCols = Math.ceil(i / s)),
(this.numRows = Math.ceil(t / s)),
(this.cells = []);
for (let e = 0; e < this.numCols; e++) {
this.cells[e] = [];
for (let l = 0; l < this.numRows; l++) {
this.cells[e][l] = [];
}
}
}
addParticle(i) {
let t = Math.floor(i.pos.x / this.cellSize);
let s = Math.floor(i.pos.y / this.cellSize);
this.cells[t][s].push(i)
i.gridCell = {
col: t,
row: s
}
}
removeParticle(i) {
let {
col: t,
row: s
} = i.gridCell
let e = this.cells[t][s];
let l = e.indexOf(i);
e.splice(l, 1);
}
determineCell(i) {
let t = Math.floor(i.pos.x / this.cellSize);
let s = Math.floor(i.pos.y / this.cellSize);
return {
col: t,
row: s
}
}
getNeighbors(particle) {
let top_left = [
floor((particle.pos.x - particle.radius) / this.cellSize),
floor((particle.pos.y - particle.radius) / this.cellSize),
]
let bottom_right = [
floor((particle.pos.x + particle.radius) / this.cellSize),
floor((particle.pos.y + particle.radius) / this.cellSize),
]
let neighbors = []
for (let i = top_left[0]; i <= bottom_right[0]; i++) {
for (let j = top_left[1]; j <= bottom_right[1]; j++) {
if (i < 0 || j < 0 || i >= this.numCols || j >= this.numRows) continue
let c = this.cells[i][j]
for (let p of c) {
// don't add the particle itself
if (p != particle) neighbors.push(p)
}
}
}
return neighbors
}
}