Created
June 25, 2012 07:08
-
-
Save DracoLi/2987114 to your computer and use it in GitHub Desktop.
Moving balls
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
canvasApp(); | |
function canvasApp() { | |
// Grab the canvas and set the context to 2d | |
var theCanvas = document.getElementById('canvasOne'); | |
var context = theCanvas.getContext("2d"); | |
// Variables | |
var numBalls = 200; // number of balls | |
var maxSize = 15; | |
var minSize = 5; | |
var maxSpeed = maxSize + 5; | |
var balls = new Array(); | |
var tempBall; | |
var tempX; | |
var tempY; | |
var tempSpeed; | |
var tempAngle; | |
var tempRadius; | |
var tempRadians; | |
var tempVelocityX; | |
var tempVelocityY; | |
// Find spots to place each ball so none start on top of each other | |
for (var i = 0; i < numBalls; i += 1) { | |
tempRadius = 5; | |
var placeOK = false; | |
while (!placeOK) { | |
tempX = tempRadius * 3 + (Math.floor(Math.random() * theCanvas.width) - tempRadius * 3); | |
tempY = tempRadius * 3 + (Math.floor(Math.random() * theCanvas.height) - tempRadius * 3); | |
tempSpeed = 4; | |
tempAngle = Math.floor(Math.random() * 360); | |
tempRadians = tempAngle * Math.PI/180; | |
tempVelocityX = Math.cos(tempRadians) * tempSpeed; | |
tempVelocityY = Math.sin(tempRadians) * tempSpeed; | |
tempBall = { | |
x: tempX, | |
y: tempY, | |
nextX: tempX, | |
nextY: tempY, | |
radius: tempRadius, | |
speed: tempSpeed, | |
angle: tempAngle, | |
velocityX: tempVelocityX, | |
velocityY: tempVelocityY, | |
mass: tempRadius | |
}; | |
placeOK = canStartHere(tempBall); | |
} | |
balls.push(tempBall); | |
} | |
// Drawing interval | |
setInterval(drawScreen, 33); | |
// Functions | |
// Returns true if a ball can start at given location, otherwise returns false | |
function canStartHere(ball) { | |
var retVal = true; | |
for (var i = 0; i < balls.length; i += 1) { | |
if (hitTestCircle(ball, balls[i])) { | |
retVal = false; | |
} | |
} | |
return retVal; | |
} | |
// Circle collision test to see if two balls are touching | |
// Uses nextX and nextY to test for collision before it occurs | |
function hitTestCircle(ball1, ball2) { | |
var retVal = false; | |
var dx = ball1.nextX - ball2.nextX; | |
var dy = ball1.nextY - ball2.nextY; | |
var distance = (dx * dx + dy * dy); | |
if (distance <= (ball1.radius + ball2.radius) * (ball1.radius + ball2.radius) ) { | |
retVal = true; | |
} | |
return retVal; | |
} | |
// Loops through all the balls in the balls array and updates the nextX and nextY properties | |
// with current x and y velocities for each ball | |
function update() { | |
for (var i = 0; i < balls.length; i += 1) { | |
ball = balls[i]; | |
ball.nextX = (ball.x += ball.velocityX); | |
ball.nextY = (ball.y += ball.velocityY); | |
} | |
} | |
// We track balls by their center, so we test for all collision by adding or subtracting | |
// each ball's radius before testing for wall collision | |
function testWalls() { | |
var ball; | |
var testBall; | |
for (var i = 0; i < balls.length; i += 1) { | |
ball = balls[i]; | |
if (ball.nextX + ball.radius > theCanvas.width) { // right wall | |
ball.velocityX = ball.velocityX * (-1); | |
ball.nextX = theCanvas.width - ball.radius; | |
} else if (ball.nextX - ball.radius < 0) { // top wall | |
ball.velocityX = ball.velocityX * (-1); | |
ball.nextX = ball.radius; | |
} else if (ball.nextY + ball.radius > theCanvas.height) { // bottom wall | |
ball.velocityY = ball.velocityY * (-1); | |
ball.nextY = theCanvas.height - ball.radius; | |
} else if (ball.nextY - ball.radius < 0) { // left wall | |
ball.velocityY = ball.velocityY * (-1); | |
ball.nextY = ball.radius; | |
} | |
} | |
} | |
// Tests whether any balls have hit each other. | |
// Uses two next loops to iterate through the balls array and test each ball against every other ball. | |
function collide() { | |
var ball; | |
var testBall; | |
for (var i = 0; i < balls.length; i += 1) { | |
ball = balls[i]; | |
for (var j = i + 1; j < balls.length; j += 1) { | |
testBall = balls[j]; | |
if (hitTestCircle(ball, testBall)) { | |
collideBalls(ball, testBall); | |
} | |
} | |
} | |
} | |
// Updates properties of colliding balls so they appear to bounce off each other. | |
// Uses nextX and nextY properties because we don't want to change where they are at the moment. | |
function collideBalls(ball1, ball2) { | |
var dx = ball1.nextX - ball2.nextX; | |
var dy = ball1.nextY - ball2.nextY; | |
var collisionAngle = Math.atan2(dy, dx); | |
// Get velocities of each ball before collision | |
var speed1 = Math.sqrt(ball1.velocityX * ball1.velocityX + ball1.velocityY * ball1.velocityY); | |
var speed2 = Math.sqrt(ball2.velocityX * ball2.velocityX + ball2.velocityY * ball2.velocityY); | |
// Get angles (in radians) for each ball, given current velocities | |
var direction1 = Math.atan2(ball1.velocityY, ball1.velocityX); | |
var direction2 = Math.atan2(ball2.velocityY, ball2.velocityX); | |
// Rotate velocity vectors so we can plug into equation for conservation of momentum | |
var rotatedVelocityX1 = speed1 * Math.cos(direction1 - collisionAngle); | |
var rotatedVelocityY1 = speed1 * Math.sin(direction1 - collisionAngle); | |
var rotatedVelocityX2 = speed2 * Math.cos(direction2 - collisionAngle); | |
var rotatedVelocityY2 = speed2 * Math.sin(direction2 - collisionAngle); | |
// Update actual velocities using conservation of momentum | |
/* Uses the following formulas: | |
velocity1 = ((mass1 - mass2) * velocity1 + 2*mass2 * velocity2) / (mass1 + mass2) | |
velocity2 = ((mass2 - mass1) * velocity2 + 2*mass1 * velocity1) / (mass1 + mass2) | |
*/ | |
var finalVelocityX1 = ((ball1.mass - ball2.mass) * rotatedVelocityX1 + (ball2.mass + ball2.mass) * rotatedVelocityX2) / (ball1.mass + ball2.mass); | |
var finalVelocityX2 = ((ball1.mass + ball1.mass) * rotatedVelocityX1 + (ball2.mass - ball1.mass) * rotatedVelocityX2) / (ball1.mass + ball2.mass); | |
// Y velocities remain constant | |
var finalVelocityY1 = rotatedVelocityY1; | |
var finalVelocityY2 = rotatedVelocityY2; | |
// Rotate angles back again so the collision angle is preserved | |
ball1.velocityX = Math.cos(collisionAngle) * finalVelocityX1 + Math.cos(collisionAngle + Math.PI/2) * finalVelocityY1; | |
ball1.velocityY = Math.sin(collisionAngle) * finalVelocityX1 + Math.sin(collisionAngle + Math.PI/2) * finalVelocityY1; | |
ball2.velocityX = Math.cos(collisionAngle) * finalVelocityX2 + Math.cos(collisionAngle + Math.PI/2) * finalVelocityY2; | |
ball2.velocityY = Math.sin(collisionAngle) * finalVelocityX2 + Math.sin(collisionAngle + Math.PI/2) * finalVelocityY2; | |
// Update nextX and nextY for both balls so we can use them in render() or another collision | |
ball1.nextX += ball1.velocityX; | |
ball1.nextY += ball1.velocityY; | |
ball2.nextX += ball2.velocityX; | |
ball2.nextY += ball2.velocityY; | |
} | |
// Draws and updates each ball | |
function render() { | |
var ball; | |
context.fillStyle = "#000000"; | |
for (var i = 0; i < balls.length; i += 1) { | |
ball = balls[i]; | |
ball.x = ball.nextX; | |
ball.y = ball.nextY; | |
context.beginPath(); | |
context.arc(ball.x, ball.y, ball.radius, 0, Math.PI *2, true); | |
context.closePath(); | |
context.fill(); | |
} | |
} | |
// Draws/updates the screen | |
function drawScreen() { | |
// Reset canvas | |
context.fillStyle = "#EEEEEE"; | |
context.fillRect(0, 0, theCanvas.width, theCanvas.height); | |
// Outside border | |
context.strokeStyle = "#000000"; | |
context.strokeRect(1, 1, theCanvas.width - 2, theCanvas.height - 2); | |
update(); | |
testWalls(); | |
collide(); | |
render(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment