<html>
<head>
<meta charset="utf-8">
<title>Balls bouncing in a 2D frustum</title>
</head>
<body onload="start();">
<canvas id=canvas width=500 height=500 style="border:1px solid #000000;">
</body>
</html>
function start() {
var canvas = document.querySelector("#canvas");
var context = canvas.getContext("2d");
var frustum = new Frustum(250, 250, 150, 200, 100);
var balls = [];
var N = 1;
for (var i = 0; i < N; i++) {
var theta = 2*i*Math.PI/N; // for the direction of speed
var ball = new Ball(
frustum.x + 1/2* frustum.L2*(0.5 - Math.random()),
frustum.y + 1/2* frustum.height*(0.5 - Math.random()),
Math.cos(theta),
Math.sin(theta)
);
balls.push(ball);
}
(function animate() {
requestAnimationFrame(animate);
context.clearRect(0, 0, canvas.width, canvas.height);
frustum.draw(context);
var dt = 1;
for (var i = 0; balls[i]; i++) {
balls[i].draw(context);
balls[i].move(dt);
frustum.move(dt);
frustum.makeBallBounce(balls[i]);
}
})();
}
function Ball(x, y, vx, vy) {
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
this.color = [
'#3A5BCD', '#EF2B36', '#FFC636', '#02A817'
][Math.floor(Math.random()*4)];
}
Ball.prototype.mass = 1;
Ball.prototype.radius = 3;
Ball.prototype.move = function (dt) {
this.x += this.vx * dt;
this.y += this.vy * dt;
};
Ball.prototype.draw = function (ctx) {
ctx.save();
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, 2*Math.PI);
ctx.fill();
ctx.restore();
};
function Frustum(x, y, height, L1, L2) {
this.mass = 1.5;
this.x = x;
this.y = y;
this.vx = 0;
this.vy = 0;
this.height = height;
this.L1 = L1;
this.L2 = L2;
this.move = function ( dt ) {
this.x += this.vx * dt;
this.y += this.vy * dt;
};
this.draw = function (ctx) {
var x = this.x, y = this.y,
height = this.height,
L1 = this.L1, L2 = this.L2;
ctx.save();
ctx.strokeStyle = "black";
ctx.translate(x, y);
ctx.beginPath();
ctx.moveTo(-L1/2,-height/2);
ctx.lineTo(+L1/2,-height/2);
ctx.lineTo(+L2/2,+height/2);
ctx.lineTo(-L2/2,+height/2);
ctx.lineTo(-L1/2,-height/2);
ctx.stroke();
ctx.restore();
};
this.makeBallBounce = function (ball) {
var totalMass = this.mass + ball.mass;
var theta = Math.atan((this.L1 - this.L2) / 2 / this.height);
var c = Math.cos(theta), s = Math.sin(theta);
var x, y, X, Y,
ballVx, ballVy, thisVx, thisVy,
ballVX, ballVY, thisVX, thisVY;
x = ball.x - this.x + this.L1/2;
y = ball.y - this.y + this.height/2;
// See https://en.wikipedia.org/wiki/Elastic_collision
if (y < 0 || y > this.height) {
ballVy = ball.vy;
thisVy = this.vy;
ball.vy =
(ball.vy*(ball.mass - this.mass) + 2*this.mass*thisVy)/totalMass;
this.vy =
(thisVy*(this.mass - ball.mass) + 2*ball.mass*ballVy)/totalMass;
if (y < 0) { ball.y = this.y - this.height/2; }
if (y > this.height) { ball.y = this.y + this.height/2; }
}
X = c*x - s*y; Y = c*y + s*x;
ballVX = c*ball.vx - s*ball.vy;
ballVY = c*ball.vy + s*ball.vx;
thisVX = c*this.vx - s*this.vy;
thisVY = c*this.vy + s*this.vx;
if (X < 0) {
ball.vX =
(ballVX*(ball.mass - this.mass) + 2*this.mass*thisVX)/totalMass;
this.vX =
(thisVX*(this.mass - ball.mass) + 2*ball.mass*ballVX)/totalMass;
ball.vx = c*ball.vX + s*ballVY;
ball.vy = c*ballVY - s*ball.vX;
this.vx = c*this.vX + s*thisVY;
this.vy = c*thisVY - s*this.vX;
X = 0;
x = c*X + s*Y; y = c*Y - s*X;
ball.x = x + this.x - this.L1/2;
ball.y = y + this.y - this.height/2;
}
x = ball.x - this.x - this.L1/2;
y = ball.y - this.y + this.height/2;
c = Math.cos(-theta); s = Math.sin(-theta);
X = c*x - s*y; Y = c*y + s*x;
ballVX = c*ball.vx - s*ball.vy;
ballVY = c*ball.vy + s*ball.vx;
thisVX = c*this.vx - s*this.vy;
thisVY = c*this.vy + s*this.vx;
if (X > 0) {
ball.vX =
(ballVX*(ball.mass - this.mass) + 2*this.mass*thisVX)/totalMass;
this.vX =
(thisVX*(this.mass - ball.mass) + 2*ball.mass*ballVX)/totalMass;
ball.vx = c*ball.vX + s*ballVY;
ball.vy = c*ballVY - s*ball.vX;
this.vx = c*this.vX + s*thisVY;
this.vy = c*thisVY - s*this.vX;
X = 0;
x = c*X + s*Y; y = c*Y - s*X;
ball.x = x + this.x + this.L1/2;
ball.y = y + this.y - this.height/2;
}
};
}
Output
This bin was created anonymously and its free preview time has expired (learn why). — Get a free unrestricted account
Dismiss xKeyboard Shortcuts
Shortcut | Action |
---|---|
ctrl + [num] | Toggle nth panel |
ctrl + 0 | Close focused panel |
ctrl + enter | Re-render output. If console visible: run JS in console |
Ctrl + l | Clear the console |
ctrl + / | Toggle comment on selected lines |
ctrl + ] | Indents selected lines |
ctrl + [ | Unindents selected lines |
tab | Code complete & Emmet expand |
ctrl + shift + L | Beautify code in active panel |
ctrl + s | Save & lock current Bin from further changes |
ctrl + shift + s | Open the share options |
ctrl + y | Archive Bin |
Complete list of JS Bin shortcuts |
JS Bin URLs
URL | Action |
---|---|
/ | Show the full rendered output. This content will update in real time as it's updated from the /edit url. |
/edit | Edit the current bin |
/watch | Follow a Code Casting session |
/embed | Create an embeddable version of the bin |
/latest | Load the very latest bin (/latest goes in place of the revision) |
/[username]/last | View the last edited bin for this user |
/[username]/last/edit | Edit the last edited bin for this user |
/[username]/last/watch | Follow the Code Casting session for the latest bin for this user |
/quiet | Remove analytics and edit button from rendered output |
.js | Load only the JavaScript for a bin |
.css | Load only the CSS for a bin |
Except for username prefixed urls, the url may start with http://jsbin.com/abc and the url fragments can be added to the url to view it differently. |