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>
Related
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>
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.
I am creating an animation using java script & canvas. I am using a fiddle as a reference, currently object are generating randomly & falling from top right corner to bottom left corner which is okay. but the issue is speed the objects are generating & falling in high speed. I want to make animation flow little slow & smooth.
I am new canvas programming, any help will be very useful.
(function() {
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
window.requestAnimationFrame = requestAnimationFrame;
})();
var particleArr = [],
canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
flakeCount = 700,
mouseX = -100,
mouseY = -100,
xMultiplier = 0.015
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
function getRandomColor() {
// Random Color Generate
const colorArr = ["rgba(215,88,69, 1)", "rgba(117, 161, 199, 1)"]; // Blue & Orange Color
const randomColor = colorArr[Math.floor(Math.random() * colorArr.length)];
return randomColor;
}
function flow() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < flakeCount; i++) {
var flake = particleArr[i],
x = mouseX,
y = mouseY,
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 = getRandomColor();
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(flow);
};
function reset(flake) {
let temp = (Math.random() * 1) + 0.5;
flake.x = canvas.width;
flake.y = 50;
flake.size = (Math.random() * 3) + 5;
flake.speed = (Math.random() * 7) + 0.5;
flake.velY = flake.speed;
flake.velX = -xMultiplier * canvas.width * temp;
// flake.opacity = (Math.random() * 0.5) + 0.3;
}
function init() {
for (var i = 0; i < flakeCount; i++) {
var x = canvas.width,
y = 50,
size = (Math.random() * 3) + 5,
// speed = (Math.random() * 1) + 0.5;
speed = 0;
// opacity = (Math.random() * 0.5) + 0.3;
particleArr.push({
speed: speed,
velY: speed,
velX: -xMultiplier * canvas.width * speed,
x: x,
y: y,
size: size,
stepSize: (Math.random()) / 30,
step: 0,
angle: 360
// opacity: opacity
});
}
flow();
};
canvas.addEventListener("mousemove", function(e) {
mouseX = e.clientX,
mouseY = e.clientY
});
window.addEventListener('resize', onWindowResize, false);
function onWindowResize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
init();
canvas {
background-color: #000000 !important;
}
body {
margin: 0;
overflow: hidden;
}
<canvas id="canvas"></canvas>
Could not work out what it was you wanted to slow down as there are several FX and interactions in the code.
I rewrote from the ground up as your code is a little old school.
Rather than play with the constants you had OI added the global variable rate (at the ver top of the source) is used to control the rate at which the animation plays, including the user interaction.
I have added two button to slow or speed up the animation.
Hope this helps :)
var rate = 1;
slower.addEventListener("click", () => rate *= 1 / 1.2);
faster.addEventListener("click", () => rate *= 1.2);
const flakes = [], flakeCount = 700, xMultiplier = 0.015;
const minDist = 150, minDistSqr = minDist * minDist;
const colors = ["#F99","#F83","#AF9","#ED9","#AC8","#FA9" ];
const ctx = canvas.getContext("2d");
const mouse = {x: -100, y: -100};
const randPick = (arr, len = arr.length) => arr[Math.random() * len | 0];
Math.rand = (min, range) => Math.random() * range + min;
function Flake() {
this.reset();
this.stepSize = Math.random() / 30;
this.step = 0;
}
Flake.prototype = {
reset() {
this.x = canvas.width;
this.y = 50;
this.size = Math.rand(5, 3);
this.speed = Math.rand(0.5, 7);
this.velY = this.speed;
this.velX = -xMultiplier * canvas.width * Math.rand(0.5, 1);
this.col = randPick(colors);
},
draw() {
ctx.fillStyle = this.col;
const s = this.size, sh = -s / 2;
ctx.fillRect(this.x + sh, this.y + sh, s, s);
},
update(w, h) {
const f = this;
const dx = f.x - mouse.x;
const dy = f.y - mouse.y;
const distSqr = dx * dx + dy * dy;
if (distSqr < minDistSqr) {
const deltaV = 2 * minDist * rate / distSqr ** 1.5;
f.velX -= deltaV * dx;
f.velY -= deltaV * dy;
} else {
f.velX -= 0.1 * rate * f.velX;
if (f.velY <= f.speed ) { f.velY = f.speed }
f.velX += Math.cos(f.step += 0.05 * rate) * f.stepSize * rate;
}
f.y += f.velY * rate;
f.x += f.velX * rate;
if (f.y >= h || f.y <= 0 || f.x >= w || f.x <= 0) { this.reset() }
else { this.draw() }
}
};
init();
mainLoop();
function mainLoop() {
if (innerWidth !== canvas.width || innerHeight !== canvas.height) { resize() }
else { ctx.clearRect(0, 0, canvas.width, canvas.height) }
for (const f of flakes) { f.update(canvas.width, canvas.height) }
requestAnimationFrame(mainLoop);
}
function init() {
var i = flakeCount;
while (i--) { flakes.push(new Flake()) }
}
canvas.addEventListener("mousemove", e => { mouse.x = e.clientX; mouse.y = e.clientY });
function resize() { canvas.width = innerWidth; canvas.height = innerHeight }
canvas {
background-color: #000;
}
body {
margin: 0;
}
.buttons {
position: absolute;
top: 12px;
left: 12px;
color: #000;
background-color: #AAA;
}
.buttons > div {
margin: 3px;
padding: 3px;
background-color: #EEE;
cursor: pointer;
}
.buttons > div:hover {
background-color: #DEF;
}
<canvas id="canvas"></canvas>
<div class = "buttons">
<div id="slower">Slower</div>
<div id="faster">Faster</div>
</div>
That requestAnimationFrame() function that calls flow() every frame is designed to run as fast as possible for whoever's computer it's running on. I wouldn't mess with your actual render loop.
Try messing with the flake.speed or the xMultiplier. Those are two of the main variables affecting the speed of your particles. You can see how each time through the flow() loop you're adjusting each particle's position based on their velocity properties and position. Then finally rendering the arc with ctx.arc(flake.x, flake.y, flake.size, 0, Math.PI * 2);
So any variable passed to ctx.arc() affects the particle's position. And many of those variables are recalculated each time through the loop.
I'm no expert here, but maybe try fiddling with your variables.
https://codepen.io/nitwit/pen/XWXJNaJ
https://jsfiddle.net/z6r8h5de/
if the issue is there are too many flakes on the screen, turn the count down from 700.
flakeCount = 100,
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.🙏
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.