2Dphysics v.1.1
The World's smallest 2D physics engine in JavaScript including springs and joints
Commented source
(20kb)
Minified
(1.5kb zipped)
Micro
(no joints, 1.2kb zipped)
Source code
G=[0,.1],O=.4,E=.01,R=20,H=[],J=[],M=[],RECTANGLE=0,CIRCLE=1,SPRING=0,REPULSIVE=1,HINGE=2,FIXED=3;
((n=x=>S(x,1/l(x)),l=x=>d(x,x)**.5,d=(x,y)=>x[0]*y[0]+x[1]*y[1],p=(x,y)=>[x[0]+y[0],x[1]+y[1]],s=(x,y)=>p(x,S(y,-1)),S=(x,y)=>[x[0]*y,x[1]*y],C=(x,y)=>x[0]*y[1]-x[1]*y[0],r=(x,y,l)=>[(x[0]-y[0])*Math.cos(l)-(x[1]-y[1])*Math.sin(l)+y[0],(x[0]-y[0])*Math.sin(l)+(x[1]-y[1])*Math.cos(l)+y[1]],x=(x,y)=>[-x.A*y[1],x.A*y[0]],I=(c,t,l,e,a,n,r=1,o)=>{r&&(o=S(e,l),c.v=s(c.v,S(o,c.M)),t.v=p(t.v,S(o,t.M))),c.R&&(c.A-=a*l*c.I),t.R&&(t.A+=n*l*t.I)})=>{shape=(t,c,m=1,w=9,h=9,g,n=t?m*w*w/2:m*(w*w+h*h)/12,s={t,m,w,h,c:[0,0],f:.5,r:.5,a:0,v:[0,0],A:0,g:G,F:[0,0],T:0,R:1,p:[],N:[],n:[[0,-1],[1,0],[0,1],[-1,0]],M:m?1/m:0,I:n?1/n:0,V:[[-w/2,-h/2],[w/2,-h/2],[w/2,h/2],[-w/2,h/2]],e:H.length,...g})=>(transform(s,c,s.a),H.push(s),s),anchor=(t,c)=>(t.p.push(p(c,t.c)),t.p.length-1),joint=(t,A,a,B,b,s,l)=>{if(t>1){A.N.push(B.e);B.N.push(A.e);l=B.a-A.a}J.push({t,A,a,B,b,s,l})},transform=(c,f,n,i=4)=>{if(c.c=p(c.c,f),!c.t)for(;i--;)c.n[i]=r(c.n[i],[0,0],n),c.V[i]=r(p(c.V[i],f),c.c,n);for(i in c.p)c.p[i]=r(p(c.p[i],f),c.c,n)},run=(a,f,t,c,e,h,o,r,A,v,w,i,V,b,B,m,g)=>{for(M=[],f=H.length;f--;)for(a=f-1;a>=0;a--)if(c=H[f],e=H[a],c.F=S(c.g,c.m),c.M+e.M&&!c.N.includes(a)){if(h=0,c.t&&e.t)o=s(e.c,c.c),(r=l(o))=0&&w>0&&wA&&(A=w,v={v:e.V[i],d:w});v||(h=0),h&&v.d0&&(b=C(V,w=n(w)),B=C(i,w),m=Math.min(c.f,e.f),g=-d(h,w)/(c.M+e.M+b*b*c.I+B*B*e.I),Math.abs(g)>Math.abs(v)*m&&(g=Math.sign(g)*Math.abs(v)*m),I(c,e,g,w,b,B));for(t of J)c=t.A,e=t.B,h=c.p[t.a],o=e.p[t.b],v=s(h,c.c),w=s(o,e.c),A=s(o,h),i=(r=l(A))>0?n(A):[1,0],V=C(v,i),b=C(w,i),(B=c.M+e.M+V*V*c.I+b*b*e.I+E)&&(t.t?t.t<2?I(c,e,t.s*Math.max(0,t.l-r)/1e4,i,V,b):(I(c,e,(-d(s(p(e.v,x(e,w)),p(c.v,x(c,v))),i)+-O*r)/B,i,V,b),t.t>2&&(m=e.a-c.a-t.l,(g=c.I+e.I)&&I(c,e,(-(e.A-c.A)+-O*m)/g,0,1,1,0)),r>0&&(B=c.M+e.M,transform(c,S(A,c.M/B),0),transform(e,S(A,-e.M/B),0))):(I(c,e,-t.s*(r-t.l)/1e3/B,i,V,b),I(c,e,-t.s*(r-t.l)/1e3/B,i,V,b)))}for(f of H)f.M&&(f.v=p(f.v,S(f.F,f.M<0?-f.M:f.M)),f.F=[0,0],f.A+=f.T*f.I,f.a+=f.A,transform(f,f.v,f.A),f.v=S(f.v,.99),Math.abs(l(f.v))<1e-4&&(f.v=[0,0]),f.A*=.99,Math.abs(f.A)<1e-4&&(f.A=0),f.T=0)}})()
API
Sample code snippets are available under the demo ↓
GLOBALS
|
SHAPES TYPES
|
FUNCTIONS
|
|
Demo
The "micro" version can only do the first two blocks below (see micro demo here).
Click the demo below to restart it.
Examples of user code
Simple renderer and game loop
// Canvas setup
c = a.getContext`2d`
// Draw
draw = (e, r) => {
// reset canvas
a.width ^= 0;
// draw shapes
for(e of H){
c.save(),
c.beginPath();
// circle
if(e.t === CIRCLE){
c.fillStyle=e.d||"#bbb",
c.translate(e.c[0],e.c[1]),
c.rotate(e.a),
c.arc(0,0,e.w,0,7),
c.lineTo(0,0)
}
// rectangle
else {
c.fillStyle=e.d||"#bbb",
c.moveTo(e.V[0][0],e.V[0][1]),
c.lineTo(e.V[1][0],e.V[1][1]),
c.lineTo(e.V[2][0],e.V[2][1]),
c.lineTo(e.V[3][0],e.V[3][1])
}
c.closePath(),
c.fill(),
c.stroke()
c.restore();
// anchors
for(r of e.p){
c.beginPath(),
c.fillStyle="#080",
c.arc(r[0],r[1],5,0,7),
c.fill(),
c.closePath()
}
}
// joints
for(e of J){
c.beginPath(),
c.strokeStyle="#fa0",
c.moveTo(e.A.p[e.a][0],e.A.p[e.a][1]),
c.lineTo(e.B.p[e.b][0],e.B.p[e.b][1]),
c.stroke(),
c.closePath()
}
}
// Game loop
setInterval(() => {
time++;
run();
draw();
}, 16);
Shapes creation
// Simple rectangle
shape(RECTANGLE, [100, 50], 10, 20, 30); // type, center, mass, width, height
// Rectangle with all optional params
shape(
RECTANGLE, // type (0)
[100, 50], // center
0, // mass (0 = fixed)
20, // width
30, // height
{ // options
f: 0.9, // friction (0-1, default: 0.5)
r: 0.1, // restitution (0-1, default: 0.5)
a: .5, // angle (in radians, default: 0)
v: [2, 0], // velocity (default: [0,0])
A: .5, // angular velocity (default: 0)
g: [0, -2], // gravity (default: G)
R: 0, // enable rotations (default: 1)
d: "red" // custom data (ex: color)
}
);
// Simple circle
shape(CIRCLE, [200, 50], 10, 50); // type, center, mass, radius
// Circle with all optional params
shape(
CIRCLE, // type (1)
[200, 50], // center
10, // mass
50, // radius
50, // radius again
{ // options
f: 0.9, // friction (0-1, default: 0.5)
r: 0.1, // restitution (0-1, default: 0.5)
a: .5, // angle (in radians, default: 0)
v: [2, 0], // velocity (default: [0,0])
A: .5, // angular velocity (default: 0)
g: [0, -2], // gravity (default: G)
R: 0, // enable rotations (default: 1)
P: [0, -1], // propulsion (default: [0,0])
O: 1, // custom overlap correction
d: "red" // custom data (ex: color)
}
);
Spring joint
r1 = shape(RECTANGLE, [130, 100], 0, 90, 50);
c1 = shape(CIRCLE, [90, 250], 1, 30, 30);
// attach anchor a1 to r1 at local position [20, 15]
a1 = anchor(r1, [20,15]);
// attach anchor a2 to c1 at local position [0, -10]
a2 = anchor(c1, [0,-10]);
// Create spring joint between r1's anchor a1 and c1's anchor a2 with a .2 force and a 80px rest length
joint(SPRING, r1, a1, c1, a2, .2, 80);
Repulsive joint
r1 = shape(RECTANGLE, [100, 120], 0, 90, 50);
c1 = shape(CIRCLE, [120, 80], 1, 30);
a1 = anchor(r1, [0,-25]);
a2 = anchor(c1, [-20,0]);
// Create repulsive joint between r1's anchor a1 and c1's anchor a2 with a .2 force and a 200px min length
joint(REPULSIVE, r1, a1, c1, a2, .2, 200);
Fixed joint
r1 = shape(RECTANGLE, [100, 120], 10, 90, 50);
c1 = shape(CIRCLE, [100, 100], 10, 30);
a1 = anchor(r1, [0,-15]);
a2 = anchor(c1, [0,20]);
// Create fixed joint between r1's anchor a1 and c1's anchor a2
joint(FIXED, r1, a1, c1, a2);
Hinge joint
r1 = shape(HINGE, [130, 160], 0, 90, 50);
c1 = shape(CIRCLE, [165, 165], 100, 30, 30);
a1 = anchor(r1, [20,15]);
a2 = anchor(c1, [-20,10]);
// Create hinge joint between r1's anchor a1 and c1's anchor a2
joint(HINGE, r1, a1, c1, a2);
Kinematic shape
// Create a fixed shape
r1 = shape(RECTANGLE, [150, 100], 0, 50, 10);
// Update the shape's position and angle in the main loop
setInterval(() => {
run();
// At each frame, increment X position and decrement angle
transform(r9, [1, 0], -0.01);
draw();
}, 16);
© Public domain - Xem, 2025
Making-of -
Source code on Github