Created
August 2, 2013 23:18
-
-
Save BonsaiDen/6144232 to your computer and use it in GitHub Desktop.
Really simple AABB only physics engine.
This file contains 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
(function(exports) { | |
// Vector Class ----------------------------------------------------------- | |
// ------------------------------------------------------------------------ | |
function Vector2(x, y) { | |
this.x = x; | |
this.y = y; | |
} | |
Vector2.prototype = { | |
add: function(v) { | |
return new Vector2(this.x + v.x, this.y + v.y); | |
}, | |
sub: function(v) { | |
return new Vector2(this.x - v.x, this.y - v.y); | |
}, | |
dot: function(v) { | |
return this.x * v.x + this.y * v.y; | |
}, | |
cross: function(v) { | |
return this.x * v.y - this.y * v.x; | |
}, | |
div: function(s) { | |
this.x /= s; | |
this.y /= s; | |
return this; | |
}, | |
mul: function(s) { | |
this.x *= s; | |
this.y *= s; | |
return this; | |
}, | |
normalize: function() { | |
var len = this.length(); | |
if (this.length > 0.0001) { | |
var invLen = 1.0 / len; | |
this.x *= invLen; | |
this.y *= invLen; | |
} | |
}, | |
// Length | |
lengthSqr: function() { | |
return this.x * this.x + this.y * this.y; | |
}, | |
length: function() { | |
return Math.sqrt(this.x * this.x + this.y * this.y); | |
} | |
}; | |
// AABB implementation ---------------------------------------------------- | |
// ------------------------------------------------------------------------ | |
function AABB(x, y, w, h, mass) { | |
mass = mass !== undefined ? mass : 0; | |
// Properties | |
this.id = ++AABB.id; | |
// The is actually the inverse mass. | |
// The value 0 is used as a placeholder for infinite mass | |
this.im = mass === 0 ? 0 : 1 / mass; | |
// How "bouncy" this box is. The higher the value, the more the box will | |
// bounce of in case of a collision | |
this.restitution = 0.1; | |
this.staticFriction = 1; | |
this.dynamicFriction = 0.3; | |
this.position = new Vector2(x, y); | |
this.velocity = new Vector2(0, 0); | |
this.size = new Vector2(w, h); | |
// Internal, temporary values only used during the collision phase | |
this.force = new Vector2(0, 0); | |
this.min = new Vector2(0, 0); | |
this.max = new Vector2(0, 0); | |
} | |
AABB.prototype = { | |
/** | |
* Quick check to see if all of the axis of two AABBs are overlapping. | |
*/ | |
isOverlapping: function(other) { | |
if (this.max.x < other.min.x || this.min.x > other.max.x) { | |
return false; | |
} else if (this.max.y < other.min.y || this.min.y > other.max.y) { | |
return false; | |
} else { | |
return true; | |
} | |
}, | |
/** | |
* Update the boundaries of the AABB | |
*/ | |
updateBounds: function() { | |
this.min.x = this.position.x - this.size.x; | |
this.max.x = this.position.x + this.size.x; | |
this.min.y = this.position.y - this.size.y; | |
this.max.y = this.position.y + this.size.y; | |
}, | |
/** | |
* Integrate force and gravity into the AABB's velocity. | |
*/ | |
integrateForces: function(gravity) { | |
if (this.im !== 0) { | |
this.velocity.x += (this.force.x * this.im + gravity.x) / 2; | |
this.velocity.y += (this.force.y * this.im + gravity.y) / 2; | |
} | |
}, | |
/** | |
* Integrate the velocity into the AABB's position. | |
*/ | |
integrateVelocity: function(gravity) { | |
if (this.im !== 0) { | |
this.position.x += this.velocity.x; | |
this.position.y += this.velocity.y; | |
this.integrateForces(gravity); | |
} | |
}, | |
applyImpulse: function(x, y) { | |
this.velocity.x += this.im * x; | |
this.velocity.y += this.im * y; | |
}, | |
applyForce: function(x, y) { | |
this.force.x += x; | |
this.force.y += y; | |
}, | |
clearForces: function() { | |
this.force.x = 0; | |
this.force.y = 0; | |
} | |
}; | |
// Manifold which contains information about a single collision ----------- | |
// ------------------------------------------------------------------------ | |
function Manifold(a, b) { | |
this.a = a; | |
this.b = b; | |
this.e = Math.min(a.restitution, b.restitution); | |
this.sf = 0; | |
this.df = 0; | |
this.normal = new Vector2(0, 0); | |
this.penetration = 0; | |
} | |
Manifold.prototype = { | |
/** | |
* Initialize the manifold for the collision phase. | |
*/ | |
init: function(gravity, epsilon) { | |
// TODO is this correct? | |
this.sf = Math.sqrt(this.a.staticFriction * this.b.staticFriction); | |
this.df = Math.sqrt(this.a.dynamicFriction * this.b.dynamicFriction); | |
// Figure out whether this is a resting collision, if so do not apply | |
// any restitution | |
var rx = this.b.velocity.x - this.a.velocity.x, | |
ry = this.b.velocity.y - this.a.velocity.y; | |
if ((rx * rx + ry * ry) < (gravity.x * gravity.x + gravity.y * gravity.y) + epsilon) { | |
this.e = 0.0; | |
} | |
}, | |
/** | |
* Solve the SAT for two AABBs | |
*/ | |
solve: function() { | |
// Vector from A to B | |
var nx = this.a.position.x - this.b.position.x, | |
ny = this.a.position.y - this.b.position.y; | |
// Calculate half extends along x axis | |
var aex = (this.a.max.x - this.a.min.x) / 2, | |
bex = (this.b.max.x - this.b.min.x) / 2; | |
// Overlap on x axis | |
var xoverlap = aex + bex - Math.abs(nx); | |
if (xoverlap > 0) { | |
// Calculate half extends along y axis | |
var aey = (this.a.max.y - this.a.min.y) / 2, | |
bey = (this.b.max.y - this.b.min.y) / 2; | |
// Overlap on x axis | |
var yoverlap = aey + bey - Math.abs(ny); | |
if (yoverlap) { | |
// Find out which axis is the axis of least penetration | |
if (xoverlap < yoverlap) { | |
// Point towards B knowing that n points from A to B | |
this.normal.x = nx < 0 ? 1 : -1; | |
this.normal.y = 0; | |
this.penetration = xoverlap; | |
return true; | |
} else { | |
// Point towards B knowing that n points from A to B | |
this.normal.x = 0; | |
this.normal.y = ny < 0 ? 1 : -1; | |
this.penetration = yoverlap; | |
return true; | |
} | |
} | |
} | |
return false; | |
}, | |
/** | |
* Resolves a collision by applying a impulse to each of the AABB's | |
* involved. | |
*/ | |
resolve: function(epsilon) { | |
var a = this.a, | |
b = this.b, | |
// Relative velocity from a to b | |
rx = b.velocity.x - a.velocity.x, | |
ry = b.velocity.y - a.velocity.y, | |
velAlongNormal = rx * this.normal.x + ry * this.normal.y; | |
// If the velocities are separating do nothing | |
if (velAlongNormal > 0 ) { | |
return; | |
} else { | |
// Correct penetration | |
var j = -(1.0 + this.e) * velAlongNormal; | |
j /= (a.im + b.im); | |
// Apply the impulse each box gets a impulse based on its mass | |
// ratio | |
a.applyImpulse(-j * this.normal.x, -j * this.normal.y); | |
b.applyImpulse(j * this.normal.x, j * this.normal.y); | |
// Apply Friction | |
var tx = rx - (this.normal.x * velAlongNormal), | |
ty = ry - (this.normal.y * velAlongNormal), | |
tl = Math.sqrt(tx * tx + ty * ty); | |
if (tl > epsilon) { | |
tx /= tl; | |
ty /= tl; | |
} | |
var jt = -(rx * tx + ry * ty); | |
jt /= (a.im + b.im); | |
// Don't apply tiny friction impulses | |
if (Math.abs(jt) < epsilon) { | |
return; | |
} | |
if (Math.abs(jt) < j * this.sf) { | |
tx = tx * jt; | |
ty = ty * jt; | |
} else { | |
tx = tx * -j * this.df; | |
ty = ty * -j * this.df; | |
} | |
a.applyImpulse(-tx, -ty); | |
b.applyImpulse(tx, ty); | |
} | |
}, | |
/** | |
* This will prevent objects from sinking into each other when they're | |
* resting. | |
*/ | |
positionalCorrection: function() { | |
var a = this.a, | |
b = this.b; | |
var percent = 0.7, | |
slop = 0.05, | |
m = Math.max(this.penetration - slop, 0.0) / (a.im + b.im); | |
// Apply correctional impulse | |
var cx = m * this.normal.x * percent, | |
cy = m * this.normal.y * percent; | |
a.position.x -= cx * a.im; | |
a.position.y -= cy * a.im; | |
b.position.x += cx * b.im; | |
b.position.y += cy * b.im; | |
} | |
}; | |
// AABB collision engine -------------------------------------------------- | |
// ------------------------------------------------------------------------ | |
function Engine() { | |
this.iterations = 10; | |
this.gravity = new Vector2(0, 1); | |
this.contacts = []; | |
this.boxes = []; | |
this.length = 0; | |
} | |
Engine.EPSILON = 0.0001; | |
Engine.prototype = { | |
findCollisions: function() { | |
this.contacts.length = 0; | |
for(var i = 0; i < this.length; i++) { | |
var a = this.boxes[i]; | |
for(var j = i + 1; j < this.length; j++) { | |
var b = this.boxes[j]; | |
// Ignore collisions between objects with infinite mass | |
if (a.im === 0 && b.im === 0) { | |
continue; | |
} else if (a.isOverlapping(b)) { | |
var c = new Manifold(a, b); | |
if (c.solve()) { | |
this.contacts.push(c); | |
} | |
} | |
} | |
} | |
}, | |
integrateForces: function() { | |
for(var i = 0; i < this.length; i++) { | |
this.boxes[i].integrateForces(this.gravity); | |
} | |
}, | |
initializeCollisions: function() { | |
for(var i = 0, l = this.contacts.length; i < l; i++) { | |
this.contacts[i].init(this.gravity, Engine.EPSILON); | |
} | |
}, | |
solveCollisions: function() { | |
for(var i = 0; i < this.iterations; i++) { | |
for(var c = 0, l = this.contacts.length; c < l; c++) { | |
this.contacts[c].resolve(Engine.EPSILON); | |
} | |
} | |
}, | |
integrateVelocities: function() { | |
for(var i = 0; i < this.length; i++) { | |
this.boxes[i].integrateVelocity(this.gravity); | |
this.boxes[i].clearForces(); | |
this.boxes[i].updateBounds(); | |
} | |
}, | |
correctPositions: function() { | |
for(var i = 0, l = this.contacts.length; i < l; i++) { | |
this.contacts[i].positionalCorrection(); | |
} | |
}, | |
tick: function() { | |
this.findCollisions(); | |
this.integrateForces(); | |
this.initializeCollisions(); | |
this.solveCollisions(); | |
this.integrateVelocities(); | |
this.correctPositions(); | |
}, | |
addBox: function(box) { | |
this.boxes.push(box); | |
this.length++; | |
} | |
}; | |
exports.Engine = Engine; | |
exports.Vector2 = Vector2; | |
exports.Box = AABB; | |
})(typeof module === 'undefined' ? window : module.exports); | |
var box = exports; | |
var ground = new box.Box(0, 20, 100, 10); | |
//ground.restitution = 1; | |
var object = new box.Box(0, -20, 10, 10, 1); | |
//var two = new box.Box(20, -40, 10, 10, 1); | |
//two.restitution = 0.1; | |
var w = new box.Engine(); | |
w.addBox(ground); | |
w.addBox(object); | |
//w.addBox(two); | |
//object.applyImpulse(4, 0); | |
setInterval(function() { | |
//object.applyForce(0.5, 0); | |
w.tick(); | |
console.log(Math.round(object.position.x), object.position.y); | |
}, 33); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment