Delayed transformations in JavaScript? - javascript

I'm working on a simple DHTML application, nothing strange: I have around 500 balls colliding with each other with different speeds and by clicking a button they stack based on their velocities creating a Maxwell-Boltzmann distribution(but that's another talk).
Well, for switching from the first to the second case I'm changing every single x and y coordinate for every single ball, to move them and pile them in that way.
Now, my question is: is it possible to have a sort of an animation in which balls from the first chaotic case, instead of jumping into the chart-configuration in one frame(as soon as the button gets clicked), gradually stack on top of each other in a much fancier and "graphical" animation? For example with transitions or transformations, but I couldn't manage to find a way to do that... I'm quite new to programming.
By the way, here's the full code:
FULL CODE:
class Ball {
constructor(x, y, dx, dy, radius, color){
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 = color;
};
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;
/* if(ob1.speed() * 160 < 400)
ob1.color = 'lightblue';
else if(ob1.speed() * 160 > 800)
ob1.color = 'red';
else
ob1.color = 'orange';
if(ob2.speed() * 160 < 400)
ob2.color = 'lightblue';
else if(ob2.speed() * 160 > 800)
ob2.color = 'red';
else
ob2.color = 'orange';*/
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 * 1;
ob.y += ob.dy * 1;
}
}
function drawObjects() {
for (let obj in objArray) {
objArray[obj].draw();
}
}
let begin = true;
let temperature;
document.getElementById("temp").oninput = function()
{
temperature = parseInt(document.getElementById("temp").value);
generateBalls(temperature);
}
function drawChart()
{
let index = 0
let cx = 10 , cy;
for(let i = 0; i < 59; i++) {
cy = canvas.height - 6;
if(probArray[i] != 0) {
n = 0;
while(n < probArray[i]) {
objArray[index + n].x = cx;
objArray[index + n].y = cy;
cy -= 12;
n++;
}
index += n;
}
cx += 20;
}
chart = !chart;
}
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 *= 20;*/
if(begin) {
generateBalls(300);
begin = false;
}
//work in progress
if(chart) {
drawChart();
}
if (clearCanv) clearCanvas();
canvasBackground();
if (!paused) {
moveObjects();
ballCollision();
}
drawObjects();
lastTime = currentTime;
window.requestAnimationFrame(draw);
}
//work in progress
function setColor(vel)
{
let red = 255, green = 255, blue = 255;
let rc;
green /= (vel * 0.001);
blue /= (vel * 0.01);
return rc = "rgb(" + red + ", " + green + ", " + blue + ")";
}
let N = 550;
let m = 2.66e-26;
let T = 5;
let dV = 50;
let k = 1.38e-23;
let v = 50;
let balls;
let angolox;
let vel;
let color;
function generateBalls(T)
{
paused = false;
v = 50;
objArray = [];
for(let i = 0; i < 59; i++)
{ //each 50m/s, with dv = 50, until 2000m/s
//molecules number between v and v+50
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);
v += 50;
}
v = 50;
let l;
for(let i = 0; i < 59; i++)
{
let n = 0;
balls = 0;
while(n < probArray[i])
{
angolox = ((Math.random() * 360) * Math.PI) / 180; //converted in radians;
vel = Math.round((Math.random() * 50) + v) / 160;
if(vel * 160 < 400)
color = 'lightblue';
else if(vel * 160 > 800)
color = 'red';
else
color = 'orange';
l = objArray.length;
objArray[objArray.length] = new Ball(randomX(), randomY(), Math.cos(angolox) * vel, Math.sin(angolox) * vel, 5, color);
balls++;
n++;
}
v += 50;
}
}
let chart = false
function drawChart_bool() {
chart = !chart;
paused = !paused;
}
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 onload="generateBalls()" id="myCanvas" width="1225%" height="500" style="border:1px solid black; margin-top: 10px;"></canvas>
<p>
<input type="range" min="50" max="2050" value="300" step="100" id="temp">
<input type="button" onclick="drawChart_bool()">
<strong>[K]</strong> to toggle clearCanvas(); <br>
<strong>[P]</strong>: pause/unpause || <strong>[R]</strong>: [RESET]
</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>
<script src="gas.js"></script>
</html>
FUNCTION I USE TO DRAW CHART(objArray is an ascending ordered one, based on speeds)
function drawChart()
{
let index = 0
let cx = 10 , cy;
for(let i = 0; i < 59; i++) {
cy = canvas.height - 6;
if(probArray[i] != 0) {
n = 0;
while(n < probArray[i]) {
objArray[index + n].x = cx;
objArray[index + n].y = cy;
cy -= 12;
n++;
}
index += n;
}
cx += 20;
}
chart = !chart;
}
FUNCTION I USE TO GENERATE BALLS:
function generateBalls(T)
{
paused = false;
v = 50;
objArray = [];
for(let i = 0; i < 59; i++)
{ //each 50m/s, with dv = 50, until 2000m/s
//molecules number between v and v+50
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);
v += 50;
}
v = 50;
let l;
for(let i = 0; i < 59; i++)
{
let n = 0;
balls = 0;
while(n < probArray[i])
{
angolox = ((Math.random() * 360) * Math.PI) / 180; //converted in radians;
vel = Math.round((Math.random() * 50) + v) / 160;
if(vel * 160 < 400)
color = 'lightblue';
else if(vel * 160 > 800)
color = 'red';
else
color = 'orange';
l = objArray.length;
objArray[objArray.length] = new Ball(randomX(), randomY(), Math.cos(angolox) * vel, Math.sin(angolox) * vel, 5, color);
balls++;
n++;
}
v += 50;
}
}
Heartfelt thanks in advance for any answer,
Greg.🙏

Related

How to evenly spread circles gravitating towards a point?

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>

Why my code get stuck when I try to fill circle inside the circle?

I am trying the simple circle filling and it works. But when I try to fill the first circle with more circles, it hangs the program immediately. Here's my draw and and class code which fills the circles inside circles:
let krr=[];
function draw() {
background(0);
print(cir.length,krr.length)
if (cir.length<=100){
let temp=new c();
cir.push(temp);
}
else if (krr.length<100){
print(1)
fr=cir[0]
if (fr.r>50){
let re=new cirgain(fr.x,fr.y,(fr.r)/2)
krr.push(re);
}
}
for (let h of krr){
h.show();
}
for (let g of cir){
g.show();
g.grow();
}
}
class cirgain{
constructor(x,y,r){
this.smr=floor(r/3);
if (krr.length==0){
while (true){
this.x=random(x-r+1,x+r-1);
this.y=random(y-r+1,y+r-1)
if (this.x*this.x+this.y*this.y<r*r-2){
break
}
}
}
else{
let flag1=1
let count1=0
while (flag){
if (count1>=500){
count1=0;
this.smr--;
}
while (true){
this.x=random(x-r+1,x+r-1);
this.y=random(y-r+1,y+r-1);
if (this.x*this.x+this.y*this.y<r*r-2)
break
}
for (let i=0;i<krr.length;i++){
if (dist(krr[i].x,krr[i].y,this.x,this.y)<r+this.smr){
flag1=1
count1++;
break;
}
flag1=0;
}
}
}
this.ccc=createVector(random(255),random(100,255),random(100,255))
}
show(){
stroke(0);
noFill();
strokeWeight(3)
stroke(this.ccc.x,this.ccc.y,this.ccc.z);
circle(this.x,this.y,this.smr)
}
}
If the whole code (including setup and class c (which at first fills the space with circles)) is needed, let me know, I will edit to include it.
Edit: Okay, here is the whole code:
let cir = [];
let maxR;
let krr = [];
function setup() {
createCanvas(windowWidth, windowHeight);
maxR = width / 4;
if (height > width)
maxR = height / 4
colorMode(HSB);
angleMode(DEGREES);
}
function draw() {
background(0);
print(cir.length, krr.length)
if (cir.length <= 100) {
let temp = new c();
cir.push(temp);
} else if (krr.length < 100) {
print(1)
fr = cir[0]
if (fr.r > 50) {
let re = new cirgain(fr.x, fr.y, (fr.r) / 2)
krr.push(re);
}
}
for (let h of krr) {
h.show();
}
for (let g of cir) {
g.show();
g.grow();
}
}
class c {
constructor() {
this.tempr = 1
if (cir.length == 0) {
this.x = random(maxR + 1, width - maxR - 1);
this.y = random(maxR + 1, height - maxR - 1)
this.r = maxR;
} else {
let flag = 1
let count = 0
while (flag) {
if (count >= 500) {
count = 0;
maxR--;
}
this.x = random(maxR / 2 + 1, width - maxR / 2 - 1);
this.y = random(maxR / 2 + 1, height - maxR / 2 - 1);
this.r = maxR;
for (let i = 0; i < cir.length; i++) {
if (dist(cir[i].x, cir[i].y, this.x, this.y) < cir[i].r / 2 + this.r / 2 + 3) {
flag = 1
count++;
break;
}
flag = 0;
}
}
}
this.cc = createVector(random(255), random(100, 255), random(100, 255))
}
show() {
stroke(0);
noFill();
strokeWeight(3)
stroke(this.cc.x, this.cc.y, this.cc.z);
circle(this.x, this.y, this.tempr)
rectMode(CENTER);
}
grow() {
if (this.tempr <= this.r)
// this.tempr+=.5
this.tempr += this.r / 100
}
}
class cirgain {
constructor(x, y, r) {
this.smr = floor(r / 3);
if (krr.length == 0) {
while (true) {
this.x = random(x - r + 1, x + r - 1);
this.y = random(y - r + 1, y + r - 1)
if (this.x * this.x + this.y * this.y < r * r - 2) {
break
}
}
} else {
let flag1 = 1
let count1 = 0
while (flag) {
if (count1 >= 500) {
count1 = 0;
this.smr--;
}
while (true) {
this.x = random(x - r + 1, x + r - 1);
this.y = random(y - r + 1, y + r - 1);
if (this.x * this.x + this.y * this.y < r * r - 2)
break
}
for (let i = 0; i < krr.length; i++) {
if (dist(krr[i].x, krr[i].y, this.x, this.y) < r + this.smr) {
flag1 = 1
count1++;
break;
}
flag1 = 0;
}
}
}
this.ccc = createVector(random(255), random(100, 255), random(100, 255))
}
show() {
stroke(0);
noFill();
strokeWeight(3)
stroke(this.ccc.x, this.ccc.y, this.ccc.z);
circle(this.x, this.y, this.smr)
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
Your logic for finding viable x/y values randomly in the cirgain constructor seems broken. Several of your while loops seem like they never exit.
let cir = [];
let krr = [];
let maxR;
function setup() {
createCanvas(windowWidth, windowHeight);
maxR = width / 4;
if (height > width) {
maxR = height / 4;
}
colorMode(HSB);
angleMode(DEGREES);
}
function draw() {
background(0);
print(cir.length, krr.length);
if (cir.length <= 100) {
let temp = new c();
cir.push(temp);
} else if (krr.length < 100) {
print(1)
fr = cir[0]
if (fr.r > 50) {
let re = new cirgain(fr.x, fr.y, (fr.r) / 2)
krr.push(re);
}
}
for (let h of krr) {
h.show();
}
for (let g of cir) {
g.show();
g.grow();
}
}
class c {
constructor() {
this.tempr = 1
if (cir.length == 0) {
this.x = random(maxR + 1, width - maxR - 1);
this.y = random(maxR + 1, height - maxR - 1)
this.r = maxR;
} else {
let flag = 1
let count = 0
while (flag) {
if (count >= 500) {
count = 0;
maxR--;
}
this.x = random(maxR / 2 + 1, width - maxR / 2 - 1);
this.y = random(maxR / 2 + 1, height - maxR / 2 - 1);
this.r = maxR;
for (let i = 0; i < cir.length; i++) {
if (dist(cir[i].x, cir[i].y, this.x, this.y) < cir[i].r / 2 + this.r / 2 + 3) {
flag = 1
count++;
break;
}
flag = 0;
}
}
}
this.cc = createVector(random(255), random(100, 255), random(100, 255))
}
show() {
stroke(0);
noFill();
strokeWeight(3)
stroke(this.cc.x, this.cc.y, this.cc.z);
circle(this.x, this.y, this.tempr)
rectMode(CENTER);
}
grow() {
if (this.tempr <= this.r)
// this.tempr+=.5
this.tempr += this.r / 100
}
}
class cirgain {
constructor(x, y, r) {
this.smr = floor(r / 3);
if (krr.length == 0) {
let n = 0;
while (++n < 1000) {
this.x = random(x - r + 1, x + r - 1);
this.y = random(y - r + 1, y + r - 1);
if (this.x * this.x + this.y * this.y < r * r - 2) {
break;
}
}
if (n >= 1000) {
console.warn('1. Unable to find an x/y value that satisfied the constraint');
debugger;
}
} else {
let flag1 = 1;
let count1 = 0;
let n = 0;
while (flag1 && ++n < 10000) {
if (count1 >= 500) {
count1 = 0;
this.smr--;
}
let n2 = 0;
while (++n2 < 1000) {
this.x = random(x - r + 1, x + r - 1);
this.y = random(y - r + 1, y + r - 1);
if (this.x * this.x + this.y * this.y < r * r - 2) {
break;
}
}
if (n2 >= 1000) {
console.warn('2. Unable to find an x/y value that satisfied the constraint');
debugger;
}
for (let i = 0; i < krr.length; i++) {
if (dist(krr[i].x, krr[i].y, this.x, this.y) < r + this.smr) {
flag1 = 1
count1++;
break;
}
flag1 = 0;
}
}
if (n >= 10000) {
console.warn('3. Flag never set to false');
debugger;
}
}
this.ccc = createVector(random(255), random(100, 255), random(100, 255))
}
show() {
stroke(0);
noFill();
strokeWeight(3)
stroke(this.ccc.x, this.ccc.y, this.ccc.z);
circle(this.x, this.y, this.smr);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
Update
You seem to have an error in your logic checking if the randomly generated x/y values are acceptable:
// Instead of this, which checks if the square of the distance from the top left (0, 0) to the randomly generated center is less than the radius squared
if (this.x * this.x + this.y * this.y < r * r - 2) {
break;
}
// You probably meant to check if the distance from the center of the circle to the randomly generated position is less than the radius:
if (dist(x, y, this.x, this.y) < r - 2) {
break;
}
Additionally I cannot make heads or tails of this logic:
for (let i = 0; i < krr.length; i++) {
if (dist(krr[i].x, krr[i].y, this.x, this.y) < r + this.smr) {
flag1 = 1
count1++;
break;
}
flag1 = 0;
}
For each cirgain that you create you are check that for every existing cirgain the new one has a distance from the existing that is less than the containing circle's radius plus the current circle's diameter*, and if that is the case (which it almost always is!) you try to find a different random position, and only after 500 attempts you decrease the current circle's radius. I can hardly tell what your objective is, but there has to be a better way.
* You're using the default ellipse mode, which means the third argument to circle() is the diameter, which makes this code especially confusing

Why are my objects overlapping in my p5.js project?

I'm working on a project where I simulate physics with balls.
Here is the link to the p5 editor of the project.
My problem is the following, when I add a lot of ball (like 200), balls are stacking but some of them will eventually collapse and I don't know why.
Can somebody explain why it does this and how to solve the problem ?
Thanks.
Here is the code of the sketch.
document.oncontextmenu = function () {
return false;
}
let isFlushing = false;
let isBallDiameterRandom = false;
let displayInfos = true;
let displayWeight = false;
let clickOnce = false;
let FRAME_RATE = 60;
let SPEED_FLUSH = 3;
let Y_GROUND;
let lastFR;
let balls = [];
function setup() {
frameRate(FRAME_RATE);
createCanvas(window.innerWidth, window.innerHeight);
Y_GROUND = height / 20 * 19;
lastFR = FRAME_RATE;
}
function draw() {
background(255);
if (isFlushing) {
for (let i = 0; i < SPEED_FLUSH; i++) {
balls.pop();
}
if (balls.length === 0) {
isFlushing = false;
}
}
balls.forEach(ball => {
ball.collide();
ball.move();
ball.display(displayWeight);
ball.checkCollisions();
});
if (mouseIsPressed) {
let ballDiameter;
if (isBallDiameterRandom) {
ballDiameter = random(15, 101);
} else {
ballDiameter = 25;
}
if (canAddBall(mouseX, mouseY, ballDiameter)) {
isFlushing = false;
let newBall = new Ball(mouseX, mouseY, ballDiameter, balls);
if (mouseButton === LEFT && !clickOnce) {
balls.push(newBall);
clickOnce = true;
}
if (mouseButton === RIGHT) {
balls.push(newBall);
}
}
}
drawGround();
if (displayInfos) {
displayShortcuts();
displayFrameRate();
displayBallCount();
}
}
function mouseReleased() {
if (mouseButton === LEFT) {
clickOnce = false;
}
}
function keyPressed() {
if (keyCode === 32) {//SPACE
displayInfos = !displayInfos;
}
if (keyCode === 70) {//F
isFlushing = true;
}
if (keyCode === 71) {//G
isBallDiameterRandom = !isBallDiameterRandom;
}
if (keyCode === 72) {//H
displayWeight = !displayWeight;
}
}
function canAddBall(x, y, d) {
let isInScreen =
y + d / 2 < Y_GROUND &&
y - d / 2 > 0 &&
x + d / 2 < width &&
x - d / 2 > 0;
let isInAnotherBall = false;
for (let i = 0; i < balls.length; i++) {
let d = dist(x, y, balls[i].position.x, balls[i].position.y);
if (d < balls[i].w) {
isInAnotherBall = true;
break;
}
}
return isInScreen && !isInAnotherBall;
}
function drawGround() {
strokeWeight(0);
fill('rgba(200,200,200, 0.25)');
rect(0, height / 10 * 9, width, height / 10);
}
function displayFrameRate() {
if (frameCount % 30 === 0) {
lastFR = round(frameRate());
}
textSize(50);
fill(255, 0, 0);
let lastFRWidth = textWidth(lastFR);
text(lastFR, width - lastFRWidth - 25, 50);
textSize(10);
text('fps', width - 20, 50);
}
function displayBallCount() {
textSize(50);
fill(255, 0, 0);
text(balls.length, 10, 50);
let twBalls = textWidth(balls.length);
textSize(10);
text('balls', 15 + twBalls, 50);
}
function displayShortcuts() {
let hStart = 30;
let steps = 15;
let maxTW = 0;
let controlTexts = [
'LEFT CLICK : add 1 ball',
'RIGHT CLICK : add 1 ball continuously',
'SPACE : display infos',
'F : flush balls',
'G : set random ball diameter (' + isBallDiameterRandom + ')',
'H : display weight of balls (' + displayWeight + ')'
];
textSize(11);
fill(0);
for (let i = 0; i < controlTexts.length; i++) {
let currentTW = textWidth(controlTexts[i]);
if (currentTW > maxTW) {
maxTW = currentTW;
}
}
for (let i = 0; i < controlTexts.length; i++) {
text(controlTexts[i], width / 2 - maxTW / 2 + 5, hStart);
hStart += steps;
}
fill(200, 200, 200, 100);
rect(width / 2 - maxTW / 2,
hStart - (controlTexts.length + 1) * steps,
maxTW + steps,
(controlTexts.length + 1) * steps - steps / 2
);
}
Here is the code of the Ball class.
class Ball {
constructor(x, y, w, e) {
this.id = e.length;
this.w = w;
this.e = e;
this.progressiveWidth = 0;
this.rgb = [
floor(random(0, 256)),
floor(random(0, 256)),
floor(random(0, 256))
];
this.mass = w;
this.position = createVector(x + random(-1, 1), y);
this.velocity = createVector(0, 0);
this.acceleration = createVector(0, 0);
this.gravity = 0.2;
this.friction = 0.5;
}
collide() {
for (let i = this.id + 1; i < this.e.length; i++) {
let dx = this.e[i].position.x - this.position.x;
let dy = this.e[i].position.y - this.position.y;
let distance = sqrt(dx * dx + dy * dy);
let minDist = this.e[i].w / 2 + this.w / 2;
if (distance < minDist) {
let angle = atan2(dy, dx);
let targetX = this.position.x + cos(angle) * minDist;
let targetY = this.position.y + sin(angle) * minDist;
this.acceleration.set(
targetX - this.e[i].position.x,
targetY - this.e[i].position.y
);
this.velocity.sub(this.acceleration);
this.e[i].velocity.add(this.acceleration);
//TODO : Effets bizarre quand on empile les boules (chevauchement)
this.velocity.mult(this.friction);
}
}
}
move() {
this.velocity.add(createVector(0, this.gravity));
this.position.add(this.velocity);
}
display(displayMass) {
if (this.progressiveWidth < this.w) {
this.progressiveWidth += this.w / 10;
}
stroke(0);
strokeWeight(2);
fill(this.rgb[0], this.rgb[1], this.rgb[2], 100);
ellipse(this.position.x, this.position.y, this.progressiveWidth);
if (displayMass) {
strokeWeight(1);
textSize(10);
let tempTW = textWidth(int(this.w));
text(int(this.w), this.position.x - tempTW / 2, this.position.y + 4);
}
}
checkCollisions() {
if (this.position.x > width - this.w / 2) {
this.velocity.x *= -this.friction;
this.position.x = width - this.w / 2;
} else if (this.position.x < this.w / 2) {
this.velocity.x *= -this.friction;
this.position.x = this.w / 2;
}
if (this.position.y > Y_GROUND - this.w / 2) {
this.velocity.x -= this.velocity.x / 100;
this.velocity.y *= -this.friction;
this.position.y = Y_GROUND - this.w / 2;
} else if (this.position.y < this.w / 2) {
this.velocity.y *= -this.friction;
this.position.y = this.w / 2;
}
}
}
I see this overlapping happen when the sum of ball masses gets bigger than the elasticity of the balls. At least it seems so. I made a copy with a smaller pool so it doesn't take so much time to reproduce the problem.
In the following example, with 6 balls (a mass of 150 units) pressing on the base row, we see that the 13 balls in the base row overlap. The base row has a width of ca. 300 pixels, which is only enough space for 12 balls of diameter 25. I think this is showing the limitation of the model: the balls are displayed circular but indeed have an amount of elasticity that they should display deformed instead. It's hard to say how this can be fixed without implementing drawing complicated shapes. Maybe less friction?
BTW: great physics engine you built there :-)
Meanwhile I was able to make another screenshot with even fewer balls. The weight of three of them (eq. 75 units) is sufficient to create overlapping in the base row.
I doubled the size of the balls and changed the pool dimensions as to detedt that there is a more serious error in the engine. I see that the balls are pressed so heavily under pressure that they have not enough space for their "volume" (area). Either they have to implode or it's elastic counter force must have greater impact of the whole scene. If you pay close attention to the pendulum movements made by the balls at the bottom, which have the least space, you will see that they are very violent, but apparently have no chance of reaching the outside.
Could it be that your evaluation order
balls.forEach(ball => {
ball.collide();
ball.move();
ball.display(displayWeight);
ball.checkCollisions();
});
is not able to propagate the collisions in a realistic way?

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>

Creating Falling Snow using HTML 5 and JS

I visited the Stack Exchange Winter Bash website and I love the falling snow! My question is, how can I recreate a similar effect that looks as nice. I attempted to reverse engineer the code to see if I could figure it out but alas no luck there. The JS is over my head. I did a bit of googling and came across some examples but they were not as elegant as the SE site or did not look very good.
Can anyone provide some instructions on how to replicate what the SE Winter Bash site creates or a place where I might learn how to do this?
Edit: I would like to replicate the effect as close as possible, IE: falling snow with snowflakes, and being able to move the mouse and cause the snow to move or swirl with the mouse moments.
Great question, I actually wrote a snow plugin a while ago that I continually update see it in action. Also a link to the pure js source
I noticed you tagged the question html5 and canvas, however you can do it without using either, and just standard elements with images or different background colors.
Here's two really simple ones I put together just now for you to mess with. The key in my opinion is using sin to get the nice wavy effect as the flakes fall. The first one uses the canvas element, the 2nd one uses regular dom elements.
Since I'm absolutely addicted to canvas here's a canvas version that performs quite nicely in my opinion.
Canvas version
Full Screen
(function() {
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
window.requestAnimationFrame = requestAnimationFrame;
})();
var flakes = [],
canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
flakeCount = 200,
mX = -100,
mY = -100
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
function snow() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < flakeCount; i++) {
var flake = flakes[i],
x = mX,
y = mY,
minDist = 150,
x2 = flake.x,
y2 = flake.y;
var dist = Math.sqrt((x2 - x) * (x2 - x) + (y2 - y) * (y2 - y)),
dx = x2 - x,
dy = y2 - y;
if (dist < minDist) {
var force = minDist / (dist * dist),
xcomp = (x - x2) / dist,
ycomp = (y - y2) / dist,
deltaV = force / 2;
flake.velX -= deltaV * xcomp;
flake.velY -= deltaV * ycomp;
} else {
flake.velX *= .98;
if (flake.velY <= flake.speed) {
flake.velY = flake.speed
}
flake.velX += Math.cos(flake.step += .05) * flake.stepSize;
}
ctx.fillStyle = "rgba(255,255,255," + flake.opacity + ")";
flake.y += flake.velY;
flake.x += flake.velX;
if (flake.y >= canvas.height || flake.y <= 0) {
reset(flake);
}
if (flake.x >= canvas.width || flake.x <= 0) {
reset(flake);
}
ctx.beginPath();
ctx.arc(flake.x, flake.y, flake.size, 0, Math.PI * 2);
ctx.fill();
}
requestAnimationFrame(snow);
};
function reset(flake) {
flake.x = Math.floor(Math.random() * canvas.width);
flake.y = 0;
flake.size = (Math.random() * 3) + 2;
flake.speed = (Math.random() * 1) + 0.5;
flake.velY = flake.speed;
flake.velX = 0;
flake.opacity = (Math.random() * 0.5) + 0.3;
}
function init() {
for (var i = 0; i < flakeCount; i++) {
var x = Math.floor(Math.random() * canvas.width),
y = Math.floor(Math.random() * canvas.height),
size = (Math.random() * 3) + 2,
speed = (Math.random() * 1) + 0.5,
opacity = (Math.random() * 0.5) + 0.3;
flakes.push({
speed: speed,
velY: speed,
velX: 0,
x: x,
y: y,
size: size,
stepSize: (Math.random()) / 30,
step: 0,
angle: 180,
opacity: opacity
});
}
snow();
};
canvas.addEventListener("mousemove", function(e) {
mX = e.clientX,
mY = e.clientY
});
init();​
Standard element version
var flakes = [],
bodyHeight = getDocHeight(),
bodyWidth = document.body.offsetWidth;
function snow() {
for (var i = 0; i < 50; i++) {
var flake = flakes[i];
flake.y += flake.velY;
if (flake.y > bodyHeight - (flake.size + 6)) {
flake.y = 0;
}
flake.el.style.top = flake.y + 'px';
flake.el.style.left = ~~flake.x + 'px';
flake.step += flake.stepSize;
flake.velX = Math.cos(flake.step);
flake.x += flake.velX;
if (flake.x > bodyWidth - 40 || flake.x < 30) {
flake.y = 0;
}
}
setTimeout(snow, 10);
};
function init() {
var docFrag = document.createDocumentFragment();
for (var i = 0; i < 50; i++) {
var flake = document.createElement("div"),
x = Math.floor(Math.random() * bodyWidth),
y = Math.floor(Math.random() * bodyHeight),
size = (Math.random() * 5) + 2,
speed = (Math.random() * 1) + 0.5;
flake.style.width = size + 'px';
flake.style.height = size + 'px';
flake.style.background = "#fff";
flake.style.left = x + 'px';
flake.style.top = y;
flake.classList.add("flake");
flakes.push({
el: flake,
speed: speed,
velY: speed,
velX: 0,
x: x,
y: y,
size: 2,
stepSize: (Math.random() * 5) / 100,
step: 0
});
docFrag.appendChild(flake);
}
document.body.appendChild(docFrag);
snow();
};
document.addEventListener("mousemove", function(e) {
var x = e.clientX,
y = e.clientY,
minDist = 150;
for (var i = 0; i < flakes.length; i++) {
var x2 = flakes[i].x,
y2 = flakes[i].y;
var dist = Math.sqrt((x2 - x) * (x2 - x) + (y2 - y) * (y2 - y));
if (dist < minDist) {
rad = Math.atan2(y2, x2), angle = rad / Math.PI * 180;
flakes[i].velX = (x2 / dist) * 0.2;
flakes[i].velY = (y2 / dist) * 0.2;
flakes[i].x += flakes[i].velX;
flakes[i].y += flakes[i].velY;
} else {
flakes[i].velY *= 0.9;
flakes[i].velX
if (flakes[i].velY <= flakes[i].speed) {
flakes[i].velY = flakes[i].speed;
}
}
}
});
init();
function getDocHeight() {
return Math.max(
Math.max(document.body.scrollHeight, document.documentElement.scrollHeight), Math.max(document.body.offsetHeight, document.documentElement.offsetHeight), Math.max(document.body.clientHeight, document.documentElement.clientHeight));
}​
I've created a pure HTML 5 and js snowfall.
Check it out on my code pen here: https://codepen.io/onlintool24/pen/GRMOBVo
// Amount of Snowflakes
var snowMax = 35;
// Snowflake Colours
var snowColor = ["#DDD", "#EEE"];
// Snow Entity
var snowEntity = "•";
// Falling Velocity
var snowSpeed = 0.75;
// Minimum Flake Size
var snowMinSize = 8;
// Maximum Flake Size
var snowMaxSize = 24;
// Refresh Rate (in milliseconds)
var snowRefresh = 50;
// Additional Styles
var snowStyles = "cursor: default; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; -o-user-select: none; user-select: none;";
/*
// End of Configuration
// ----------------------------------------
// Do not modify the code below this line
*/
var snow = [],
pos = [],
coords = [],
lefr = [],
marginBottom,
marginRight;
function randomise(range) {
rand = Math.floor(range * Math.random());
return rand;
}
function initSnow() {
var snowSize = snowMaxSize - snowMinSize;
marginBottom = document.body.scrollHeight - 5;
marginRight = document.body.clientWidth - 15;
for (i = 0; i <= snowMax; i++) {
coords[i] = 0;
lefr[i] = Math.random() * 15;
pos[i] = 0.03 + Math.random() / 10;
snow[i] = document.getElementById("flake" + i);
snow[i].style.fontFamily = "inherit";
snow[i].size = randomise(snowSize) + snowMinSize;
snow[i].style.fontSize = snow[i].size + "px";
snow[i].style.color = snowColor[randomise(snowColor.length)];
snow[i].style.zIndex = 1000;
snow[i].sink = snowSpeed * snow[i].size / 5;
snow[i].posX = randomise(marginRight - snow[i].size);
snow[i].posY = randomise(2 * marginBottom - marginBottom - 2 * snow[i].size);
snow[i].style.left = snow[i].posX + "px";
snow[i].style.top = snow[i].posY + "px";
}
moveSnow();
}
function resize() {
marginBottom = document.body.scrollHeight - 5;
marginRight = document.body.clientWidth - 15;
}
function moveSnow() {
for (i = 0; i <= snowMax; i++) {
coords[i] += pos[i];
snow[i].posY += snow[i].sink;
snow[i].style.left = snow[i].posX + lefr[i] * Math.sin(coords[i]) + "px";
snow[i].style.top = snow[i].posY + "px";
if (snow[i].posY >= marginBottom - 2 * snow[i].size || parseInt(snow[i].style.left) > (marginRight - 3 * lefr[i])) {
snow[i].posX = randomise(marginRight - snow[i].size);
snow[i].posY = 0;
}
}
setTimeout("moveSnow()", snowRefresh);
}
for (i = 0; i <= snowMax; i++) {
document.write("<span id='flake" + i + "' style='" + snowStyles + "position:absolute;top:-" + snowMaxSize + "'>" + snowEntity + "</span>");
}
window.addEventListener('resize', resize);
window.addEventListener('load', initSnow);
body{
background: skyblue;
height:100%;
width:100%;
display:block;
position:relative;
}
<span id="flake0" style="cursor: default; user-select: none; position: absolute; font-family: inherit; font-size: 19px; color: rgb(221, 221, 221); z-index: 1000; left: 226px; top: 561px;">•</span>
The falling snow is simple: Create a canvas, create a bunch of snowflakes, draw them.
You can create a snowflake class like so:
function Snowflake() {
this.x = Math.random()*canvas.width;
this.y = -16;
this.speed = Math.random()*3+1;
this.direction = Math.random()*360;
this.maxSpeed = 4;
}
Or something like that. Then you have a timer that, each step, adjusts each snowflake's direction by a small amount, and then calculates its new X and Y by factoring in the Speed and Direction.
It's hard to explain, but actually quite basic.

Categories