2Dphysics
Extracto
Commented source(20kb)
Resumen
Resumen Principal
2Dphysics v.1.1 se presenta como el motor de física 2D más pequeño del mundo escrito en JavaScript, ofreciendo capacidades fundamentales para simulaciones interactivas en la web. Este motor destaca por su increíble compacidad, con una versión minificada de 1.5kb (comprimida) y una "micro" versión aún más reducida de 1.2kb, lo que lo hace ideal para proyectos donde el tamaño del código es crítico. A pesar de su reducido tamaño, incluye funcionalidades esenciales como muelles (springs) y juntas (joints), permitiendo crear interacciones complejas entre objetos. Su arquitectura está diseñada para ser flexible y extensible, proporcionando una API clara para la creación y manipulación de formas y restricciones físicas. La disponibilidad del código fuente comentado (20kb) fomenta la comprensión y personalización, posicionándolo como una herramienta robusta para desarrolladores que buscan una solución de física 2D ligera y eficiente para aplicaciones web, juegos o prototipos interactivos, manteniendo un alto grado de control sobre la simulación.
Elementos Clave
- Diseño Modular y Optimizado: El motor se ofrece en múltiples formatos: una versión con código fuente comentado para facilitar el estudio, una versión minificada (1.5kb zipped) para despliegue en producción, y una versión "micro" (1.2kb zipped) que omite las juntas para escenarios de ultra-bajo peso. Esta estrategia de empaquetado demuestra un enfoque meticuloso en la optimización del tamaño, sin sacrificar las funcionalidades básicas de la física 2D.
- API Flexible para la Creación de Objetos y Restricciones: La API permite definir formas (shapes) como
RECTANGLEoCIRCLEcon propiedades detalladas como masa, fricción, restitución, ángulo, velocidad, gravedad personalizada y posibilidad de activar/desactivar rotaciones. También incluye la funciónanchorpara fijar puntos específicos en las formas yjointpara conectar dos formas con diferentes tipos de uniones, incluyendoSPRING(muelle),REPULSIVE(repulsiva),HINGE(bisagra) yFIXED(fija), cada una con sus propios parámetros de fuerza y longitud. - Control Granular sobre la Simulación: Los globales
G(Gravedad),O(Corrección de superposición de formas),E(Elasticidad de juntas fijas) yR(Resoluciones por frame) permiten a los desarrolladores ajustar el comportamiento global de la simulación. La funcióntransformhabilita la manipulación cinemática de las formas, permitiendo cambiar su posición y ángulo directamente en cada frame, lo que es esencial para objetos controlados por el usuario o animaciones predefinidas. - Enfoque en el Core de Física con Extensibilidad: El motor se centra exclusivamente en el cálculo de la física, dejando la implementación del renderizador completamente abierta al usuario. El ejemplo de código demuestra cómo integrar un renderizador básico de Canvas y un bucle de juego (
setIntervalconrun()ydraw()), subrayando la flexibilidad del motor para adaptarse a cualquier entorno de renderizado y lógica de aplicación.
Análisis e Implicaciones
Este motor es una solución altamente eficiente para desarrolladores que necesitan integrar física 2D en entornos web con requisitos estrictos de rendimiento y tamaño de archivo. Su diseño modular y su API robusta implican que se puede adaptar fácilmente para impulsar una variedad de experiencias interactivas, desde juegos educativos hasta simulaciones complejas. La capacidad de personalizar profundamente las propiedades físicas de las formas y las juntas proporciona un control sin precedentes sobre la dinámica del mundo virtual.
Contexto Adicional
El proyecto se distribuye bajo dominio público y su código fuente está disponible en GitHub, fomentando la colaboración y el desarrollo comunitario. Esto lo convierte en una base ideal para experimentar y construir soluciones personalizadas de física 2D.
Contenido
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