Following a few tutorials to create a physics sim with javascript and html. The goal is to spawn a number of different sized balls and when they spawn to have them fall to the bottom of the canvas and land on each other till it nearly fills the canvas. I’ve gotten pretty far with proper collisions and gravity but one bug I’m running into is that once the balls start to settle at the bottom, the balls start to slowly sink and clip into each other.
gif of the outcome
const canvas = document.querySelector('canvas')
const c = canvas.getContext('2d')
const img = document.getElementById('planet1');
canvas.width = innerWidth-100;
canvas.height = innerHeight-100;
const mouse = {
x: innerWidth / 2,
y: innerHeight / 2
}
const colors = ['#2185C5', '#7ECEFD', '#FF46E5', '#FF7F66'];
var gravity = 0.2;
var friction = .4;
// Event Listeners
addEventListener('mousemove', (event) => {
mouse.x = event.clientX
mouse.y = event.clientY
})
addEventListener('resize', () => {
canvas.width = innerWidth-100
canvas.height = innerHeight-100
init()
})
function randomIntFromRange(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min)
}
function randomColor(colors) {
return colors[Math.floor(Math.random() * colors.length)]
}
function distance(x1, y1, x2, y2) {
const xDist = x2 - x1
const yDist = y2 - y1
return Math.sqrt(Math.pow(xDist, 2) + Math.pow(yDist, 2))
}
function rotate(velocity, angle) {
const rotatedVelocities = {
x: velocity.x * Math.cos(angle) - velocity.y * Math.sin(angle),
y: velocity.x * Math.sin(angle) + velocity.y * Math.cos(angle)
};
return rotatedVelocities;
}
function resolveCollision(particle, otherParticle) {
const xVelocityDiff = (particle.velocity.x - otherParticle.velocity.x);
const yVelocityDiff = (particle.velocity.y - otherParticle.velocity.y);
const xDist = otherParticle.x - particle.x;
const yDist = otherParticle.y - particle.y;
// Prevent accidental overlap of particles
if (xVelocityDiff * xDist + yVelocityDiff * yDist >= 0) {
// Grab angle between the two colliding particles
const angle = -Math.atan2(otherParticle.y - particle.y, otherParticle.x - particle.x);
// Store mass in var for better readability in collision equation
const m1 = particle.mass;
const m2 = otherParticle.mass;
// Velocity before equation
const u1 = rotate(particle.velocity, angle);
const u2 = rotate(otherParticle.velocity, angle);
// Velocity after 1d collision equation
const v1 = { x: u1.x * (m1 - m2) / (m1 + m2) + u2.x * 2 * m2 / (m1 + m2), y: u1.y };
const v2 = { x: u2.x * (m1 - m2) / (m1 + m2) + u1.x * 2 * m2 / (m1 + m2), y: u2.y };
// Final velocity after rotating axis back to original location
const vFinal1 = rotate(v1, -angle);
const vFinal2 = rotate(v2, -angle);
// Swap particle velocities for realistic bounce effect
particle.velocity.x = vFinal1.x;
particle.velocity.y = vFinal1.y;
otherParticle.velocity.x = vFinal2.x;
otherParticle.velocity.y = vFinal2.y;
}
}
// Objects
class Planet {
constructor(x, y, radius, color) {
this.x = x
this.y = y
//this.dx = dx;
//this.dy = dy;
this.velocity = {
x: Math.random() - 0.5,
y: Math.random() - 0.5
}
this.radius = radius
this.color = color
this.mass = 2;
}
draw() {
c.beginPath()
c.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false)
c.fillStyle = this.color
c.fill()
c.strokeStyle = 'black'
c.stroke()
c.closePath()
}
update(planets) {
for (let i = 0; i < planets.length; i++){
if (this === planets[i])continue;
if ((distance(this.x,this.y,planets[i].x,planets[i].y)) - (this.radius + planets[i].radius) < 0){
resolveCollision(this,planets[i])
}
}
if (this.y + this.radius + this.velocity.y > canvas.height) {
this.velocity.y = -this.velocity.y;
this.velocity.y = this.velocity.y * friction;
this.velocity.x = this.velocity.x * friction;
} else {
this.velocity.y += gravity;
}
if (this.x + this.radius >= canvas.width || this.x - this.radius <= 0) {
this.velocity.x = -this.velocity.x * friction;
}
this.x += this.velocity.x;
this.y += this.velocity.y;
this.draw()
}
}
// Implementation
let planets;
function init() {
planets = [];
for (let i = 0; i < 150; i++) {
const radius = randomIntFromRange(10,40);
let x = randomIntFromRange(radius,canvas.width-radius);
let y = randomIntFromRange(radius,canvas.height-radius);
//const dx = randomIntFromRange(-3,3);
//const dy = randomIntFromRange(-2,2);
const color = randomColor(colors);
//dont spawn on top of each other
if (i!==0){
for(let j = 0; j < planets.length; j++){
if((distance(x,y,planets[j].x,planets[j].y)) - (radius + planets[j].radius) < 0){
x = randomIntFromRange(radius,canvas.width-radius);
y = randomIntFromRange(radius,canvas.height-radius);
j = -1;
}
}
}
planets.push(new Planet(x,y,radius,color));
}
}
// Animation Loop
function animate() {
requestAnimationFrame(animate)
let length = planets.length;
c.clearRect(0, 0, canvas.width, canvas.height);
for(let i =0;i<planets.length;i++){
planets[i].update(planets);
}
}
init()
animate()
I’m thinking it has to do with how I’m applying the gravity on line 132 but I am not sure how to proceed with fixing it. Any help would be greatly appreciated! Thanks!!