A physics engine (code library) typically provides at minimum the ability to simulate rigid objects under constraints, such as keeping the objects from interpenetrating significantly or respecting joints between rigid objects. Rigid objects are different from the particles treated in introductory physics in that they are extended in space and have an orientation as well as a position. This makes even the most basic effects (e.g. apply a single force to the object) somewhat more complex. Additionally, rigid objects can lean upon one another, which is very difficult to treat correctly.
A physics engine constructs a second simulation within your simulation. It requires its own representation of each object relevant to the physics, which may be simplified in shape to reduce computational cost, and which contains additional information needed for physics, e.g. the mass of the object. As time goes by, the physics engine simulation is updated, and the resuting changes in object position and orientation must be brought over to the visual simulation.
The two most difficult (from the code complexity and also run time complexity point of view) both have to do with objects in contact with each other. The first is "collision detection", i.e. detecting which pairs of objects are in contact. Because collision detection is so computationally expensive, it is typically broken up into at least two phases, the "broad phase" and the "narrow phase". The goal of the broad phase is to determine which pairs of objects are close enough to potentially be in contact. The detailed computation of which of these pairs are in actual contact is left to the narrow phase.
The second difficult task is determining the implicit forces that arise when multiple objects are in contact. It is obvious that objects in contact must be applying forces to each other that keep them from interpenetrating. But computing these forces is anything but simple, and in fact must be done by considering each arbitrarily large set of objects in mutual contact as a group by running an expensive optimization algorithm. It is worth noting that if the objects are allowed to move too far in a single frame, they may interpenetrate so badly that the forces applied by the engine to reduce the problem may in fact blow the objects far away from one another.
This example is a simple physics-based simulation of a falling block written using Ammo. Ammo is a JavaScript port of the open source C++ Bullet library, so the API is very similar, but note that
myPhysicsObj
has a field m_someData
, you should try to access it via myObj.get_m_someData()
The physics engine is initialized by this code block which creates the collision detection and dynamics update infrastructure for the simulation. The primary end result is the dynamicsWorld
object that will actually do the physics simulation.
var collision_config = new Ammo.btDefaultCollisionConfiguration();
var dispatcher = new Ammo.btCollisionDispatcher( collision_config );
var overlappingPairCache = new Ammo.btDbvtBroadphase();
var solver = new Ammo.btSequentialImpulseConstraintSolver();
var dynamicsWorld = new Ammo.btDiscreteDynamicsWorld( dispatcher, overlappingPairCache, solver, collision_config );
dynamicsWorld.setGravity(new Ammo.btVector3(0, -12, 0));
In the case of this simple simulation, there are just two objects taken into account by the physics, the block and the ground. This block of code initializes the ground and adds it to dynamicsWorld
.
var groundShape=new Ammo.btBoxShape(new Ammo.btVector3(20,10,20));
var groundTransform=new Ammo.btTransform();
groundTransform.setIdentity();
groundTransform.setOrigin(new Ammo.btVector3(0,-25,0));
var localInertia=new Ammo.btVector3(0,0,0);
var motionState = new Ammo.btDefaultMotionState( groundTransform );
var cInfo = new Ammo.btRigidBodyConstructionInfo(0, motionState, groundShape, localInertia);
var body=new Ammo.btRigidBody(cInfo);
dynamicsWorld.addRigidBody(body);
This code is very typical of how the physics simulation is informed about an object. Note particularly the btBoxShape
call that informs the sim about the shape of the object. There are several other calls appropriate for various standard and user specified shapes, but simpler is always better in terms of making the physics run as quickly as possible.
Note also how the variable localInertia
is set to a vector of zeros, i.e. to have no mass. This is how the simulation is informed that this object does not move. Moving objects must have a non-zero moment of inertia (3x3) matrix. If the object is one of the standard shapes, Ammo will calculate the matrix for you from the total mass as is done for the block:
boxShape.calculateLocalInertia(mass,localInertia);
Once the engine is intialized and has been made aware of the properties of all objects to be simulated, the physics is periodically updated, and the results used to drive the graphics. The update is performed by the call dynamicsWorld.stepSimulation(dt,1)
. The one indicates that the engine is to update the physics in only a single step (multiple steps are slower but may be more stable). Afterwards, the transform of each moving object must be extracted from the physics engine and applied to the graphical objects, converting it into the form required for the graphics engine along the way. The helper function b2three
does most of the work for you.