The script is based on a simple ball collision simulation.
When a ball hit the wall, it triggers a vibration. The script uses the smartphone accelerometer to change the direction of the gravity force.
Press button to reset.
var canvas = document.querySelector('canvas');
var pen = canvas.getContext('2d');
const W = canvas.width;
const H = canvas.height;
var numBalls = 1;
var grav = [0, -0.1];
function Ball(x, y, dx, dy, r) {
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.r = r;
this.color = 'hsl(' + Math.random() * 360 + ',90%,50%)';
this.draw = function () {
pen.fillStyle = this.color;
pen.beginPath();
pen.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
pen.fill();
};
this.update = function () {
this.x += this.dx;
this.y += this.dy;
this.dx += grav[0];
this.dy -= grav[1];
if (this.x > W - this.r) {
this.x = W - this.r;
if (this.dx > 4)
navigator.vibrate(10);
// We made the phone vibrate because it had sufficent velocity
this.dx *= -0.5;
} else if (this.x < this.r) {
this.x = this.r;
if (this.dx < 4)
navigator.vibrate(10);
this.dx *= -0.5;
}
if (this.y > H - this.r) {
this.y = H - this.r;
if (this.dy > 4)
navigator.vibrate(10);
this.dy *= -0.5;
} else if (this.y < this.r) {
this.y = this.r + 1;
if (this.dy < 4)
navigator.vibrate(10);
this.dy *= -0.5;
}
this.draw();
};
}
var balls = [];
function reset() {
balls = [];
for (var i = 0; i < numBalls; i++) {
var x = Math.random() * W;
var y = Math.random() * H;
var r = Math.random() * 20 + 10;
balls.push(new Ball(x, y, Math.random() * 10 - 5, Math.random() * 10 - 5, r));
}
}
reset();
var output = document.querySelector('.output');
function handleOrientation(event) {
var x = event.beta; // In degree in the range [-180,180]
var y = event.gamma; // In degree in the range [-90,90]
output.innerHTML = "beta : " + x + "\n";
output.innerHTML += "gamma: " + y + "\n";
if (x > 90) { x = 90};
if (x < -90) { x = -90};
grav[1] = -x/90;
grav[0] = y/90;
}
window.addEventListener('deviceorientation', handleOrientation, false);
window.addEventListener('keydown', function (key) {
if (key.code === 'Space') {
reset();
}
});
var mouseDown = false;
var cooldown = 0;
var mouse = {
x: undefined,
y: undefined };
canvas.addEventListener('mousedown', function (event) {
mouseDown = true;
});
canvas.addEventListener('mouseup', function (event) {
mouseDown = false;
});
canvas.addEventListener('mousemove', function (event) {
mouse.x = event.x - 15;
mouse.y = event.y - 15;
});
function addball() {
var r = Math.random() * 20 + 10;
balls.push(new Ball(0, 0, Math.random() * 10 - 5, Math.random() * 10 - 5, r));
}
function animateb() {
pen.clearRect(0, 0, W, H);
cooldown++;
if (mouseDown && cooldown > 2) {
var r = Math.random() * 20 + 10;
balls.push(new Ball(mouse.x, mouse.y, Math.random() * 10 - 5, Math.random() * 10 - 5, r));
cooldown = 0;
}
for (var ball of balls) {
ball.update();
for (var ball2 of balls) {//Not the most efficient way to check every pair, but this is just a rough version
if (ball !== ball2) {
var collision = checkCollision(ball, ball2);
if (collision[0]) {
adjustPositions(ball, ball2, collision[1]);
resolveCollision(ball, ball2);
}
}
}
}
requestAnimationFrame(animateb);
}
animateb();
function checkCollision(ballA, ballB) {
var rSum = ballA.r + ballB.r;
var dx = ballB.x - ballA.x;
var dy = ballB.y - ballA.y;
return [rSum * rSum > dx * dx + dy * dy, rSum - Math.sqrt(dx * dx + dy * dy)];
}
function resolveCollision(ballA, ballB) {
var relVel = [ballB.dx - ballA.dx, ballB.dy - ballA.dy];
var norm = [ballB.x - ballA.x, ballB.y - ballA.y];
var mag = Math.sqrt(norm[0] * norm[0] + norm[1] * norm[1]);
norm = [norm[0] / mag, norm[1] / mag];
var velAlongNorm = relVel[0] * norm[0] + relVel[1] * norm[1];
if (velAlongNorm > 0)
return;
var bounce = 0.7;
var j = -(1 + bounce) * velAlongNorm;
j /= 1 / ballA.r + 1 / ballB.r;
var impulse = [j * norm[0], j * norm[1]];
ballA.dx -= 1 / ballA.r * impulse[0];
ballA.dy -= 1 / ballA.r * impulse[1];
ballB.dx += 1 / ballB.r * impulse[0];
ballB.dy += 1 / ballB.r * impulse[1];
vit = relVel[0] + relVel[1]
}
function adjustPositions(ballA, ballB, depth) {//Inefficient implementation for now
const percent = 0.2;
const slop = 0.01;
var correction = Math.max(depth - slop, 0) / (1 / ballA.r + 1 / ballB.r) * percent;
var norm = [ballB.x - ballA.x, ballB.y - ballA.y];
var mag = Math.sqrt(norm[0] * norm[0] + norm[1] * norm[1]);
norm = [norm[0] / mag, norm[1] / mag];
correction = [correction * norm[0], correction * norm[1]];
ballA.x -= 1 / ballA.r * correction[0];
ballA.y -= 1 / ballA.r * correction[1];
ballB.x += 1 / ballB.r * correction[0];
ballB.y += 1 / ballB.r * correction[1];
}