# Edit in JSFiddle

```(function (\$, window) {

function Vector2d(x, y) {
this.x = x;
this.y = y;
}

Vector2d.prototype.dot = function(vector) {
return this.x * vector.x + this.y * vector.y;
}

return new Vector2d(this.x + vector.x, this.y + vector.y);
}

Vector2d.prototype.subtract = function(vector) {
return new Vector2d(this.x - vector.x, this.y - vector.y);
}

Vector2d.prototype.length = function() {
return Math.sqrt(this.x*this.x + this.y*this.y);
}

Vector2d.prototype.multiply = function(scaleFactor) {
return new Vector2d(this.x * scaleFactor, this.y * scaleFactor);
}

Vector2d.prototype.normalize = function() {
var len = this.length();
if (len == 0) {
this.x = 0;
this.y = 0;
return this;
} else {
this.x = this.x / len;
this.y = this.y / len;
return this;
}
}

function Ball(pos, vel, radius, mass, color) {
this.pos = pos;
this.vel = vel;
this.color = color;
this.mass = mass;
}

Ball.prototype.render = function(ctx) {
ctx.fillStyle = this.color;
ctx.strokeStyle = this.color;
ctx.beginPath();
ctx.arc(this.pos.x, this.pos.y, this.radius, 0, Math.PI*2, false);
ctx.fill();
}

Ball.prototype.resolveCollision = function(ball) {
var delta = this.pos.subtract(ball.pos);
var dist2 = delta.dot(delta);

if (dist2 > r*r) { return; /* not colliding */ }

var d = delta.length();
var mtd;

if (d != 0) {
} else { // special case, balls are exactly on top of eachother
}

// resolve intersection
var im1 = 1 / this.mass;
var im2 = 1 / ball.mass;

// push/pull them apart
this.pos = this.pos.add(mtd.multiply(im1 / (im1 + im2)));
ball.pos = ball.pos.subtract(mtd.multiply( im2 / (im1 + im2)));

// impact speed
var v = this.vel.subtract(ball.vel);
var vn = v.dot(mtd.normalize());

// sphere intersecting but moving away from each other already
if (vn > 0.0) { return; }

var i = (-(1.0 + Bouncer.RESTITUTION) * vn) / (im1 + im2);
var impulse = mtd.multiply(i);

ball.vel = ball.vel.subtract(impulse.multiply(im2));
}

function Bouncer(ctx) {
this.ctx = ctx;
this.balls = [];
this.now = undefined;
this.last = undefined;
this.launchBall = undefined;
}

Bouncer.GRAVITY = 700; /* pixels per second */
Bouncer.RESTITUTION = 0.75;

Bouncer.prototype.init = function() {
var self = this;

\$(self.ctx.canvas).bind('mousedown', function(e) {
var x = e.pageX-\$(self.ctx.canvas).offset().left;
var y = e.pageY-\$(self.ctx.canvas).offset().top;
self.launchBall = new Ball(new Vector2d(x, y), new Vector2d(x, y), 10, 1, "#00FF00");
});

\$(document).bind('mouseup', function(e) {
if (self.launchBall) {

var x = e.pageX-\$(self.ctx.canvas).offset().left;
var y = e.pageY-\$(self.ctx.canvas).offset().top;

new Vector2d((self.launchBall.vel.x - self.launchBall.pos.x) * 5,
(self.launchBall.vel.y - self.launchBall.pos.y) * 5),
1,
"#FF0000");

self.launchBall = undefined;
}
});

\$(document).bind('mousemove', function(e) {
if (self.launchBall) {
var x1 = self.launchBall.pos.x;
var y1 = self.launchBall.pos.y;
var x2 = e.pageX-\$('#canvas').offset().left;
var y2 = e.pageY-\$('#canvas').offset().top;
var dx = Math.abs(x2 - x1);
var dy = Math.abs(y2 - y1);

//console.log("x1:" + x1 + " y1:" + y1 + " xV2:" + self.launchBall.xVel + " yV2:" + self.launchBall.yVel);
//console.log("x2:" + x2 + " y2: " + y2 + " dx:" + dx + " dy:" + dy);

if ((x2 - x1) < 0) {
self.launchBall.vel.x = x1 + dx;
} else {
self.launchBall.vel.x = x1 - dx;
}

if ((y2 - y1) < 0) {
self.launchBall.vel.y = y1 + dy;
} else {
self.launchBall.vel.y = y1 - dy;
}
}
});

this.last = new Date();
this.step();
}

Bouncer.prototype.render = function() {
// Modify canvas dimensions
this.ctx.canvas.width = window.innerWidth - 50;
this.ctx.canvas.height = window.innerHeight - 50;

// Clear
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);

// Render balls
for(var i = 0; i < this.balls.length; i++) {
this.balls[i].render(this.ctx);
}

// Render launching ball
if (this.launchBall) {
this.launchBall.render(this.ctx);
this.ctx.moveTo(this.launchBall.pos.x, this.launchBall.pos.y);
this.ctx.lineTo(this.launchBall.vel.x, this.launchBall.vel.y);
this.ctx.strokeStyle = "#FF6633";
this.ctx.stroke();
}
}

Bouncer.prototype.step = function() {
this.now = + new Date;
var dt = (this.now - this.last) / 1000;
this.last = this.now;

this.tick(dt);
this.render();

var self = this;
setTimeout(function() { self.step() }, 0);
}

Bouncer.prototype.tick = function(dt) {
for(var i = 0; i < this.balls.length; i++) {
this.balls[i].vel.y += Bouncer.GRAVITY * dt;
this.balls[i].pos.y += this.balls[i].vel.y * dt;
this.balls[i].pos.x += this.balls[i].vel.x * dt;
}

this.checkWallCollisions();
this.checkBallCollisions();
}

Bouncer.prototype.checkBallCollisions = function() {
for(var i = 0; i < this.balls.length; i++) {
for(var j = i; j < this.balls.length; j++) {
this.balls[i].resolveCollision(this.balls[j]);
}
}
}

Bouncer.prototype.checkWallCollisions = function() {
for(var i = 0; i < this.balls.length; i++) {
if ((this.balls[i].pos.x- this.balls[i].radius) < 0) {
this.balls[i].vel.x = -this.balls[i].vel.x * Bouncer.RESTITUTION;
}

if ((this.balls[i].pos.x + this.balls[i].radius) > this.ctx.canvas.width) {
this.balls[i].vel.x = -this.balls[i].vel.x * Bouncer.RESTITUTION;
}

if ((this.balls[i].pos.y - this.balls[i].radius) < 0) {
this.balls[i].vel.y = -this.balls[i].vel.y * Bouncer.RESTITUTION;
}

if ((this.balls[i].pos.y + this.balls[i].radius) > this.ctx.canvas.height) {
this.balls[i].vel.y = -this.balls[i].vel.y * Bouncer.RESTITUTION;
}
}
}

this.balls.push(new Ball(pos, vel, radius, mass, color));
}

\$(function() {
var canvas = \$('#canvas')[0];
var ctx = canvas.getContext('2d');

var bouncer = new Bouncer(ctx, canvas);
bouncer.init();
});

}(jQuery, window));```
```<canvas id="canvas" width="600" height="600" />
```