2. Simulación - nelsonmoreno/Box2d GitHub Wiki

Box2d puede simular objetos rígidos cóncavos y/o convexos de tamaños que van desde 0.1 metros a 10 metros. Los objetos pueden interactuar con fuerzas como la gravedad, el impulso, la fricción, el rebote y la elasticidad. La fricción tal como la gravedad pueden ser diferentes para cada objeto y en el caso de la gravedad no esta necesariamente dirigida hacia abajo.

Requisitos:

Realmente es poco lo que se requiere, solo debes de disponer de un entorno de desarrollo WEB el cual en la mayoría de los casos es proporcionado por el IDE que mas te gusta, en mi caso yo uso Intellij y como plataforma WEB uso GAE, si estas interesado en aprender a desplegar aplicaciones locales ó en la nube usa este tutorial. Si dispones de una cuenta de correo educativa reconocida por JetBrains como por ejemplo @upb.edu.co entonces puedes usar sus herramientas de desarrollo profesionales, sin limitaciones y sin ningún costo (https://www.jetbrains.com/student/).

Adicionalmente solo requieres la API de Box2d para JavaScritp que puedes descargar del repositorio, directamente de este link.

Código completo

El siguiente es el código completo con una simulación en Box2d, mas adelante explicare cada fragmento de este, ten en cuenta que este es la base para las siguientes demostraciones (también puedes ver la demostración):

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Box2D Javascript Fun : 00 : Most Basic Box2D Example</title>
    <script src="../Box2d.min.js"></script>
</head>
<body>
<h1>Box2d / Primeros pasos</h1>
<canvas id="c" width="640" height="480" style="border: 1px solid black"></canvas>

<script>
// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
window.requestAnimFrame = (function(){
    return  window.requestAnimationFrame       ||
            window.webkitRequestAnimationFrame ||
            window.mozRequestAnimationFrame    ||
            window.oRequestAnimationFrame      ||
            window.msRequestAnimationFrame     ||
            function(/* function */ callback, /* DOMElement */ element){
                window.setTimeout(callback, 1000 / 60);
            };
})();

var canvas = document.getElementById("c");
var ctx = canvas.getContext("2d");
var world;

function init() {
    var   b2Vec2 = Box2D.Common.Math.b2Vec2
            , b2BodyDef = Box2D.Dynamics.b2BodyDef
            , b2Body = Box2D.Dynamics.b2Body
            , b2FixtureDef = Box2D.Dynamics.b2FixtureDef
            , b2Fixture = Box2D.Dynamics.b2Fixture
            , b2World = Box2D.Dynamics.b2World
            , b2MassData = Box2D.Collision.Shapes.b2MassData
            , b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape
            , b2CircleShape = Box2D.Collision.Shapes.b2CircleShape
            , b2DebugDraw = Box2D.Dynamics.b2DebugDraw
            , b2RevoluteJointDef = Box2D.Dynamics.Joints.b2RevoluteJointDef
            ;

    world = new b2World(
            new b2Vec2(0, 10)    //gravity
            ,  true                 //allow sleep
    );

    var SCALE = 30;

    var fixDef = new b2FixtureDef;
    fixDef.density = 1.0;
    fixDef.friction = 0.5;
    fixDef.restitution = 0.2;

    var bodyDef = new b2BodyDef;

    //create ground
    bodyDef.type = b2Body.b2_staticBody;

    // positions the center of the object (not upper left!)
    bodyDef.position.x = canvas.width / 2 / SCALE;
    bodyDef.position.y = (canvas.height / SCALE) - 1;

    fixDef.shape = new b2PolygonShape;

    // half width, half height. eg actual height here is 1 unit
    fixDef.shape.SetAsBox((canvas.width / SCALE) / 2, 0.5 / 2);
    world.CreateBody(bodyDef).CreateFixture(fixDef);

    //create object as box
    bodyDef.type = b2Body.b2_dynamicBody;
    fixDef.shape = new b2PolygonShape;
    fixDef.shape.SetAsBox(
            Math.random() + 0.1 //half width
            ,  Math.random() + 0.1 //half height
    );
    //at this point
    bodyDef.position.x = Math.random() * 25;
    bodyDef.position.y = Math.random() * 10;
    world.CreateBody(bodyDef).CreateFixture(fixDef);


    //setup debug draw
    var debugDraw = new b2DebugDraw();
    debugDraw.SetSprite(document.getElementById("c").getContext("2d"));
    debugDraw.SetDrawScale(SCALE);
    debugDraw.SetFillAlpha(0.3);
    debugDraw.SetLineThickness(1.0);
    debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
    world.SetDebugDraw(debugDraw);

}; // init()

function update() {
    world.Step(
            1 / 60   //frame-rate
            ,  10       //velocity iterations
            ,  10       //position iterations
    );
    world.DrawDebugData();
    world.ClearForces();

    requestAnimFrame(update);
}; // update()

init();
requestAnimFrame(update);

</script>
</body>
</html>

requestAnimationFrame (rAF)

rAF es la API base para construir cualquier animación en un navegador WEB, esto permite que el navegador optimice las animaciones y pueda dibujar en un solo ciclo varias animaciones simultaneas. Lo que a la larga permite optimizar el uso de la GPU, la CPU la memoria y por ende el uso de la energía, especialmente si dependemos de una batería.

Cualquier otra técnica como por ejemplo crear un ciclo con setInterval or setTimeout provocara que en cualquier momento tu navegador detenga la animación en aras de prevenir un bucle indeseado. El siguiente código es un ejemplo de como usar esta API.

// shim layer with setTimeout fallback
window.requestAnimFrame = (function(){`
  return  window.requestAnimationFrame       ||
          window.webkitRequestAnimationFrame ||
          window.mozRequestAnimationFrame    ||
          function( callback ){
            window.setTimeout(callback, 1000 / 60);
          };
})();


// usage:
// instead of setInterval(render, 16) ....

(function animloop(){
  requestAnimFrame(animloop);
  render();
})();
// place the rAF *before* the render() to assure as close to
// 60fps with the setTimeout fallback.

El mundo

En Box2d se debe definir un vector de gravedad y una variable de tipo booleano para indicar si los objetos en reposo deben ser puestos en modo sleep. No es necesario indicar cuales son las dimensiones del mundo.

world = new b2World(
  new b2Vec2(0, 10)    //gravity
  , true               //allow sleep
);

El suelo

Box2d no proporciona un suelo por omisión por lo que hay que definirlo, sin embargo esto es algo muy simple ya que su comportamiento es igual al de cualquier otro objeto estático dentro del mundo y solo se requiere establecer 3 propiedades para crearlo:

La cosa: define los atributos del objeto tales como densidad, fricción y elasticidad.

var fixDef = new b2FixtureDef;
fixDef.density = 1.0;
fixDef.friction = 0.5;
fixDef.restitution = 0.2;

El cuerpo: define donde esta ubicado el objeto y si es estático o dinámico (reacciona en un evento con otros objetos).

var bodyDef = new b2BodyDef;
bodyDef.type = b2Body.b2_staticBody;
   
// positions the center of the object (not upper left!)
bodyDef.position.x = CANVAS_WIDTH / 2 / SCALE;
bodyDef.position.y = CANVAS_HEIGHT / SCALE;

La forma: define el tipo de objeto referente a la geometría (circulo, cuadrado...).

fixDef.shape = new b2PolygonShape;

// half width, half height.
fixDef.shape.SetAsBox((600 / SCALE) / 2, (10/SCALE) / 2);  

Habiendo definido el suelo este puede ser adherido al mundo:

world.CreateBody(bodyDef).CreateFixture(fixDef);

Un objeto que cae

Luego de haber creado el mundo podemos crear un objeto y agregarlo a este a determinada altura sobre el suelo, el resto lo hace la gravedad!! En el siguiente fragmento vemos como se crea un objeto tipo caja cuyas dimensiones y posición dentro del mundo serán aleatorias.

//create object as box 
bodyDef.type = b2Body.b2_dynamicBody;
fixDef.shape = new b2PolygonShape;
fixDef.shape.SetAsBox(
      Math.random() + 0.1 //half width
      ,  Math.random() + 0.1 //half height
    );
//at this point 
bodyDef.position.x = Math.random() * 25;
bodyDef.position.y = Math.random() * 10;
world.CreateBody(bodyDef).CreateFixture(fixDef);

Visualizar la simulación

Box2D permite inspeccionar lo que sucede durante la simulación, para ello hay que especificar la escala con la que se dibujan los objetos (normalmente es 30m) y hacer referencia al Canvas de HTML5.

Código HTML

<canvas id="c" width="640" height="480" style="border: 1px solid black"></canvas>

Código JavaScript

//setup debug draw
var debugDraw = new b2DebugDraw();
debugDraw.SetSprite(document.getElementById("c").getContext("2d")); //canvas reference
debugDraw.SetDrawScale(SCALE);
debugDraw.SetFillAlpha(0.3); //specify the scale into the graphics canvas
debugDraw.SetLineThickness(1.0);
debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);

world.SetDebugDraw(debugDraw);

Ejecutar la simulación

Para ejecutar la simulación se requiere configurar tres fases: la frecuencia de repetición de las imagenes (Frame Rate), el impulso necesario que requiere un objeto para moverse (velocity iteration) y por último la fase de posicionamiento (position iterations) que ajusta las posiciones para prevenir solapamientos o desprendimientos de objetos que están adheridos. Tenga en cuenta que todas estas son una relación entre rendimiento y precisión.

world.Step(
    1 / 60      //frame-rate
    ,  10       //velocity iterations
    ,  10       //position iterations
);

Solo nos queda poner todo junto dentro del loop de animación usando el API rAF.

function update() {
   world.Step(
         1 / 60   //frame-rate
         ,  10    //velocity iterations
         ,  10   //position iterations
   );
   world.DrawDebugData();
   world.ClearForces();
 
   requestAnimFrame(update);
}; // update()
⚠️ **GitHub.com Fallback** ⚠️