2DEngine

Box2D physics tutorial

Contents

  1. Basics
    1. Debug drawing
    2. Simplifying the API
    3. Fixed timestep
  2. Collisions
    1. Contact lists
    2. Normal and tangent impulses
    3. Torque and centers of mass
  3. Common questions
    1. Is the body moving?
    2. Is the body rotating?
    3. Is the body moving along an axis?
    4. Is the body pushed along an axis?
    5. Is the fixture flat on its side?
    6. What is the "right" coefficient of restitution?
    7. What is the relative velocity of any two bodies?
    8. What is the relative velocity at the moment of collision?
    9. How much energy is involved in a collision?

Box2D is the library hiding under the hood of love.physics. In this tutorial, we'll learn how to use Box2D like a pro!

Basics

Debug drawing

First off, we need a reliable way to draw our Box2D simulation on the screen. This is very important for debugging purposes and general feedback. One easy approach is to use the debug registry to inject our custom "draw" functionality into the existing Box2D objects. Save the following piece of code as "b2draw.lua" and drop it in your game folder:

local reg = debug.getregistry()
local lg = love.graphics

-- World
function reg.World:draw()
  local bodies = self:getBodies()
  for _, v in ipairs(bodies) do
    v:draw()
  end
  lg.setColor(1,0,0,1)
  local joints = self:getJoints()
  for _, joint in ipairs(joints) do
    local x1, y1, x2, y2 = joint:getAnchors()
    if joint.getGroundAnchors then
      local x3, y3, x4, y4 = joint:getGroundAnchors()
      lg.line(x1, y1, x3, y3, x4, y4, x2, y2)
    else
      lg.line(x1, y1, x2, y2)
    end
  end
end

-- Body
function reg.Body:draw()
  local x, y = self:getPosition()
  local r = self:getAngle()
  lg.push()
  lg.translate(x, y)
  lg.rotate(r)
  local fixtures = self:getFixtures()
  for _, fixture in ipairs(fixtures) do
    local shape = fixture:getShape()
    shape:draw()
  end
  lg.pop()
end

-- Shape
function reg.CircleShape:draw()
  local x, y = self:getPoint()
  local r = self:getRadius()
  lg.circle("line", x, y, r, 32)
  lg.line(x, y, x + r, y)
end

function reg.PolygonShape:draw()
  lg.polygon("line", self:getPoints()) 
end

function reg.ChainShape:draw()
  lg.line(self:getPoints()) 
end

function reg.EdgeShape:draw()
  lg.line(self:getPoints()) 
end

"b2draw" makes drawing our physics simulation super easy! However, Box2D is based on the metric system so some scaling may be required. A circle shape with a radius of 100 is simulated as if its radius is 100 meters. 100 meters is nearly the length of a football field and is probably not the ideal size to simulate a bouncing ball. To keep the simulation realistic, we need to stick to everyday-sized objects. For example, a soccer ball is around 0.1 meters in radius. Rendering such tiny shapes in an 800 by 600 window requires scaling your sprites or using a "camera". Alternatively you could use love.physics.setMeter.

Simplifying the API

Since Box2D was exported almost verbatim from C++, some of its syntax looks unattractive when programming in Lua. For example, creating new shapes and fixtures can be quite tedious. To simplify this process we are going to inject some additional Lua functions. Save the following piece of code as "b2access.lua":

local reg = debug.getregistry()
local lp = love.physics

local newBody = lp.newBody
function reg.World:newBody(x, y, t)
  return newBody(self, x, y, t)
end

local destroyBody = reg.Body.destroy
function reg.World:destroyBody(b)
  destroyBody(b)
end

local destroyJoint = reg.Joint.destroy
function reg.World:destroyJoint(j)
  destroyJoint(j)
end

local newPolygonShape = lp.newPolygonShape
local newFixture = lp.newFixture
function reg.Body:newPolygon(...)
  local s = newPolygonShape(...)
  return newFixture(self, s)
end

local newRectangleShape = lp.newRectangleShape
function reg.Body:newBox(w, h, lx, ly, la)
  lx, ly = lx or 0, ly or 0
  la = la or 0
  local s = newRectangleShape(lx, ly, w*2, h*2, la)
  return newFixture(self, s)
end

local newCircleShape = lp.newCircleShape
function reg.Body:newCircle(radius, lx, ly)
  lx, ly = lx or 0, ly or 0
  local s = newCircleShape(lx, ly, radius)
  return newFixture(self, s)
end

local newChainShape = lp.newChainShape
function reg.Body:newChain(vertices, loop)
  local s = newChainShape(loop, vertices)
  return newFixture(self, s)
end

local newEdgeShape = lp.newEdgeShape
function reg.Body:newEdge(x1, y1, x2, y2)
  local s = newEdgeShape(x1, y1, x2, y2)
  return newFixture(self, s)
end

local newRevoluteJoint = lp.newRevoluteJoint
function reg.World:newRevoluteJoint(a, b, x, y, cc)
  return newRevoluteJoint(a, b, x, y, cc)
end

local newPrismaticJoint = lp.newPrismaticJoint
function reg.World:newPrismaticJoint(a, b, x, y, ax, ay, cc)
  return newPrismaticJoint(a, b, x, y, ax, ay, cc)
end

local newDistanceJoint = lp.newDistanceJoint
function reg.World:newDistanceJoint(a, b, p1x, p1y, p2x, p2y, cc)
  return newDistanceJoint(a, b, p1x, p1y, p2x, p2y, cc)
end

local newRopeJoint = lp.newRopeJoint
local sqrt = math.sqrt
function reg.World:newRopeJoint(a, b, p1x, p1y, p2x, p2y, l, cc)
  if l == nil then
    local lx, ly = p1x - p2x, p1y - p2y
    l = sqrt(lx*lx + ly*ly)
  end
  return newRopeJoint(a, b, p1x, p1y, p2x, p2y, l, cc)
end

local newPulleyJoint = lp.newPulleyJoint
function reg.World:newPulleyJoint(a, b, p1x, p1y, p2x, p2y, p3x, p3y, p4x, p4y, ratio, cc)
  return newPulleyJoint(a, b, p1y, p2x, p2y, p3x, p3y, p4x, p4y, ratio, cc)
end

local newGearJoint = lp.newGearJoint
function reg.World:newGearJoint(a, b, j1, j2, ratio, cc)
  return newGearJoint(j1, j2, ratio, cc)
end

local newWeldJoint = lp.newWeldJoint
function reg.World:newWeldJoint(a, b, p1x, p1y, cc)
  return newWeldJoint(a, b, p1x, p1y, cc)
end

local newFrictionJoint = lp.newFrictionJoint
function reg.World:newFrictionJoint(a, b, p1x, p1y, cc)
  return newFrictionJoint(a, b, p1x, p1y, cc)
end

local newWheelJoint = lp.newWheelJoint
function reg.World:newWheelJoint(a, b, p1x, p1y, ax, ay, cc)
  return newWheelJoint(a, b, p1x, p1y, ax, ay, cc)
end

local newMouseJoint = lp.newMouseJoint
function reg.World:newMouseJoint(a, x, y, mf)
  local joint = newMouseJoint(a, x, y)
  joint:setMaxForce(mf)
  return joint
end

The code for creating new fixtures and shapes becomes much shorter and cleaner thanks to "b2access.lua". This is all achieved with barely any effect on performance.

require("b2draw")
require("b2access")

function love.load()
  world = love.physics.newWorld()
  body = world:newBody(100, 100)
  fixture = body:newCircle(10)
end

function love.update(dt)
  world:update(dt)
end

function love.draw()
  world:draw()
end

Fixed timestep

According to the official manual, Box2D should be updated with a constant or "fixed" time step. To keep the simulation as smooth as possible on varying machines, it is recommended to use accumulators. Let's look at an example of how it works. The following code snippet uses 1/60 = 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
interval = 1/60
maxframeskip = 10

function love.update(dt)
  accumulator = accumulator + dt
  accumulator = math.min(accumulator, maxframeskip*interval)
  while accumulator >= interval do
    world:update(interval)
    accumulator = accumulator - interval
  end
end

Note that when using accumulators, not all of "delta" will be used during a single cycle. Some people use this tiny left-over "delta" to interpolate while drawing:

function love.draw()
  -- sync the sprites of all bodies
  local bodies = world:getBodies()
  for i, body in ipairs(bodies) do
    -- interpolate position
    local x, y = body:getPosition()
    local lvx, lvy = body:getLinearVelocity()
    x = x + lvx*accumulator
    y = y + lvy*accumulator
    -- draw body
    ...
  end
end

Collisions

Contact lists

The "Contact" object gives us plenty of information about potentially colliding fixtures. Each body in Box2D keeps a list of its potential contacts that you can iterate at any time:
local contacts = body:getContacs()
for i, contact in ipairs(contacts) do
  ...
end
or you can iterate all contacts:
local contacts = world:getContacts()
Additionally a reference to the "Contact" object is provided when using collision callbacks.

Contact lists may contain potential collisions of fixtures that may not be touching at all. Using the "contact:IsTouching()" function tells us if there an actual collision.

function reg.Body:IsTouching(other)
  -- iterate contacts
  local contacts = self:getContacts()
  for i, contact in ipairs(contacts) do
    -- make sure there's actual contact
    if contact:IsTouching() then
      -- look for a specific body
      local f1, f2 = contact:getFixtures()
      if f1:getBody() == other or f2:getBody() == other then
        return true
      end
    end
  end
  return false
end
Note that the code above returns true even if there is no "solid" contact. Non-solid contact occurs when one or both of the contacting fixtures is a "sensor". Therefore, the approach shown above is pretty good if you want to add "sensor triggers" in your game.

A contact between two fixtures may have 2, 1 or 0 contact points. As mentioned above, with "non-solid" contact (involving one or two sensor fixtures) there are 0 contact points. When a circle collides with another fixture or a polygon vertex hits an edge, we always get 1 contact point. When there is an edge-to-edge collision between two polygons, we may get 2 contact points.

Three different contacts with the contact points shown in white
Left: circle with 1 contact (1 point)
Center: triangle with 1 contact (1 point, vertex to edge)
Right: rectangle with 1 contact (2 points, edge to edge)

Keeping in mind that all fixtures in Box2D are convex, it's easy to realize that there cannot be more than 2 contact points between the same pair of fixtures. However, one fixture may be in contact with two or more other fixtures. Therefore a body can have several contacts acting upon it at the same time:

Four contacts with the contact points shown in white
Left: circle with 2 contacts (1 point each)
Right: rectangle with 2 contacts (1 point each)

Another useful vector is the "collision normal" (see yellow lines in the figures above). Each contact has a "collision normal" which is basically the "axis of shortest separation". In layman terms, it's a (normalized) vector describing the direction in which the two fixtures are "pushing" each other.

Normal and tangent impulses

You have probably heard of the formula F=ma:
force = mass*acceleration
acceleration = changeInVelocity/time
changeInVelocity = finalVelocity - initialVelocity
An impulse is similar, but with "time" removed from the equation:
impulse = mass*changeInVelocity
changeInVelocity = finalVelocity - initialVelocity
So you can think of an "impulse" as an instant change in velocity of an object times its mass.

Each contact point has an impulse associated with it. Box2D describes the magnitude of these impulses in two parts. The direction of these impulses is determined by the "collision normal".
As you can see from the following figures, normal impulses (shown in yellow) push the two fixtures apart so that they are not inter-penetrating.


Rectangle going down an inclined slope.
Normal impulse shown in yellow and tangent impulse shown in green.
Left: with friction (1)
Right: without friction (0)


Tangent impulses are applied at 90-degrees relative to the "collision normal".
Tangent impulses are determined by the "friction" of fixtures and can cause the body to spin and roll.


Circle going down an inclined slope.
Normal impulse shown in yellow and tangent impulse shown in green.
Left: with friction (1)
Center: without friction (0)


Remember: if you want your circles to "roll" make sure to give them a "friction" value greater than 0! Otherwise, they will glide down awkwardly while remaining upright.

Torque and centers of mass

Box2D uses impulses (changes in momentum) to resolve collisions between bodies.
impulse = changeInVelocity*mass
changeInVelocity = impulse/mass
Keep in mind that the exact point where an impulse is being applied is important too. The location of the contact point along with the center of mass of each body may produce torque and cause the body to spin.

Collisions of falling objects. Impulses shown in yellow, centers of mass shown in blue and contact points in white.
Left: circle with the contact point and impulse in line with the center of mass (no resulting torque)
Right: rectangle with the contact point off the side to the center of mass (torque spins the rectangle counter-clockwise)


Collisions in Box2D are solved in sub-steps so we need to use the "BeginContact", "PreSolve" and "PostSolve" callbacks or else we might miss the actual impulses that were applied to each body. Remember that resting contacts are also solved using impulses so for our purposes we need to distinguish between an "impact" and "resting contact".

Iterating the contact lists is great when you are interested in the "resting" contact between fixtures. Sometimes however collisions may occur "between frames". If you want to know how much impulse is applied in such collisions, you probably want to use a ContactListener with the "PostSolve" callback.
One downside is that the "PostSolve" callback may be evoked many times during a single update step. I don't recommend "PostSolve" unless you need to know every impulse that is applied between fixtures.

Common questions

When making games with Box2D, one natural question that arises is: "what can we determine about the general state of a body given its velocity and collision information"?

Is the body moving?

One approach is to compare the linear velocity of the body against a given threshold. This method returns true for a body resting on top of a moving platform.
function reg.Body:isMoving(treshold)
  treshold = treshold or 0
  local lvx, lvy = self:getLinearVelocity()
  return (lvx*lvx + lvy*lvy) > treshold*treshold
end

Is the body rotating?

By looking at the angular velocity of a body we can determine if it's spinning. Note that the angular velocity could be negative. This approach would always return false for bodies with "fixed rotation".
function reg.Body:isRotating(treshold)
  treshold = treshold or 0
  local angular = self:getAngularVelocity()
  return angular < -treshold or angular > treshold
end

Is the body moving along an axis?

This is a common question, when you want to find out if an object is "falling". We can use the dot product to check for movement along a given axis (ax, ay). For example, you can use the gravity vector as an axis.
function reg.Body:isMovingOnAxis(ax, ay)
  local lvx, lvy = self:getLinearVelocity(lv)
  return ax*lvx + ay*lvy > 0
end

Is the body pushed along an axis?

Using the contact information, we can check if a body is "pushed" by other bodies in a given direction. This applies for resting contact too. Suppose that we have a rectangle supported on top of a moving platform. Using the gravity axis, we check if the platform is "pushing up" the rectangle. This is one of the most accurate way to tell if a given body is "on the ground".
function reg.Body:isPushedOnAxis(ax, ay)
  local contacts = self:getContacts()
  for i, contact in ipairs(contacts) do
    if contact:IsTouching() then
      local x1, y1, y2, y2 = contact:getPositions()
      if x1 and y1 then
        local nx, ny = contact:getNormal()
        local f1, f2 = contact:getFixtures()
        local other = f1:getBody()
        if other ~= self then
          nx, ny = -nx, -ny
        end
        if ax*nx + ay*ny > 0 then
          return true
        end
      end
    end
  end
  return false
end
One further refinement could be to compute the total impulse acting on the body along the given axis. This can help us determine how firmly the body is being supported.

Is the fixture flat on its side?

Consider again the example of a rectangle resting on top of a platform. The number of points in each contact tells us how the body is being supported. If there is one point, the rectangle is supporting itself by a single vertex. If there are two points, the rectangle is flat on its side. There cannot be more than two contact points between a pair of (convex) fixtures.

Rectangle supported by a platform. Contact points shown in white and collision normal in blue
Left: 1 contact with 1 point (vertex to edge)
Right: 1 contact with 2 points (edge to edge)


Note that it's important to consider the direction of each collision normal in order to determine if a contact is pushing the body up, down, sideways or both! One fixture can be in contact with several others, so it's possible for the poor rectangle to be "sandwiched" or pushed in opposite directions at the same time.

What is the "right" coefficient of restitution?

The restitution coefficient affects colliding bodies along the normal axis and is described as "the degree of relative kinetic energy retained after a collision". Box2D provides the following function to get the restitution between two colliding fixtures:
restitution = contact:getRestitution()
Depending on the restitution, we can categorize collisions in three types:

Perfectly elastic (restitution = 1)
No kinetic energy is lost so there is no sound or damage caused to the colliding objects.
Example: perfectly elastic ball that can bounce forever

Elastic (restitution > 0 and restitution < 1)
Some kinetic energy is converted into heat, sound or causes deformation.
Example: bouncing basketball

Inelastic (restitution = 0)
A lot of kinetic energy is converted into heat, sound or causes deformation.
The colliding objects remain together after the impact.
Example: ball made of soft clay that sticks to floor when dropped

Generally, momentum and energy are always conserved when dealing with a closed system. With Box2D, this is not particularly true for example when using "static" bodies with 0 restitution. Also note that, simulating deformation is beyond the scope of both Box2D and this tutorial.

As a general reference, let's look at the restitution coefficients of different types of balls:
0.858 golf ball
0.804 billiard ball
0.712 tennis ball
0.658 glass marble
0.597 steel ball bearing

What is the relative velocity of any two bodies?

One simple approach is to subtract the linear velocities of the two bodies (assuming that their rotation or torque is insignificant).
velocity1 = firstBody:getLinearVelocity()
velocity2 = secondBody:getLinearVelocity()
velocityDiff = velocity1 - velocity2
Next, we find how fast the two bodies are moving towards each other, given the their "difference in velocity" and position.
-- direction vector
direction = firstBody:getPosition() - secondBody:getPosition()
directionNormal = normalize(direction)
-- relative speed (in Meters per second)
relativeSpeed = dotProduct(velocityDiff, directionNormal)
The resulting "relative speed" is:
  • positive when one or both bodies are moving towards each other,
  • zero when both bodies are immobile or moving together in parallel,
  • negative when one or both bodies are moving away from each other.


    The relative velocity in one dimension: two cars located at two positions (p1 and p2) moving at speeds (v1 and v2).
    direction = p1 - p2
    directionNormal = direction/abs(direction)
    velocityDiff = v1 - v2
    relativeSpeed = velocityDiff*directionNormal

    What is the relative velocity at the moment of collision?

    Collisions in Box2D are resolved when the "world:Step" function is called. That's why there is the "ContactListener" class - so our game is notified of collision events that happen during "world:Step".

    The "ContactListener" class has four callbacks: "BeginContact", "EndContact", "PreSolve" and "PostSolve". Just remember that you really, really want to be using "Pre-" and "PostSolve" to buffer only the most essential collision data. Bodies and fixtures that are irrelevant to your game should be ignored altogether. Collision callbacks may be evoked several times per fixture which could easily amount to hundreds, even thousands of calls per frame.

    The following method should work best during the "BeginContact" or "PreSolve" callbacks, before the velocity of the body is modified by Box2D. Given a contact point between two fixtures, Chris Campbell shows us a clever way to determine the "relative velocity of the contact points on each body". Campbell's approach is useful because it implicitly takes into account the bodies' angular velocity.
    function preSolve(contact)
      local x1, y1, x2, y2 = contact:getPositions()
      if x1 and y1 then
        local f1, f2 = contact:getFixtures()
        local b1 = f1:getBody()
        local b2 = f2:getBody()
        -- Campbell's method
        local lvx1, lvy1 = b1:getLinearVelocityFromWorldPoint(x1, y1)
        local lvx2, lvy2 = b2:getLinearVelocityFromWorldPoint(x1, y1)
        -- velocity difference vector
        local dvx, dvy = lvx1 - lvx2, lvy1- lvy2
        -- impact speed (in Meters per second)
        local nx, ny = contact:getNormal()
        local impactSpeed = dvx*nx + dvy*ny -- dot product
    When used in the "PreSolve" callback, the result is the relative speed of the contact points at the moment of impact! When we know the "impact speed" it's possible to estimate the impulse which will later be applied to the body during the "PostSolve" callback. Again, remember that "impact speed" is actually the relative velocity of the points in contact. When torque is involved, it could be different than the linear velocity of the body.

    Contact points shown in white, and their velocities shown in green.
    Left: with torque
    Right: without torque