Physics part 4

This tutorial will focus on the peculiarities and differences of using Box2D in Lua compared to its original C++ version. It should be noted that the official Box2D API documentation is included with the engine in the "Doc/3rd Party" folder.

Gear joints

Gear joints are a little tricky to set up. The general configuration should be:
gearDef = b2.GearJointDef()
gearDef.joint1 = firstJoint
gearDef.joint2 = secondJoint
gearDef.bodyA = firstDynamicBody
gearDef.bodyB = secondDynamicBody
gearDef.ratio = 1
gearDef.collideConnected = false
gear = world:CreateJoint(gearDef)
Let say, we have two dynamic bodies both connected to a static "ground" body.

Two "gears" connected to the ground (static body) using revolute joints.
The two revolute joints are connected to each other using a gear joint.


The set up should looks like:
firstJointDef = b2.RevoluteJointDef()
firstJointDef.bodyA = groundBody
firstJointDef.bodyB = topGearBody
firstJoint = world:CreateJoint(firstJointDef)

secondJointDef = b2.RevoluteJointDef()
secondJointDef.bodyA = groundBody
secondJointDef.bodyB = bottomGearBody
secondJoint = world:CreateJoint(secondJointDef)

gearJointDef = b2.GearJointDef()
gearJointDef.joint1 = firstJoint
gearJointDef.joint2 = secondJoint
gearJointDef.bodyA = topGearBody
gearJointDef.bodyB = bottomGearBody
gearJoint = world:CreateJoint(gearJointDef)
The configuration is a little more complicated when the two joints are connected to same body.

Here, we see a horizontal slider attached to the ground (static body) with a prismatic joint.
The slider has three wheels. Each wheel (dynamic body) is connected to the slider with a revolute joint.
Lastly, each wheel is connected to the slider with a gear joint.

prismaticDef = b2.PrismaticJointDef()
prismaticDef.bodyA = groundBody
prismaticDef.bodyB = sliderBody
prismatic = world:CreateJoint(prismaticDef)

revoluteDef = b2.RevoluteJointDef()
revoluteDef.bodyA = sliderBody
revoluteDef.bodyB = wheelBody
revolute = world:CreateJoint(revoluteDef)

gearDef = b2.GearJointDef()
gearDef.joint1 = prismatic
gearDef.joint2 = revolute
gearDef.bodyA = sliderBody
gearDef.bodyB = wheelBody
gear = world:CreateJoint(gearDef)
Some reasons why your setup may not work or perhaps lead to a crash:
1.make sure the dynamic body or bodies do not have fixed rotation when using revolute joints
2.make sure your gear joint is created after and destroyed before the other involved joints

Box2D constants and variables

There are several global variables that you should be aware of when using Box2D. b2.maxTranslation and b2.maxRotation show the maximum linear/angular velocity that your bodies can have. This is important since it affects how the physics simulation looks especially with large scale objects. Apart from the above-mentioned constants, there are a few globals that can be edited from Lua:

VariableDescription
b2.angularSlopA small angle used as a collision and constraint tolerance.
b2.linearSlopA small length used as a collision and constraint tolerance.
b2.toiSlopContinuous collision detection (CCD) works with core, shrunken shapes. This is the amount by which shapes are automatically shrunk to work with CCD. This must be larger than b2_linearSlop.

Concave polygonal shapes

Box2D accepts convex polygons with a counter clockwise winding. The global constant b2.maxPolygonVertices shows the maximum number of vertices that a polygon shape may have. If you want to use a concave polygon shape the best route is to divide it into a number of triangles. We have exported an extra function called b2.Fill that can triangulate almost any polygonal shape. b2.Fill takes in a contour (represented as a table of b2.Vec's) and outputs a list of triangles.

-- an example concave shape
local vertices = {}
vertices[1] = b2.Vec2 ( 0, 0 )
vertices[2] = b2.Vec2 ( -10, 10 )
vertices[3] = b2.Vec2 ( 0, 5 )
vertices[4] = b2.Vec2 ( 10, 10 )

-- triangulating the concave shape
local out = {}
local ret = b2.Fill ( vertices, out )
if ret == false then
  error ( "Triangulation failed" )
end

-- adding the output triangles to a b2Body
local triangles = {}
local def = b2.PolygonShape ( )
def.vertexCount = 3

fix.shape = def
for i = 1, #out - 1, 3 do
  local t = { out[i], out[i + 1], out[i + 2] }
  def:Set(t)
  local b2shape = b2body:CreateFixture ( fix )
  if b2shape == nil then
    assert ( b2shape, "Polygon was partially generated" )
    return
  end
  table.insert ( triangles, b2shape )
end

Note that Box2D has some constraints regarding the type of shapes that it accepts. For example, Box2D might produce an error when supplied with a triangle one of whose interior angles is less than 0.0001 degrees.

Accumulators

According to the official manual, Box2D should be updated with a constant time step. To keep the simulation as smooth as possible on varying machines, it is recommended to use "accumulators". Instead of describing to you what an "accumulator" is, let's look at an example of how it works. The following code snippet uses 16 milliseconds as the time step interval. You may use a different value for your game, however it should not be changed once the simulation is running.

accumulator = 0

function Update ( delta )
  accumulator = accumulator + delta

  while accumulator > 0.016 do
    world:Step ( 0.016, 10, 8 )
    accumulator = accumulator - 0.016
  end

  -- update graphics after updating the simulation
  LerpSprites ( accumulator )
end

Using accumulators makes rendering moving bodies a bit more complicated. Since the game state is updated with a fixed time step, not all of "delta" will be used during a single cycle. This may cause staggering in your frame rate. To produce smooth, frame-independent motion, the tiny remaining delta value (which will be smaller than the time step, but usually greater than zero) has to be passed to our drawing functions so that they can interpolate the future position of moving objects.

function LerpSprites ( interpolation )
  -- sync the sprites of all bodies
  for i, body in pairs ( bodies ) do
    -- interpolate position
    local pt = body:GetPosition ( )
    local lv = body:GetLinearVelocity ( )
    body.sprite.x = pt.x + lv.x * interpolation
    body.sprite.y = pt.y + lv.y * interpolation

    -- interpolate angle (in radians)
    local a = body:GetAngle ( )
    local av = body:GetAngularVelocity ( )
    -- angle is negated since AGen sprites rotate clockwise
    body.sprite:set_rotation_r ( -( a + av * interpolation ) )
  end
end