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)}})()
Sample code snippets are available under the demo ↓
GLOBALS
|
SHAPES TYPES
|
FUNCTIONS
|
|
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)
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);