var screen = ge1doot.screen.init("screen", function ()
PHY2D.rectangle(screen.width / 2, screen.height + 50, screen.width, 100, 0, 0);
PHY2D.rectangle(screen.width / 2, -3000, screen.width, 100, 0, 0);
PHY2D.rectangle(-50, -1500 + screen.height, 100, 3000 + screen.height, 0, 0);
PHY2D.rectangle(screen.width + 50, -1500 + screen.height, 100, 3000 + screen.height, 0, 0);
PHY2D.zoom = screen.width / 1660;
ctx.setLineDash = function () {}
var pointer = screen.pointer.init(
kImgPath: "http://www.dhteumeuleu.com/images/"
PHY2D.Vector = function (x, y)
PHY2D.Vector.prototype = {
return this.x * v.x + this.y * v.y;
return this.x * this.x + this.y * this.y;
return Math.sqrt(this.x * this.x + this.y * this.y);
return Math.sqrt(dx * dx + dy * dy);
transform: function (v, m)
this.x = m.cos * v.x - m.sin * v.y + m.tx;
this.y = m.sin * v.x + m.cos * v.y + m.ty;
this.x = m.cos * v.x - m.sin * v.y;
this.y = m.sin * v.x + m.cos * v.y;
len = Math.sqrt(x * x + y * y);
project: function (a, b, n)
len = Math.sqrt(x * x + y * y);
return (-y / len) * n.x + (x / len) * n.y;
addScale: function (v1, v2, s)
this.x = v1.x + (v2.x * s);
this.y = v1.y + (v2.y * s);
subScale: function (v1, v2, s)
this.x = v1.x - (v2.x * s);
this.y = v1.y - (v2.y * s);
var invLen = 1.0 / Math.sqrt(this.x * this.x + this.y * this.y);
clamp: function (v, min, max)
else if (v < min) v = min;
rotateIntoSpaceOf: function (a, m)
this.x = dx * m.cos + dy * m.sin;
this.y = dx * -m.sin + dy * m.cos;
projectPointOntoEdge: function (p, e0, e1)
var t = (ex * (p.x - e0.x) + ey * (p.y - e0.y)) / (ex * ex + ey * ey);
if (t < 0) t = 0; else if (t > 1) t = 1;
this.x = e0.x + (ex * t);
this.y = e0.y + (ey * t);
array: function (n, values)
var array = new Array(n);
array.min = new PHY2D.Vector();
array.max = new PHY2D.Vector();
for (var i = 0; i < n; i++)
array[i] = new PHY2D.Vector(
values ? values[i * 2 + 0] : 0.0,
values ? values[i * 2 + 1] : 0.0
array.transform = function (v, m)
for (var i = 0, len = this.length; i < len; i++)
var x = m.cos * vi.x - m.sin * vi.y + m.tx;
var y = m.sin * vi.x + m.cos * vi.y + m.ty;
if (x < this.min.x) this.min.x = x;
if (y < this.min.y) this.min.y = y;
if (x > this.max.x) this.max.x = x;
if (y > this.max.y) this.max.y = y;
array.rotate = function (v, m)
for (var i = 0, len = this.length; i < len; i++)
elem.x = m.cos * vi.x - m.sin * vi.y;
elem.y = m.sin * vi.x + m.cos * vi.y;
array.resetMinmax = function ()
array.normal = function (points)
for (var i = 0; i < this.length; i++)
points[(i + 1) % this.length],
PHY2D.Matrix = function ()
PHY2D.Matrix.prototype.set = function (x, y, a)
PHY2D.v0 = new PHY2D.Vector();
PHY2D.v1 = new PHY2D.Vector();
PHY2D.v2 = new PHY2D.Vector();
PHY2D.v3 = new PHY2D.Vector();
PHY2D.v4 = new PHY2D.Vector();
PHY2D.v5 = new PHY2D.Vector();
PHY2D.z0 = new PHY2D.Vector();
PHY2D.contacts.index = 0;
PHY2D.contacts.create = function (A, B, pa, pb, dist, normal)
if (!this[this.index]) this[this.index] = new PHY2D.Contact();
this[this.index++].set(A, B, pa, pb, dist, normal);
PHY2D.RigidBody.initBase = function (x, y, w, h, mass, angle, img)
image.src = PHY2D.kImgPath + img.src;
w: (img.w ? img.w * PHY2D.zoom : w),
h: (img.h ? img.h * PHY2D.zoom : h),
x: (img.x ? img.x * PHY2D.zoom : 0),
y: (img.y ? img.y * PHY2D.zoom : 0),
this.pos = new PHY2D.Vector(x, y);
this.vel = new PHY2D.Vector();
this.invMass = mass ? 1 / mass : 0;
this.matrix = new PHY2D.Matrix().set(x, y, angle);
this.nextFrame = new PHY2D.Matrix();
this.aabb = new PHY2D.AABB();
this.c1 = new PHY2D.Vector();
this.c0 = new PHY2D.Vector();
this.normal = new PHY2D.Vector();
PHY2D.RigidBody.draw = function ()
ctx.translate(this.pos.x, this.pos.y);
ctx.drawImage(img.elem, -img.w * 0.5 + img.x, -img.h * 0.5 + img.y, img.w, img.h);
PHY2D.RigidBody.integrate = function ()
if (this.invMass > 0) this.vel.y += PHY2D.kGravity;
var vx = this.vel.x * PHY2D.kTimeStep;
var vy = this.vel.y * PHY2D.kTimeStep;
var va = this.angVel * PHY2D.kTimeStep;
this.matrix.set(this.pos.x, this.pos.y, this.ang);
this.nextFrame.set(this.pos.x + vx, this.pos.y + vy, this.ang + va);
if (!this.static) this.motionAABB();
if (pointer.active && !PHY2D.manipulate && this.invMass)
if (this.isPointInPoly(pointer.pos.x, pointer.pos.y))
PHY2D.manipulate = new PHY2D.Manipulate(this);
PHY2D.manipulate.solve();
PHY2D.Polygon = Object.create(PHY2D.RigidBody);
PHY2D.Polygon.init = function (x, y, w, h, vertices, mass, angle, img)
this.initBase(x, y, w, h, mass, angle, img);
this.length = (vertices.length / 2) | 0;
this.localSpacePoints = new PHY2D.Vector().array(this.length, vertices);
this.localSpaceNormals = new PHY2D.Vector().array(this.length).normal(this.localSpacePoints);
this.worldSpaceNormals = new PHY2D.Vector().array(this.length);
this.worldSpacePoints = new PHY2D.Vector().array(this.length);
this.invI = (this.invMass > 0) ? 1 / ((1 / this.invMass) * (w * w + h * h) / 3) : 0
PHY2D.Polygon.motionAABB = function ()
this.worldSpacePoints.resetMinmax();
PHY2D.kMotionAABB && this.worldSpacePoints.transform(this.localSpacePoints, this.nextFrame);
this.worldSpacePoints.transform(this.localSpacePoints, this.matrix);
this.worldSpaceNormals.rotate(this.localSpaceNormals, this.matrix);
var min = this.worldSpacePoints.min;
var max = this.worldSpacePoints.max;
this.aabb.x = (min.x + max.x) * 0.5;
this.aabb.y = (min.y + max.y) * 0.5;
this.aabb.w = (max.x - min.x) * 0.5;
this.aabb.h = (max.y - min.y) * 0.5;
PHY2D.Polygon.isPointInPoly = function (x, y)
for (var p = this.worldSpacePoints, i = -1, l = this.length, j = l - 1; ++i < l; j = i)
((p[i].y <= y && y < p[j].y) || (p[j].y <= y && y < p[i].y))
&& (x <= (p[j].x - p[i].x) * (y - p[i].y) / (p[j].y - p[i].y) + p[i].x)
PHY2D.Polygon.generateContacts = function (B)
if (B.type === "poly") return this.polyVsPoly(B);
if (B.type === "circle") return this.polyVsCircle(B);
PHY2D.Polygon.polyVsPoly = function (that)
var face, vertex, vertexRect, faceRect, fp, va, vb, vc, wsN, wdV0, wdV1, wsV0, wsV1;
var mp = PHY2D.mostPenetrating, ms = PHY2D.mostSeparated;
ms.set(100000, -1, -1, 0);
mp.set(-100000, -1, -1, 0);
this.featurePairJudgement(that, 2);
that.featurePairJudgement(this, 1);
if (ms.dist > 0 && ms.fpc !== 0)
wsN = faceRect.worldSpaceNormals[face];
va = vertexRect.worldSpacePoints[(vertex - 1 + vertexRect.length) % vertexRect.length];
vb = vertexRect.worldSpacePoints[vertex];
vc = vertexRect.worldSpacePoints[(vertex + 1) % vertexRect.length];
if (PHY2D.v0.project(vb, va, wsN) < PHY2D.v1.project(vc, vb, wsN))
wsV0 = faceRect.worldSpacePoints[face];
wsV1 = faceRect.worldSpacePoints[(face + 1) % faceRect.length];
this.c0.projectPointOntoEdge(wsV0, wdV0, wdV1);
this.c1.projectPointOntoEdge(wsV1, wdV0, wdV1);
that.c0.projectPointOntoEdge(wdV1, wsV0, wsV1);
that.c1.projectPointOntoEdge(wdV0, wsV0, wsV1);
this.c0.projectPointOntoEdge(wdV1, wsV0, wsV1);
this.c1.projectPointOntoEdge(wdV0, wsV0, wsV1);
that.c0.projectPointOntoEdge(wsV0, wdV0, wdV1);
that.c1.projectPointOntoEdge(wsV1, wdV0, wdV1);
var d0 = PHY2D.v1.sub(that.c0, this.c0).dot(this.normal);
var d1 = PHY2D.v1.sub(that.c1, this.c1).dot(this.normal);
PHY2D.contacts.create(this, that, this.c0, that.c0, d0, this.normal);
PHY2D.contacts.create(this, that, this.c1, that.c1, d1, this.normal);
PHY2D.contacts.create(this, that, this.c1, that.c1, d1, this.normal);
PHY2D.contacts.create(this, that, this.c0, that.c0, d0, this.normal);
PHY2D.Polygon.polyVsCircle = function (that)
var mp = PHY2D.mostPenetrating, ms = PHY2D.mostSeparated;
ms.set(100000, -1, -1, 0);
mp.set(-100000, -1, -1, 0);
var wsN = this.worldSpaceNormals;
var wsP = this.worldSpacePoints;
for (var i = 0; i < this.length; i++)
var wsV1 = wsP[(i + 1) % this.length];
var dist = v1.projectPointOntoEdge(that.pos, wsV0, wsV1).dist(that.pos);
if (dist > 0 && dist < ms.dist)
var penetration = mp.dist - that.radius;
this.normal.copy(this.worldSpaceNormals[mp.face]);
this.normal.sub(that.pos, this.c0).unit();
this.normal.sub(that.pos, this.c1).unit();
penetration = ms.dist - that.radius;
this.c1.subScale(that.pos, this.normal, that.radius);
PHY2D.contacts.create(this, that, this.c0, this.c1, penetration, this.normal);
PHY2D.Polygon.featurePairJudgement = function (that, fpc)
var wsN, closestI, closest, dist;
var mp = PHY2D.mostPenetrating, ms = PHY2D.mostSeparated;
for (var edge = 0; edge < this.length; edge++)
wsN = this.worldSpaceNormals[edge];
PHY2D.v5.rotateIntoSpaceOf(wsN, that.matrix);
for (var i = 0; i < that.length; i++)
var d = PHY2D.v5.dot(that.localSpacePoints[i]);
var closest = that.worldSpacePoints[closestI];
PHY2D.v0.sub(closest, this.worldSpacePoints[edge]);
var dist = PHY2D.v0.dot(wsN);
PHY2D.v1.sub(closest, this.worldSpacePoints[(edge + 1) % this.length]);
dist = this.c0.projectPointOntoEdge(PHY2D.z0, PHY2D.v0, PHY2D.v1).lenSqr();
ms.set(dist, closestI, edge, fpc);
mp.set(dist, closestI, edge, fpc);
PHY2D.Circle = Object.create(PHY2D.RigidBody);
PHY2D.Circle.init = function (x, y, w, mass, img)
this.initBase(x, y, w, w, mass, 0, img);
this.invI = (1 / 10) * mass * this.radius * this.radius;
PHY2D.Circle.isPointInPoly = function (x, y)
var dx = (x - this.pos.x);
var dy = (y - this.pos.y);
if (dx * dx + dy * dy < this.radius * this.radius) return true;
PHY2D.Circle.motionAABB = function ()
var x = this.pos.x + (this.vel.x * PHY2D.kTimeStep);
var y = this.pos.y + (this.vel.y * PHY2D.kTimeStep);
this.aabb.x = (this.pos.x + x) * 0.5;
this.aabb.y = (this.pos.y + y) * 0.5;
this.aabb.w = this.radius + Math.abs(this.pos.x - x) * 0.5;
this.aabb.h = this.radius + Math.abs(this.pos.y - y) * 0.5;
PHY2D.Circle.generateContacts = function (that)
if (that.type === "circle")
this.normal.sub(that.pos, this.pos);
var distCentre = this.normal.len();
var penetration = distCentre - (this.radius + that.radius);
penetration = -(this.radius + that.radius);
this.normal.unitLen(distCentre);
this.c0.addScale(this.pos, this.normal, this.radius);
this.c1.subScale(that.pos, this.normal, that.radius);
PHY2D.contacts.create(this, that, this.c0, this.c1, penetration, this.normal);
PHY2D.FeaturePair = function ()
PHY2D.FeaturePair.prototype.set = function (dist, closestI, edge, fpc)
this.closestI = closestI;
PHY2D.mostSeparated = new PHY2D.FeaturePair();
PHY2D.mostPenetrating = new PHY2D.FeaturePair();
PHY2D.Contact = function ()
this.normal = new PHY2D.Vector();
this.tangent = new PHY2D.Vector();
this.ra = new PHY2D.Vector();
this.rb = new PHY2D.Vector();
this.impulse = new PHY2D.Vector();
this.dv = new PHY2D.Vector();
this.v1 = new PHY2D.Vector();
this.v2 = new PHY2D.Vector();
PHY2D.Contact.prototype = {
set: function (A, B, pa, pb, dist, normal)
this.normal.copy(normal);
this.tangent.perp(normal);
this.ra.sub(pa, A.pos).perpSelf();
this.rb.sub(pb, B.pos).perpSelf();
ran = this.ra.dot(this.normal);
rbn = this.rb.dot(this.normal);
this.invDenom = 1 / (A.invMass + B.invMass + (ran * ran * A.invI) + (rbn * rbn * B.invI));
ran = this.ra.dot(this.tangent);
rbn = this.rb.dot(this.tangent);
this.invDenomTan = 1 / (A.invMass + B.invMass + (ran * ran * A.invI) + (rbn * rbn * B.invI));
applyImpulse: function ()
this.a.vel.addScale(this.a.vel, this.impulse, this.a.invMass);
this.b.vel.subScale(this.b.vel, this.impulse, this.b.invMass);
this.a.angVel += this.impulse.dot(this.ra) * this.a.invI;
this.b.angVel -= this.impulse.dot(this.rb) * this.b.invI;
this.v1.addScale(this.b.vel, this.rb, this.b.angVel),
this.v2.addScale(this.a.vel, this.ra, this.a.angVel)
newImpulse = (this.dv.dot(this.normal) + this.dist / PHY2D.kTimeStep) * this.invDenom + this.impulseN;
if (newImpulse > 0) newImpulse = 0;
this.impulse.scale(this.normal, newImpulse - this.impulseN)
this.impulseN = newImpulse;
absMag = -this.impulseN * PHY2D.kFriction;
newImpulse = this.impulse.clamp(this.dv.dot(this.tangent) * this.invDenomTan + this.impulseT, -absMag, absMag);
this.impulse.scale(this.tangent, newImpulse - this.impulseT);
this.impulseT = newImpulse;
PHY2D.Constraint = function (A, va, B, vb)
this.ra = new PHY2D.Vector();
this.rb = new PHY2D.Vector();
this.axis = new PHY2D.Vector();
this.normal = new PHY2D.Vector();
this.impulse = new PHY2D.Vector();
this.dv = new PHY2D.Vector();
this.va = new PHY2D.Vector(va[0] * PHY2D.zoom, va[1] * PHY2D.zoom);
this.vb = new PHY2D.Vector(vb[0] * PHY2D.zoom, vb[1] * PHY2D.zoom);
this.pa = new PHY2D.Vector();
this.pb = new PHY2D.Vector();
PHY2D.Constraint.prototype.presolve = function ()
this.pa.transform(this.va, this.a.matrix);
this.pb.transform(this.vb, this.b.matrix);
this.axis.sub(this.pb, this.pa);
this.normal.scale(this.axis, 1 / (this.axis.len() + 0.0001));
this.ra.sub(this.pa, this.a.pos).perpSelf();
this.rb.sub(this.pb, this.b.pos).perpSelf();
var ran = this.ra.dot(this.normal), rbn = this.rb.dot(this.normal);
this.invDenom = 1 / (this.a.invMass + this.b.invMass + (ran * ran * this.a.invI) + (rbn * rbn * this.b.invI));
PHY2D.Constraint.prototype.solve = function ()
PHY2D.v1.addScale(this.b.vel, this.rb, this.b.angVel),
PHY2D.v2.addScale(this.a.vel, this.ra, this.a.angVel)
var dist = this.axis.dot(this.normal);
var newImpulse = (this.dv.dot(this.normal) + dist / PHY2D.kTimeStep) * this.invDenom + this.impulseN;
this.impulse.scale(this.normal, newImpulse - this.impulseN)
this.impulseN = newImpulse;
PHY2D.Constraint.prototype.applyImpulse = PHY2D.Contact.prototype.applyImpulse;
PHY2D.Manipulate = function (B)
this.rb = new PHY2D.Vector();
this.axis = new PHY2D.Vector();
this.normal = new PHY2D.Vector();
this.impulse = new PHY2D.Vector();
this.dv = new PHY2D.Vector();
this.vb = (B.type === "circle") ? new PHY2D.Vector() : new PHY2D.Vector().rotateIntoSpaceOf(PHY2D.v1.sub(B.pos, pointer.pos), B.matrix);
this.pb = new PHY2D.Vector();
PHY2D.Manipulate.prototype.solve = function ()
this.pb.transform(this.vb, this.b.matrix);
this.axis.sub(this.pb, pointer.pos);
this.normal.scale(this.axis, 1 / (this.axis.len() + 0.0001));
this.rb.sub(this.pb, this.b.pos).perpSelf();
this.dv.addScale(this.b.vel, this.rb, this.b.angVel);
this.impulse.scale(this.normal, this.dv.dot(this.normal) + this.axis.dot(this.normal) * 2);
this.b.vel.sub(this.b.vel, this.impulse);
this.b.angVel -= this.impulse.dot(this.rb) * this.b.invI;
PHY2D.render = function ()
PHY2D.contacts.index = 0;
for (var i = 0, len = PHY2D.objects.length; i < len - 1; i++)
var A = PHY2D.objects[i];
for (var j = i + 1; j < len; j++)
var B = PHY2D.objects[j];
if (A.invMass || B.invMass)
Math.abs(b.x - a.x) - (a.w + b.w) < 0 &&
Math.abs(b.y - a.y) - (a.h + b.h) < 0
var contacts = PHY2D.contacts, len = contacts.index;
var joints = PHY2D.joints, cln = joints.length;
for (var i = 0; i < cln; i++)
PHY2D.joints[i].presolve();
for (var j = 0; j < 5; j++)
for (var i = 0; i < cln; i++)
for (var i = 0; i < len; i++)
PHY2D.contacts[i].solve();
for (var i = 0, len = PHY2D.objects.length; i < len; i++)
PHY2D.objects[i].integrate();
for (var i = 0; i < len; i++)
var rb = PHY2D.objects[i];
PHY2D.manipulate.solve();
ctx.moveTo(pointer.pos.x, pointer.pos.y);
ctx.lineTo(PHY2D.manipulate.pb.x, PHY2D.manipulate.pb.y);
ctx.strokeStyle = "rgb(255,255,255)";
ctx.fillStyle = "rgb(255,255,255)";
ctx.arc(PHY2D.manipulate.pb.x, PHY2D.manipulate.pb.y, 5, 0, 2 * Math.PI);
PHY2D.rectangle = function (x, y, w, h, mass, angle, img)
w / 2, -h / 2, -w / 2, -h / 2, -w / 2, h / 2, w / 2, h / 2
var rb = Object.create(PHY2D.Polygon).init(x, y, w, h, vertices, mass, angle, img);
PHY2D.triangle = function (x, y, w, h, mass, angle, img)
w / 2, h / 2, 0, - h / 2, -w / 2, h / 2
var rb = Object.create(PHY2D.Polygon).init(x, y, w, h, vertices, mass, angle, img);
PHY2D.circle = function (x, y, w, mass, img)
var rb = Object.create(PHY2D.Circle).init(x, y, w, mass, img);
PHY2D.addJoint = function (A, va, B, vb)
new PHY2D.Constraint(A, va, B, vb)
PHY2D.deleteStatic = function ()
var k = PHY2D.objects.length;
var p = PHY2D.objects[k];
PHY2D.objects.splice(k, 1);
PHY2D.stopManipulate = function () { PHY2D.manipulate = null; }
for (var i = 0; i < 6; i++) PHY2D.circle(100 + i * 200, screen.height - 100, 200, 1, {src: "e6.png"});
for (var i = 1; i < 6; i++) PHY2D.circle(100 + i * 233, screen.height - 333, 233, 1, {src: "e8.png"});
PHY2D.rectangle(500, -400, 370, 420, 4, Math.PI / 2, {
requestAnimationFrame(run);
ctx.clearRect(0, 0, screen.width, screen.height);