How to evenly spread circles gravitating towards a point? - javascript

I have created a full demonstration of the problem I'm experiencing below:
const rng = (min, max) => Math.random() * (max - min + 1) + min;
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.strokeStyle = "#000";
ctx.lineWidth = 4;
ctx.fillStyle = "#ff0000";
function drawCircle(c) {
ctx.beginPath();
ctx.arc(c.x, c.y, c.r, 0, 2 * Math.PI);
ctx.stroke();
ctx.fill();
}
class Circle {
constructor(x, y, r) {
this.x = x;
this.y = y;
this.r = r;
this.vX = 0;
this.vY = 0;
}
}
const circles = [];
for (let i = 0; i < 300; i++) {
circles.push(new Circle(rng(0, canvas.width), rng(0, canvas.height), rng(12, 14)));
}
function processCollision(c1, c2) {
const deltaX = c2.x - c1.x;
const deltaY = c2.y - c1.y;
const sumRadius = c1.r + c2.r;
const centerDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (centerDistance === 0 || centerDistance > sumRadius) { return; } // not colliding
const circleDistance = centerDistance - sumRadius;
const aX = deltaX / centerDistance;
const aY = deltaY / centerDistance;
const force = 5;
c1.vX += aX * circleDistance * force;
c1.vY += aY * circleDistance * force;
}
function update() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (const c of circles) {
c.vX = (canvas.width / 2) - c.x; // move towards center x
c.vY = (canvas.height / 2) - c.y; // move towards center y
}
for (const c1 of circles) {
for (const c2 of circles) {
c1 !== c2 && processCollision(c1, c2);
}
}
for (const c of circles) {
c.x += c.vX * (1 / 60);
c.y += c.vY * (1 / 60);
drawCircle(c);
}
}
setInterval(update, 16.6666);
<canvas width="600" height="600" style="border:1px solid #d3d3d3;">
Notice how all the circles gravitate around the center. However, they are all heavily colliding with one another. I would like to modify the processCollision function such that the circles no longer significantly overlap one another and instead are roughly evenly spread around the center point.
I tried increasing the force variable, but unfortunately while this does indeed cause greater spread, it also creates lot of shaky and jerky movement. The solution must be smooth, similar to the example above. I have been messing with this for weeks but unfortunately cannot seem to come to a solution.

This seems to behave the way you probably want (or close to it)... It uses a control theory model combined with a physics model, and one needs to tweak the constants k0, k1, strength, buffer, step_size...
const rng = (min, max) => Math.random() * (max - min + 1) + min;
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
ctx.strokeStyle = '#000';
ctx.lineWidth = 4;
ctx.fillStyle = '#ff0000';
const k0 = 1.5;
const k1 = 5;
const strength = 1000000;
const buffer = 2;
class Disc {
constructor(x, y, r) {
this.x = x;
this.y = y;
this.r = r;
this.vX = 0;
this.vY = 0;
return;
}
drawDisc(ctx) {
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
ctx.stroke();
ctx.fill();
return;
}
addVelocity(step_size) {
this.x = this.x + step_size * this.vX;
this.y = this.y + step_size * this.vY;
return;
}
addAcceleration(aX, aY, step_size) {
this.vX = this.vX + step_size * aX;
this.vY = this.vY + step_size * aY;
return;
}
applyCentralAcceleration(step_size) {
const accelX = -k1 * this.vX - k0 * (this.x - canvas.width / 2);
const accelY = -k1 * this.vY - k0 * (this.y - canvas.height / 2);
this.addAcceleration(accelX, accelY, step_size);
return;
}
applyInteractionAcceleration(that, step_size) {
let dX = this.x - that.x;
let dY = this.y - that.y;
const dist = dX * dX + dY * dY;
const magnitude = strength / (dist - (this.r + buffer + that.r) ** 2) ** 2;
dX = magnitude * dX;
dY = magnitude * dY;
this.addAcceleration(dX, dY, step_size);
return;
}
}
class System {
constructor(numDiscs) {
this.n = numDiscs;
this.discs = [];
for (let i = 0; i < numDiscs; i++) {
this.discs.push(
new Disc(rng(0, canvas.width), rng(0, canvas.height), rng(6, 7))
);
}
return;
}
applyCentralAcceleration(step_size) {
for (let i = 0; i < this.n; i++) {
this.discs[i].applyCentralAcceleration(step_size);
}
}
applyInteractionAcceleration(step_size) {
for (let i = 0; i < this.n; i++) {
for (let j = 0; j < this.n; j++) {
if (i === j) {
continue;
}
this.discs[i].applyInteractionAcceleration(this.discs[j], step_size);
}
}
}
applyVelocity(step_size) {
for (let i = 0; i < this.n; i++) {
this.discs[i].addVelocity(step_size);
}
}
updateSystemState(step_size) {
this.applyCentralAcceleration(step_size);
this.applyInteractionAcceleration(step_size);
this.applyVelocity(step_size);
return;
}
drawSystemDiscs() {
for (let i = 0; i < this.n; i++) {
this.discs[i].drawDisc(ctx);
}
}
}
systemOfDiscs = new System(50);
function update() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const step_size = 1 / 100;
systemOfDiscs.updateSystemState(step_size);
systemOfDiscs.drawSystemDiscs();
return;
}
setInterval(update, 16.6666);
<canvas width="300" height="300" style="border: 1px solid #d3d3d3"></canvas>

Related

How to implement object rotation animation?

There is a falling balls code https://jsfiddle.net/d1e8x7wk/,
which is generated using canvas
Code also added to this editor
window.addEventListener('load', () => {
//---------------------------------------
// Set up ball options
//---------------------------------------
const imgBalls = [
'https://cdn.iconscout.com/icon/premium/png-256-thumb/basketball-2500972-2093649.png',
'https://cdn.iconscout.com/icon/premium/png-256-thumb/basketball-2500972-2093649.png',
'https://cdn.iconscout.com/icon/premium/png-256-thumb/basketball-2500972-2093649.png',
'https://cdn.iconscout.com/icon/premium/png-256-thumb/basketball-2500972-2093649.png',
'https://cdn.iconscout.com/icon/premium/png-256-thumb/basketball-2500972-2093649.png',
'https://cdn.iconscout.com/icon/premium/png-256-thumb/basketball-2500972-2093649.png'
]
let ballCount = imgBalls.length, // How many balls
DAMPING = 0.4, // Damping
GRAVITY = 0.01, // Gravity strength
SPEED = 5, // Ball speed
ballAdditionTime = 100, // How fast are balls added
ballSrc = imgBalls, // Ball image source
topOffset = 400, // Adjust this for initial ball spawn point
xOffset = 0, // left offset
yOffset = 0, // bottom offset
ballDensity = 20, // How dense are the balls
ball_1_size = 200, // Ball 1 size
ball_2_size = 180, // Ball 2 size
ball_3_size = 62, // Ball 6 size
canvasWidth = 1500, // Canvas width
canvasHeight = 1000, // Canvas height
stackBall = true, // Stack the balls (or false is overlap)
ballsLoaded = 0,
stopAnimation = false;
//---------------------------------------
// Canvas globals
//---------------------------------------
let canvas,
ctx,
TWO_PI = Math.PI * 2,
balls = [],
vel_x,
vel_y;
let rect = {
x: 0,
y: 0,
w: canvasWidth,
h: canvasHeight
};
//---------------------------------------
// do the animation
//---------------------------------------
window.requestAnimFrame =
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, ballAdditionTime);
};
//---------------------------------------
// set up the ball
//---------------------------------------
const Ball = function(x, y, radius, num) {
this.x = x;
this.y = y;
this.px = x;
this.py = y;
this.fx = 0;
this.fy = 0;
this.radius = radius;
this.num = num;
this.angle = 0;
// Different ball sizes
let random = Math.round(Math.random() * imgBalls.length)
if (random === 0) {
this.width = ball_1_size;
this.height = ball_1_size;
if (stackBall) {
this.radius = ball_1_size / 2;
}
} else if (random === 1 || random === 2) {
this.width = ball_2_size;
this.height = ball_2_size;
if (stackBall) {
this.radius = ball_2_size / 2;
}
} else {
this.width = ball_3_size;
this.height = ball_3_size;
if (stackBall) {
this.radius = ball_3_size / 2;
}
}
ctx.rotate(this.angle * Math.PI / 180);
};
//---------------------------------------
// Apply the physics
//---------------------------------------
Ball.prototype.apply_force = function(delta) {
delta *= delta;
this.fy += GRAVITY;
this.x += this.fx * delta;
this.y += this.fy * delta;
this.fx = this.fy = 0;
};
//---------------------------------------
// Newtonian motion algorithm
//---------------------------------------
Ball.prototype.velocity = function() {
var nx = this.x * 2 - this.px;
var ny = this.y * 2 - this.py;
this.px = this.x;
this.py = this.y;
this.x = nx;
this.y = ny;
};
//---------------------------------------
// Ball prototype
//---------------------------------------
Ball.prototype.draw = function(ctx) {
img = new Image();
img.src = imgBalls[this.num];
if (stackBall) {
ctx.drawImage(
img,
this.x - this.radius - xOffset,
this.y - this.radius - xOffset,
this.width,
this.height
);
} else {
ctx.drawImage(
img,
this.x - xOffset,
this.y - yOffset,
this.width,
this.height
);
}
};
//---------------------------------------
// resolve collisions (ball on ball)
//---------------------------------------
let resolve_collisions = function(ip) {
let i = balls.length;
while (i--) {
let ball_1 = balls[i];
let n = balls.length;
while (n--) {
if (n == i) continue;
let ball_2 = balls[n];
let diff_x = ball_1.x - ball_2.x;
let diff_y = ball_1.y - ball_2.y;
let length = diff_x * diff_x + diff_y * diff_y;
let dist = Math.sqrt(length);
let real_dist = dist - (ball_1.radius + ball_2.radius);
if (real_dist < 0) {
let vel_x1 = ball_1.x - ball_1.px;
let vel_y1 = ball_1.y - ball_1.py;
let vel_x2 = ball_2.x - ball_2.px;
let vel_y2 = ball_2.y - ball_2.py;
let depth_x = diff_x * (real_dist / dist);
let depth_y = diff_y * (real_dist / dist);
ball_1.x -= depth_x * 0.5;
ball_1.y -= depth_y * 0.5;
ball_2.x += depth_x * 0.5;
ball_2.y += depth_y * 0.5;
if (ip) {
let pr1 = (DAMPING * (diff_x * vel_x1 + diff_y * vel_y1)) / length;
let pr2 = (DAMPING * (diff_x * vel_x2 + diff_y * vel_y2)) / length;
vel_x1 += pr2 * diff_x - pr1 * diff_x;
vel_x2 += pr1 * diff_x - pr2 * diff_x;
vel_y1 += pr2 * diff_y - pr1 * diff_y;
vel_y2 += pr1 * diff_y - pr2 * diff_y;
ball_1.px = ball_1.x - vel_x1;
ball_1.py = ball_1.y - vel_y1;
ball_2.px = ball_2.x - vel_x2;
ball_2.py = ball_2.y - vel_y2;
}
}
}
}
};
//---------------------------------------
// Bounce off the walls
//---------------------------------------
let check_walls = function() {
let i = balls.length;
while (i--) {
let ball = balls[i];
if (ball.x < ball.radius) {
let vel_x = ball.px - ball.x;
ball.x = ball.radius;
ball.px = ball.x - vel_x * DAMPING;
} else if (ball.x + ball.radius > canvas.width) {
vel_x = ball.px - ball.x;
ball.x = canvas.width - ball.radius;
ball.px = ball.x - vel_x * DAMPING;
}
// Ball is new. So don't do collision detection until it hits the canvas. (with an offset to stop it snapping)
if (ball.y > 100) {
if (ball.y < ball.radius) {
let vel_y = ball.py - ball.y;
ball.y = ball.radius;
ball.py = ball.y - vel_y * DAMPING;
} else if (ball.y + ball.radius > canvas.height) {
vel_y = ball.py - ball.y;
ball.y = canvas.height - ball.radius;
ball.py = ball.y - vel_y * DAMPING;
}
}
}
};
//---------------------------------------
// Add a ball to the canvas
//---------------------------------------
let add_ball = function(num) {
let x = Math.random() * canvas.width;
let y = Math.random() * canvas.height;
let r = 30 + Math.random() * ballDensity;
let diff_x = x;
let diff_y = y;
let dist = Math.sqrt(diff_x * diff_x + diff_y * diff_y);
balls.push(new Ball(x, y, r, num));
};
//---------------------------------------
// iterate balls
//---------------------------------------
let update = function() {
let iter = 1;
let delta = SPEED / iter;
while (iter--) {
let i = balls.length;
while (i--) {
balls[i].apply_force(delta);
balls[i].velocity();
}
resolve_collisions();
check_walls();
i = balls.length;
while (i--) {
balls[i].velocity();
let ball = balls[i];
}
resolve_collisions();
check_walls();
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
i = balls.length;
while (i--) {
balls[i].draw(ctx);
}
requestAnimFrame(update);
};
//---------------------------------------
// Set up the canvas object
//---------------------------------------
function doBalls() {
stopAnimation = false;
canvas = document.getElementById("balls");
ctx = canvas.getContext("2d");
let $canvasDiv = document.querySelector(".section");
function respondCanvas() {
canvas.height = $canvasDiv.clientHeight;
canvas.width = $canvasDiv.clientWidth;
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
respondCanvas();
ballAdd();
}
function ballAdd() {
let count = 0;
let timer = setInterval(function() {
addBallTimer();
}, 100);
let addBallTimer = function() {
add_ball(count % ballCount);
count++;
if (count === ballCount) {
stopTimer();
}
};
let stopTimer = function() {
clearInterval(timer);
};
update();
}
doBalls();
});
.section {
position: relative;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: #000;
}
<div class="section">
<canvas id="balls"></canvas>
</div>
I'm trying to make a rotation the balls
const Ball = function(x, y, radius, num) {
this.x = x;
this.y = y;
this.px = x;
this.py = y;
this.fx = 0;
this.fy = 0;
this.radius = radius;
this.num = num;
this.angle = 0;
// Different ball sizes
let random = Math.round(Math.random() * imgBalls.length)
if (random === 0) {
this.width = ball_1_size;
this.height = ball_1_size;
if (stackBall) {
this.radius = ball_1_size / 2;
}
} else if (random === 1 || random === 2) {
this.width = ball_2_size;
this.height = ball_2_size;
if (stackBall) {
this.radius = ball_2_size / 2;
}
} else {
this.width = ball_3_size;
this.height = ball_3_size;
if (stackBall) {
this.radius = ball_3_size / 2;
}
}
ctx.rotate(this.angle * Math.PI / 180);
};
After reading the information,
I wrote this code, but it does not work for me
ctx.rotate(this.angle * Math.PI / 180);
I must have misunderstood how to do this, tell me how to properly rotate the balls
Thanks in advance
You will need to
increase the angle at some point in your code
apply a rotation transform to rotate around each ball center before each drawImage call
ctx.save();
ctx.translate(+this.x, +this.y);
ctx.rotate(this.angle++ * Math.PI / 180);
ctx.translate(-this.x, -this.y);
ctx.drawImage
ctx.restore()
ctx.save() and ctx.restore() is called to restore the canvas transform after each draw call.
Please read
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/rotate#rotating_a_shape_around_its_center
for details on applying transforms to Canvas.

how to load next string when i erase particle text?

I am new to vanilla js and webgl content. I created a particle text which get distorted on mouse hover. and whose particles are connected with each other with a line. the code is as follows.
const canvas = document.getElementById("canvas1");
const dpr = window.devicePixelRatio;
//console.log(dpr);
const ctx = canvas.getContext("2d");
ctx.scale(dpr, dpr);
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
let particleArray = [];
let adjustX = 10;
let adjustY = 0;
//handle mouse
const mouse = {
x: null,
y: null,
radius: 30,
};
window.addEventListener("mousemove", function (event) {
mouse.x = event.x;
mouse.y = event.y;
//console.log(mouse.x, mouse.y);
});
ctx.fillStyle = "white";
ctx.font = "35px verdana";
ctx.fillText("aman ", 0, 30);
const textCoordinates = ctx.getImageData(0, 0, 100, 100);
class Particle {
constructor(x, y) {
this.x = x + Math.random() * 6 - 3;
this.y = y + Math.random() * 6 - 3;
this.size = 1;
this.baseX = this.x;
this.baseY = this.y;
this.density = Math.random() * 10 + 50;
}
draw() {
ctx.fillStyle = "white";
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
}
//distance between mouse and particle
update() {
let dx = mouse.x - this.x;
let dy = mouse.y - this.y;
let distance = Math.sqrt(dx * dx + dy * dy);
let forceDirectionX = dx / distance;
let forceDirectionY = dy / distance;
let maxDistance = mouse.radius;
let force = (maxDistance - distance) / maxDistance;
let directionX = forceDirectionX * force * this.density;
let directionY = forceDirectionY * force * this.density;
if (distance < mouse.radius) {
this.x -= directionX;
this.y -= directionY;
} else {
if (this.x !== this.baseX) {
let dx = this.x - this.baseX;
this.x += dx / 10;
}
if (this.y !== this.baseY) {
let dy = this.y - this.baseY;
this.y += dy / 10;
}
}
}
}
//console.log(textCoordinates);
function init() {
particleArray = [];
for (let y = 0, y2 = textCoordinates.height; y < y2; y++) {
for (let x = 0, x2 = textCoordinates.width; x < x2; x++) {
if (
textCoordinates.data[y * 4 * textCoordinates.width + x * 4 + 3] > 128
) {
let positionX = x + adjustX;
let positionY = y + adjustY;
particleArray.push(new Particle(positionX * 4, positionY * 4));
}
}
}
}
init();
//console.log(particleArray);
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < particleArray.length; i++) {
particleArray[i].draw();
particleArray[i].update();
}
connect();
requestAnimationFrame(animate);
}
animate();
function connect() {
let opacityValue = 1;
for (let a = 0; a < particleArray.length; a++) {
for (let b = a; b < particleArray.length; b++) {
let dx = particleArray[a].x - particleArray[b].x;
let dy = particleArray[a].y - particleArray[b].y;
let distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 14) {
opacityValue = 0.5;
ctx.strokeStyle = "rgb(200,150,160," + opacityValue + ")";
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(particleArray[a].x, particleArray[a].y);
ctx.lineTo(particleArray[b].x, particleArray[b].y);
ctx.stroke();
}
}
}
}
now what I want to do is that, when I erase this particle text, next text must appear. just like on this website https://animal.cc/ .
can anyone please help me in this :))
(This isn't three.js related)
When you determine "this particle text" has been erased, you can do this in animate():
ctx.fillText("NEW ", 0, 30);
textCoordinates = ctx.getImageData(0, 0, 100, 100);
init();
EDIT: code for comment (below)
for (let y = 0, y2 = textCoordinates.height; y < y2; y++) {
for (let x = 0, x2 = textCoordinates.width; x < x2; x++) {

Adding gravity to billiard physics in JS canvas animation

I am trying to write aa small physics demo using Javascript. I have multiple balls that bounce off each other just fine, but things go wrong when I try to add gravity.
I am trying to conserve the momentum once they hit, but when I add constant gravity to each one, the physics start to break down.
Here is what I have in terms of code:
class Ball {
constructor ({
x,
y,
vx,
vy,
radius,
color = 'red',
}) {
this.x = x
this.y = y
this.vx = vx
this.vy = vy
this.radius = radius
this.color = color
this.mass = 1
}
render (ctx) {
ctx.save()
ctx.fillStyle = this.color
ctx.strokeStyle = this.color
ctx.translate(this.x, this.y)
ctx.strokeRect(-this.radius, -this.radius, this.radius * 2, this.radius * 2)
ctx.beginPath()
ctx.arc(0, 0, this.radius, Math.PI * 2, false)
ctx.closePath()
ctx.fill()
ctx.restore()
return this
}
getBounds () {
return {
x: this.x - this.radius,
y: this.y - this.radius,
width: this.radius * 2,
height: this.radius * 2
}
}
}
const intersects = (rectA, rectB) => {
return !(rectA.x + rectA.width < rectB.x ||
rectB.x + rectB.width < rectA.x ||
rectA.y + rectA.height < rectB.y ||
rectB.y + rectB.height < rectA.y)
}
const checkWall = (ball) => {
const bounceFactor = 0.5
if (ball.x + ball.radius > canvas.width) {
ball.x = canvas.width - ball.radius
ball.vx *= -bounceFactor
}
if (ball.x - ball.radius < 0) {
ball.x = ball.radius
ball.vx *= -bounceFactor
}
if (ball.y + ball.radius > canvas.height) {
ball.y = canvas.height - ball.radius
ball.vy *= -1
}
if (ball.y - ball.radius < 0) {
ball.y = ball.radius
ball.vy *= -bounceFactor
}
}
const rotate = (x, y, sin, cos, reverse) => {
return {
x: reverse ? x * cos + y * sin : x * cos - y * sin,
y: reverse ? y * cos - x * sin : y * cos + x * sin
}
}
const checkCollision = (ball0, ball1, dt) => {
const dx = ball1.x - ball0.x
const dy = ball1.y - ball0.y
const dist = Math.sqrt(dx * dx + dy * dy)
const minDist = ball0.radius + ball1.radius
if (dist < minDist) {
//calculate angle, sine, and cosine
const angle = Math.atan2(dy, dx)
const sin = Math.sin(angle)
const cos = Math.cos(angle)
//rotate ball0's position
const pos0 = {x: 0, y: 0}
//rotate ball1's position
const pos1 = rotate(dx, dy, sin, cos, true)
//rotate ball0's velocity
const vel0 = rotate(ball0.vx, ball0.vy, sin, cos, true)
//rotate ball1's velocity
const vel1 = rotate(ball1.vx, ball1.vy, sin, cos, true)
//collision reaction
const vxTotal = (vel0.x - vel1.x)
vel0.x = ((ball0.mass - ball1.mass) * vel0.x + 2 * ball1.mass * vel1.x) /
(ball0.mass + ball1.mass)
vel1.x = vxTotal + vel0.x
const absV = Math.abs(vel0.x) + Math.abs(vel1.x)
const overlap = (ball0.radius + ball1.radius) - Math.abs(pos0.x - pos1.x)
pos0.x += vel0.x / absV * overlap
pos1.x += vel1.x / absV * overlap
//rotate positions back
const pos0F = rotate(pos0.x, pos0.y, sin, cos, false)
const pos1F = rotate(pos1.x, pos1.y, sin, cos, false)
//adjust positions to actual screen positions
ball1.x = ball0.x + pos1F.x
ball1.y = ball0.y + pos1F.y
ball0.x = ball0.x + pos0F.x
ball0.y = ball0.y + pos0F.y
//rotate velocities back
const vel0F = rotate(vel0.x, vel0.y, sin, cos, false)
const vel1F = rotate(vel1.x, vel1.y, sin, cos, false)
ball0.vx = vel0F.x
ball0.vy = vel0F.y
ball1.vx = vel1F.x
ball1.vy = vel1F.y
}
}
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
let oldTime = 0
canvas.width = innerWidth
canvas.height = innerHeight
document.body.appendChild(canvas)
const log = document.getElementById('log')
const balls = new Array(36).fill(null).map(_ => new Ball({
x: Math.random() * innerWidth,
y: Math.random() * innerHeight,
vx: (Math.random() * 2 - 1) * 5,
vy: (Math.random() * 2 - 1) * 5,
radius: 20,
}))
requestAnimationFrame(updateFrame)
function updateFrame (ts) {
const dt = ts - oldTime
oldTime = ts
ctx.clearRect(0, 0, innerWidth, innerHeight)
for (let i = 0; i < balls.length; i++) {
const ball = balls[i]
// ADD GRAVITY HERE
ball.vy += 2
ball.x += ball.vx * (dt * 0.005)
ball.y += ball.vy * (dt * 0.005)
checkWall(ball)
}
for (let i = 0; i < balls.length; i++) {
const ball0 = balls[i]
for (let j = i + 1; j < balls.length; j++) {
const ball1 = balls[j]
// CHECK FOR COLLISIONS HERE
checkCollision(ball0, ball1, dt)
}
}
for (let i = 0; i < balls.length; i++) {
const ball = balls[i]
ball.render(ctx)
}
// const dist = ball2.x - ball1.x
// if (Math.abs(dist) < ball1.radius + ball2.radius) {
// const vxTotal = ball1.vx - ball2.vx
// ball1.vx = ((ball1.mass - ball2.mass) * ball1.vx + 2 * ball2.mass * ball2.vx) / (ball1.mass + ball2.mass)
// ball2.vx = vxTotal + ball1.vx
// ball1.x += ball1.vx
// ball2.x += ball2.vx
// }
// ball.vy += 0.5
// ball.x += ball.vx
// ball.y += ball.vy
//
// ball.render(ctx)
requestAnimationFrame(updateFrame)
}
* { margin: 0; padding: 0; }
As you can see, I have checkCollision helper method, which calculates the kinetic energy and new velocities of a ball once it has collided with another ball. My update loop looks like this:
// add velocities to balls position
// check if its hitting any wall and bounce it back
for (let i = 0; i < balls.length; i++) {
const ball = balls[i]
// Add constant gravity to the vertical velocity
// When balls stack up on each other at the bottom, the gravity is still applied and my
// "checkCollision" method freaks out and the physics start to explode
ball.vy += 0.8
ball.x += ball.vx * (dt * 0.005)
ball.y += ball.vy * (dt * 0.005)
checkWall(ball)
}
for (let i = 0; i < balls.length; i++) {
const ball0 = balls[i]
for (let j = i + 1; j < balls.length; j++) {
const ball1 = balls[j]
// Check collisions between two balls
checkCollision(ball0, ball1, dt)
}
}
// Finally render the ball on-screen
for (let i = 0; i < balls.length; i++) {
const ball = balls[i]
ball.render(ctx)
}
How do I calculate aa gravity, while preventing the physics from exploding when the balls start stacking on top of each other?
It seems that the gravity force is colliding with the "checkCollision" method. The checkCollision method tries to move them back in place, but the constant gravity overwrites it and continues pulling them down.
EDIT: After some reading I understand some Verlet integration is in order, but am having difficulties with wrapping my head around it.
for (let i = 0; i < balls.length; i++) {
const ball = balls[i]
// This line needs to be converted to verlet motion?
ball.vy += 2
ball.x += ball.vx * (dt * 0.005)
ball.y += ball.vy * (dt * 0.005)
checkWall(ball)
}
Balls do not overlap
There is a fundamental flaw in the collision testing due to the fact that the collisions are calculated only when 2 balls overlap. In the real world this never happens.
The result of "collide on overlap" when many balls are interacting, will result in behavior that does not conserve the total energy of the system.
Resolve by order of collision
You can resolve collisions such that balls never overlap however the amount of processing is indeterminate per frame, growing exponentially as the density of balls increases.
The approach is to locate the first collision between balls in the time between frames. Resolve that collision and then with the new position of that collision find the next collision closest in time forward from the last. Do that until there are no pending collisions for that frame. (There is more to it than that) The result is that the simulation will never be in the impossible state where balls overlap.
Check out my Pool simulator on CodePen that uses this method to simulate pool balls. The balls can have any speed and always resolve correctly.
Verlet integration.
However you can reduce the noise using the overlapping collisions by using verlet integration which will keep the total energy of the balls at a more stable level.
To do that we introduce 2 new properties of the ball, px, py that hold the previous position of the ball.
Each frame we calculate the balls velocity as the difference between the current position and the new position. That velocity is used to do the calculations for the frame.
When a ball changes direction (hits wall or another ball) we also need to change the balls previous position to match where it would have been on the new trajectory.
Use constant time steps.
Using time steps based on time since last frame will also introduce noise and should not be used in the overlap collision method.
Reduce time, increase iteration
To further combat the noise you need to slow the overall speed of the balls to reduce the amount they overlay and thus more closely behave as if they collided at the point ballA.radius + ballB.radius apart. Also you should test every ball against every other ball, not just ball against balls above it in the balls array.
To keep the animation speed up you solve the ball V ball V wall collisions a few times per frame. The example does 5. The best value depends on the total energy of the balls, the level of noise that is acceptable, and the CPU power of the device its running on.
Accuracy matters
Your collision function is also way out there. I had a quick look and it did not look right. I added an alternative in the example.
When a ball hits a wall it does so at some time between the frames. You must move the ball away from the wall by the correct distance. Not doing so is like simulating a ball that sticks to a wall a tiny bit each time it hits, further diverging from what really happens.
Example
This is a rewrite of your original code. Click canvas to add some energy.
const ctx = canvas.getContext("2d");
const BOUNCE = 0.75;
const resolveSteps = 5;
var oldTime = 0;
const $setOf = (count, fn = (i) => i) => {var a = [], i = 0; while (i < count) { a.push(fn(i++)) } return a };
const $rand = (min = 1, max = min + (min = 0)) => Math.random() * (max - min) + min;
const $randP = (min = 1, max = min + (min = 0), p = 2) => Math.random() ** p * (max - min) + min;
var W = canvas.width, H = canvas.height;
const BALL_COUNT = 80;
const BALL_RADIUS = 15, BALL_MIN_RADIUS = 6;
const GRAV = 0.5 / resolveSteps;
requestAnimationFrame(updateFrame);
canvas.addEventListener("click", () => {
balls.forEach(b => {
b.px = b.x + (Math.random() * 18 - 9);
b.py = b.y + (Math.random() * -18);
})
});
class Ball {
constructor({x, y, vx, vy, radius}) {
this.x = x;
this.y = y;
this.px = x - vx;
this.py = y - vy;
this.vx = vx;
this.vy = vy;
this.radius = radius;
this.mass = radius * radius * Math.PI * (4 / 3); // use sphere volume as mass
}
render(ctx) {
ctx.moveTo(this.x + this.radius, this.y);
ctx.arc(this.x, this.y, this.radius, Math.PI * 2, false);
}
move() {
this.vx = this.x - this.px;
this.vy = this.y - this.py;
this.vy += GRAV;
this.px = this.x;
this.py = this.y;
this.x += this.vx;
this.y += this.vy;
this.checkWall();
}
checkWall() {
const ball = this;
const top = ball.radius;
const left = ball.radius;
const bottom = H - ball.radius;
const right = W - ball.radius;
if (ball.x > right) {
const away = (ball.x - right) * BOUNCE;
ball.x = right - away;
ball.vx = -Math.abs(ball.vx) * BOUNCE;
ball.px = ball.x - ball.vx;
} else if (ball.x < left) {
const away = (ball.x - left) * BOUNCE;
ball.x = left + away;
ball.vx = Math.abs(ball.vx) * BOUNCE;
ball.px = ball.x - ball.vx;
}
if (ball.y > bottom) {
const away = (ball.y - bottom) * BOUNCE;
ball.y = bottom - away;
ball.vy = -Math.abs(ball.vy) * BOUNCE;
ball.py = ball.y - ball.vy;
} else if (ball.y < top) {
const away = (ball.y - top) * BOUNCE;
ball.y = top + away;
ball.vy = Math.abs(ball.vy) * BOUNCE;
ball.py = ball.y - ball.vy;
}
}
collisions() {
var b, dx, dy, nx, ny, cpx, cpy, p, d, i = 0;
var {x, y, vx, vy, px, py, radius: r, mass: m} = this;
while (i < balls.length) {
b = balls[i++];
if (this !== b) {
const rr = r + b.radius;
if (x + rr > b.x && x < b.x + rr && y + rr > b.y && y < b.y + rr) {
dx = x - b.x;
dy = y - b.y;
d = (dx * dx + dy * dy) ** 0.5;
if (d < rr) {
nx = (b.x - x) / d;
ny = (b.y - y) / d;
p = 2 * (vx * nx + vy * ny - b.vx * nx - b.vy * ny) / (m + b.mass);
cpx = (x * b.radius + b.x * r) / rr;
cpy = (y * b.radius + b.y * r) / rr;
x = cpx + r * (x - b.x) / d;
y = cpy + r * (y - b.y) / d;
b.x = cpx + b.radius * (b.x - x) / d;
b.y = cpy + b.radius * (b.y - y) / d;
px = x - (vx -= p * b.mass * nx);
py = y - (vy -= p * b.mass * ny);
b.px = b.x - (b.vx += p * m * nx);
b.py = b.y - (b.vy += p * m * ny);
}
}
}
}
this.x = x;
this.y = y;
this.px = px;
this.py = py;
this.vx = vx;
this.vy = vy;
this.checkWall();
}
}
const balls = (() => {
return $setOf(BALL_COUNT, () => new Ball({
x: $rand(BALL_RADIUS, W - BALL_RADIUS),
y: $rand(BALL_RADIUS, H - BALL_RADIUS),
vx: $rand(-2, 2),
vy: $rand(-2, 2),
radius: $randP(BALL_MIN_RADIUS, BALL_RADIUS, 4),
}));
})();
function updateFrame(ts) {
var i = 0, j = resolveSteps;
ctx.clearRect(0, 0, W, H);
while (i < balls.length) { balls[i++].move() }
while (j--) {
i = 0;
while (i < balls.length) { balls[i++].collisions(balls) }
}
ctx.fillStyle = "#0F0";
ctx.beginPath();
i = 0;
while (i < balls.length) { balls[i++].render(ctx) }
ctx.fill();
requestAnimationFrame(updateFrame)
}
<canvas id="canvas" width="400" height="180" style="border:1px solid black;"></canvas>
<div style="position: absolute; top: 10px; left: 10px;">Click to stir</div>

Javascript text particle in canvas slow in firefox but not on chrome (requestAnimationFrame ?)

I'm struggling with the implementation of a text particle in javascript.
My code is working fine with chromium-based browsers (Chromium: 84) but is unexpectedly slow on firefox (79.0).
I've searched a lot, but still don't find a solution and where the problem is.(This is pretty much linked to this Javascript Animation(Canvas) not working in firefox, Edge but works on Chrome).
Here is my codepen:
https://codepen.io/jgazeau/pen/LYNYPYB
function getRandomInt(max) {
return Math.floor(Math.random() * Math.floor(max));
}
function getCssVar(v) {
return getComputedStyle(document.documentElement).getPropertyValue(v);
}
let text404 = '404';
let canvas = document.getElementById('scene');
let ch = canvas.height = canvas.getBoundingClientRect().height;
let cw = canvas.width = canvas.getBoundingClientRect().width;
let sceneBackground = getCssVar('--scene-background');
let context = canvas.getContext('2d');
let previousMouseCoord = {x:0, y:0};
let mouseCoord = {x:0, y:0};
let sceneResize = false;
let particlesCount = 0;
let particles = [];
let colors = [
getCssVar('--particle-color-1'),
getCssVar('--particle-color-2'),
getCssVar('--particle-color-3'),
getCssVar('--particle-color-4'),
getCssVar('--particle-color-5')
];
const dpi = 200;
let Particle = function(x, y) {
this.x = getRandomInt(cw);
this.y = getRandomInt(ch);
this.coord = {x:x, y:y};
this.r = Math.min((getRandomInt((cw / dpi)) + 1), 6);
this.vx = (Math.random() - 0.5) * 100;
this.vy = (Math.random() - 0.5) * 100;
this.accX = 0;
this.accY = 0;
this.friction = Math.random() * 0.05 + 0.90;
this.color = colors[Math.floor(Math.random() * 6)];
}
Particle.prototype.render = function(isDisableMouse) {
this.accX = (this.coord.x - this.x) / 100;
this.accY = (this.coord.y - this.y) / 100;
this.vx += this.accX;
this.vy += this.accY;
this.vx *= this.friction;
this.vy *= this.friction;
this.x += this.vx;
this.y += this.vy;
if (!isDisableMouse) {
let a = this.x - mouseCoord.x;
let b = this.y - mouseCoord.y;
var distance = Math.sqrt(a * a + b * b);
if(distance < (cw / 15)) {
this.accX = (this.x - mouseCoord.x) / 100;
this.accY = (this.y - mouseCoord.y) / 100;
this.vx += this.accX;
this.vy += this.accY;
}
}
context.fillStyle = this.color;
context.beginPath();
context.arc(this.x, this.y, this.r, 0, Math.PI * 2, false);
context.fill();
context.closePath();
}
function onmouseCoordMove(e) {
mouseCoord.x = e.clientX;
mouseCoord.y = e.clientY;
}
function onTouchMove(e) {
if(e.touches.length > 0 ) {
mouseCoord.x = e.touches[0].clientX;
mouseCoord.y = e.touches[0].clientY;
}
}
function onTouchEnd() {
mouseCoord.x = -9999;
mouseCoord.y = -9999;
}
function initScene() {
ch = canvas.height = canvas.getBoundingClientRect().height;
cw = canvas.width = canvas.getBoundingClientRect().width;
context.clearRect(0, 0, canvas.width, canvas.height);
context.font = 'bold ' + (cw / 5) + 'px sans-serif';
context.fillStyle = sceneBackground;
context.textAlign = 'center';
context.fillText(text404, cw / 2, ch / 2);
let imageData = context.getImageData(0, 0, cw, ch).data;
context.clearRect(0, 0, canvas.width, canvas.height);
context.globalCompositeOperation = 'screen';
particles = [];
for(let y = 0; y < ch; y += Math.round(cw / dpi)) {
for(let x = 0; x < cw; x += Math.round(cw / dpi)) {
if(imageData[((x + y * cw) * 4) + 3] > 128){
particles.push(new Particle(x, y));
}
}
}
particlesCount = particles.length;
}
function renderScene() {
context.clearRect(0, 0, canvas.width, canvas.height);
let isDisableMouse = false;
if ((previousMouseCoord.x === mouseCoord.x) && (previousMouseCoord.x === mouseCoord.x)) {
isDisableMouse = true;
} else {
previousMouseCoord.x = mouseCoord.x;
previousMouseCoord.x = mouseCoord.x;
isDisableMouse = false;
}
for (let i = 0; i < particlesCount; i++) {
particles[i].render(isDisableMouse);
}
requestAnimationFrame(renderScene);
};
document.addEventListener('DOMContentLoaded', function() {
initScene();
renderScene();
window.addEventListener('mousemove', onmouseCoordMove);
window.addEventListener('touchmove', onTouchMove);
window.addEventListener('touchend', onTouchEnd);
window.addEventListener('resize', function() {
if (!sceneResize) {
requestAnimationFrame(function() {
initScene();
sceneResize = false;
});
sceneResize = true;
}
});
});
Can anyone know where this slow issue could come from ?
Thanks a lot for your help.

How do I simulate a gas behavior in JavaScript?

I'm still currently at high-school, and my self-taught programming knowledge isn't that accurate at all.
By the way, I worked on many projects(I code in C, C++, Python, HTML, JavaScript) and the one I'm currently working on has been giving me a hard time: a simulator for a gas sample's behavior(following Maxwell-Boltzmann's law of distribution for speeds).
M-B distribution is a kinda hard and advanced physics argument especially for my high-school standards, but I still managed to understand its functioning and found out the equation that gives the number of molecules with a velocity between v and v+dv:M-B equationhere.
Now, programming JS part takes place, and in the following snippet I inserted the
balls constructor part, the draw() core function and the part where I spawn balls(100, just as a test, since they should be around 1000) according to M-B equation; now, I think everything should be fine, but spawned balls behave very strangely changing direction whenever they want to, and sometimes getting stuck with each other instead of elastically bouncing off... could you please take a look at the "spawning" part and see if somethings wrong? Then, I also uploaded the whole HTML+JS+CSS code so you can try it, and also verify JS functions for ball-to-ball and ball-to-wall collisions and all that kind of stuff.
I would extremely appreciate it if someone could please help me... I'm confused.
Heartfelt thanks in advance for any answer,
Greg🙏.
class Ball {
constructor(x, y, dx, dy, radius){
this.radius = radius;
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
// mass is that of a sphere as opposed to circle
// it *does* make a difference in how realistic it looks
this.mass = this.radius * this.radius * this.radius;
this.color = 'red';
};
draw() {
ctx.beginPath();
ctx.arc(Math.round(this.x), Math.round(this.y), this.radius, 0, 2 * Math.PI);
ctx.fillStyle = this.color;
ctx.fill();
ctx.strokeStyle = this.color;
ctx.stroke();
ctx.closePath();
};
speed() {
// magnitude of velocity vector
return Math.sqrt(this.dx * this.dx + this.dy * this.dy);
};
angle() {
// velocity's angle with the x axis
return Math.atan2(this.dy, this.dx);
};
onGround() {
return (this.y + this.radius >= canvas.height)
};
};
function draw() {
currentTime = (new Date()).getTime();
dt = (currentTime - lastTime) / 1000; // delta time in seconds
// dirty and lazy solution
// instead of scaling down every velocity vector
// we decrease the speed of time
dt *= 0.1;
if (clearCanv) clearCanvas();
canvasBackground();
if (!paused) {
moveObjects();
ballCollision();
}
drawObjects();
//logger();
lastTime = currentTime;
window.requestAnimationFrame(draw);
}
let N = 500;
let m = 2.66e-26;
let T = 300;
let dV = 50;
let k = 1.38e-23;
let v = 50;
let balls = 0;
let anglex;
for(let i = 0; i < 29; i++) { //each 50m/s, with dv = 50, until 1500m/s,
probArray[i] = Math.floor(4 * Math.PI * N * (((m) / (2 * Math.PI * k * T))**1.5) * (v**2) * Math.exp((-m) / (2 * k * T) * (v**2)) * dV);//molecules num between v e v+50
v += 50;
}
v = 50;
for(let i = 0; i < 29; i++){
let n = 0;
while(n < probArray[i] && balls < 100) {
anglex = ((Math.random() * 360) * Math.PI) / 180; //converted in radians;
vel = Math.round((Math.random() * 50) + v);
objArray[objArray.length] = new Ball(randomX(), randomY(), Math.cos(anglex) * vel, Math.sin(anglex) * vel, 3);
n++;
balls++;
}
v += 50;
}
class Ball {
constructor(x, y, dx, dy, radius){
this.radius = radius;
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
// mass is that of a sphere as opposed to circle
// it *does* make a difference in how realistic it looks
this.mass = this.radius * this.radius * this.radius;
this.color = 'red';
};
draw() {
ctx.beginPath();
ctx.arc(Math.round(this.x), Math.round(this.y), this.radius, 0, 2 * Math.PI);
ctx.fillStyle = this.color;
ctx.fill();
ctx.strokeStyle = this.color;
ctx.stroke();
ctx.closePath();
};
speed() {
// magnitude of velocity vector
return Math.sqrt(this.dx * this.dx + this.dy * this.dy);
};
angle() {
// velocity's angle with the x axis
return Math.atan2(this.dy, this.dx);
};
onGround() {
return (this.y + this.radius >= canvas.height)
};
};
//FUNCTIONS
//will remove
function randomColor() {
let red = Math.floor(Math.random() * 3) * 127;
let green = Math.floor(Math.random() * 3) * 127;
let blue = Math.floor(Math.random() * 3) * 127;
// dim down the small balls
if (!bigBalls){
red *= 0.65
green *= 0.65
blue *= 0.65
}
let rc = "rgb(" + red + ", " + green + ", " + blue + ")";
return rc;
}
function randomX() {
let x = Math.floor(Math.random() * canvas.width);
if (x < 30) {
x = 30;
} else if (x + 30 > canvas.width) {
x = canvas.width - 30;
}
return x;
}
function randomY() {
let y = Math.floor(Math.random() * canvas.height);
if (y < 30) {
y = 30;
} else if (y + 30 > canvas.height) {
y = canvas.height - 30;
}
return y;
}
//will remove
function randomRadius() {
if (bigBalls) {
let r = Math.ceil(Math.random() * 10 + 20);
return r;
} else {
let r = Math.ceil(Math.random() * 2 + 2);
//let r = 5;
return r;
}
}
//will remove
function randomDx() {
let r = Math.floor(Math.random() * 10 - 4);
return r;
}
//will remove
function randomDy() {
let r = Math.floor(Math.random() * 10 - 3);
return r;
}
function distanceNextFrame(a, b) {
return Math.sqrt((a.x + a.dx - b.x - b.dx)**2 + (a.y + a.dy - b.y - b.dy)**2) - a.radius - b.radius;
}
function distance(a, b) {
return Math.sqrt((a.x - b.x)**2 + (a.y - b.y)**2);
}
let canvas = document.getElementById("myCanvas");
let ctx = canvas.getContext("2d");
let objArray = [];
let probArray = [];
let paused = false;
let bumped = false;
let leftHeld = false;
let upHeld = false;
let rightHeld = false;
let downHeld = false;
let arrowControlSpeed = .25;
let gravityOn = false;
let clearCanv = true;
let bigBalls = false;
let lastTime = (new Date()).getTime();
let currentTime = 0;
let dt = 0;
let numStartingSmallBalls = 500;
let numStartingBigBalls = 0;
document.addEventListener("keydown", keyDownHandler);
function clearCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
function keyDownHandler(event) {
if (event.keyCode == 80) { // p
paused = !paused;
} else if (event.keyCode == 82) { // r
objArray = [];
} else if (event.keyCode == 75) { // k
clearCanv = !clearCanv;
} else if (event.keyCode == 88) { // x
bigBalls = !bigBalls;
}
}
function canvasBackground() {
canvas.style.backgroundColor = "rgb(215, 235, 240)";
}
function wallCollision(ball) {
if (ball.x - ball.radius + ball.dx < 0 ||
ball.x + ball.radius + ball.dx > canvas.width) {
ball.dx *= -1;
}
if (ball.y - ball.radius + ball.dy < 0 ||
ball.y + ball.radius + ball.dy > canvas.height) {
ball.dy *= -1;
}
if (ball.y + ball.radius > canvas.height) {
ball.y = canvas.height - ball.radius;
}
if (ball.y - ball.radius < 0) {
ball.y = ball.radius;
}
if (ball.x + ball.radius > canvas.width) {
ball.x = canvas.width - ball.radius;
}
if (ball.x - ball.radius < 0) {
ball.x = ball.radius;
}
}
function ballCollision() {
for (let i=0; i<objArray.length-1; i++) {
for (let j=i+1; j<objArray.length; j++) {
let ob1 = objArray[i]
let ob2 = objArray[j]
let dist = distance(ob1, ob2)
if (dist < ob1.radius + ob2.radius) {
let theta1 = ob1.angle();
let theta2 = ob2.angle();
let phi = Math.atan2(ob2.y - ob1.y, ob2.x - ob1.x);
let m1 = ob1.mass;
let m2 = ob2.mass;
let v1 = ob1.speed();
let v2 = ob2.speed();
let dx1F = (v1 * Math.cos(theta1 - phi) * (m1-m2) + 2*m2*v2*Math.cos(theta2 - phi)) / (m1+m2) * Math.cos(phi) + v1*Math.sin(theta1-phi) * Math.cos(phi+Math.PI/2);
let dy1F = (v1 * Math.cos(theta1 - phi) * (m1-m2) + 2*m2*v2*Math.cos(theta2 - phi)) / (m1+m2) * Math.sin(phi) + v1*Math.sin(theta1-phi) * Math.sin(phi+Math.PI/2);
let dx2F = (v2 * Math.cos(theta2 - phi) * (m2-m1) + 2*m1*v1*Math.cos(theta1 - phi)) / (m1+m2) * Math.cos(phi) + v2*Math.sin(theta2-phi) * Math.cos(phi+Math.PI/2);
let dy2F = (v2 * Math.cos(theta2 - phi) * (m2-m1) + 2*m1*v1*Math.cos(theta1 - phi)) / (m1+m2) * Math.sin(phi) + v2*Math.sin(theta2-phi) * Math.sin(phi+Math.PI/2);
ob1.dx = dx1F;
ob1.dy = dy1F;
ob2.dx = dx2F;
ob2.dy = dy2F;
staticCollision(ob1, ob2)
}
}
wallCollision(objArray[i]);
}
if (objArray.length > 0)
wallCollision(objArray[objArray.length - 1])
}
function staticCollision(ob1, ob2, emergency = false)
{
let overlap = ob1.radius + ob2.radius - distance(ob1, ob2);
let smallerObject = ob1.radius < ob2.radius ? ob1 : ob2;
let biggerObject = ob1.radius > ob2.radius ? ob1 : ob2;
// When things go normally, this line does not execute.
// "Emergency" is when staticCollision has run, but the collision
// still hasn't been resolved. Which implies that one of the objects
// is likely being jammed against a corner, so we must now move the OTHER one instead.
// in other words: this line basically swaps the "little guy" role, because
// the actual little guy can't be moved away due to being blocked by the wall.
if (emergency) [smallerObject, biggerObject] = [biggerObject, smallerObject]
let theta = Math.atan2((biggerObject.y - smallerObject.y), (biggerObject.x - smallerObject.x));
smallerObject.x -= overlap * Math.cos(theta);
smallerObject.y -= overlap * Math.sin(theta);
if (distance(ob1, ob2) < ob1.radius + ob2.radius) {
// we don't want to be stuck in an infinite emergency.
// so if we have already run one emergency round; just ignore the problem.
if (!emergency) staticCollision(ob1, ob2, true)
}
}
function moveObjects() {
for (let i=0; i<objArray.length; i++) {
let ob = objArray[i];
ob.x += ob.dx * dt;
ob.y += ob.dy * dt;
}
}
function drawObjects() {
for (let obj in objArray) {
objArray[obj].draw();
}
}
function draw() {
currentTime = (new Date()).getTime();
dt = (currentTime - lastTime) / 1000; // delta time in seconds
// dirty and lazy solution
// instead of scaling down every velocity vector
// we decrease the speed of time
dt *= 0.1;
if (clearCanv) clearCanvas();
canvasBackground();
if (!paused) {
moveObjects();
ballCollision();
}
drawObjects();
//logger();
lastTime = currentTime;
window.requestAnimationFrame(draw);
}
/*
for (i = 0; i<numStartingSmallBalls; i++) {
objArray[objArray.length] = new Ball(randomX(), randomY(), 3);
}*/
let N = 500;
let m = 2.66e-26;
let T = 300;
let dV = 50;
let k = 1.38e-23;
let v = 50;
let balls = 0;
let angolox;
for(let i = 0; i < 29; i++) { //ogni 50 velocità, con dv = 50, fino a 1500m/s,
probArray[i] = Math.floor(4 * Math.PI * N * (((m) / (2 * Math.PI * k * T))**1.5) * (v**2) * Math.exp((-m) / (2 * k * T) * (v**2)) * dV);//num molecole tra v e v+50
v += 50;
}
v = 50;
for(let i = 0; i < 29; i++){
let n = 0;
while(n < probArray[i] && balls < 100) {
angolox = ((Math.random() * 360) * Math.PI) / 180; //converted in radians;
vel = Math.round((Math.random() * 50) + v);
objArray[objArray.length] = new Ball(randomX(), randomY(), Math.cos(angolox) * vel, Math.sin(angolox) * vel, 3);
n++;
balls++;
}
v += 50;
}
draw();
body {
background-color: khaki;
text-align: center;
font-family: Ubuntu Mono;
}
#title {
color: black;
font-size: 200%;
font-style: normal;
margin: 1px;
border: 1px;
}
#balls {
margin-top: 5px;
}
#myCanvas {
margin-top: -20px;
}
section.footer {
color: black;
font-family: Ubuntu Mono;
font-style: normal;
font-size: small;
}
#disclaimer {
font-size: 74%;
color: gray;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>2d collision</title>
<link rel="stylesheet" type="text/css" href="gas.css">
</head>
<body style="text-align: center">
<canvas id="myCanvas" width="1225%" height="650%" style="border:1px solid black; margin-top: 10px;"></canvas>
<script src="gas.js"></script>
<p>
<strong>[X]</strong> to toggle SIZE of future balls <br>
<strong>[K]</strong> to toggle clearCanvas(); <br>
<strong>[P]</strong>: pause/unpause || <strong>[R]</strong>: [RESET]
</p>
<p>
source code on github
</p>
<div id="disclaimer" align="center" style="word-break: break-word; width: 350px; display:inline-block;">
<p>
Make sure to press a few buttons and play around.<br>Made with pure javascript.
</p>
</div>
</section>
</body>
</html>

Categories