Physics part 5

This tutorial will look at different ways to get meaningful information about the collisions in Box2D.

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 contactEdge = body:GetContactList()
while contactEdge do
  local contact = contactEdge.contact
  contactEdge = contactEdge.next
end
or you can iterate all contacts:
local contact = world:GetContactList()
while contact do
  contact = contact:GetNext()
end
Additionally a reference to the "Contact" object is provided when using ContactListeners.

Contact between two bodies

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 b2.Body:IsTouching(other)
  -- iterate contacts
  local contactEdge = self:GetContactList()
  while contactEdge do
    -- look for a specific body
    if contactEdge.other == other then
      -- make sure there's actual contact
      if contactEdge.contact:IsTouching() then
        return true
      end
    end
    contactEdge = contactEdge.next
  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.

Contact points

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)

Using "contact:GetWorldManifold()" and "contact:GetManifold()" functions we can easily determine where a contact point is located in world or local coordinates.

worldManifold = b2.WorldManifold()
contact:GetWorldManifold(worldManifold)
contactPoint1 = worldManifold.points[1]
contactPoint2 = worldManifold.points[2]
Another useful part of the "worldManifold" 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.
-- collision normal
normalVector = worldManifold.normal

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:

manifold = contact:GetManifold()
-- impulse for the first contact point
manifoldPoint1 = manifold.points[1]

-- normal impulse
-- separates or pushes the two fixtures apart and is always positive
normal = manifoldPoint1.normalImpulse
assert(normal >= 0)

-- tangent impulse
-- simulates friction between the two fixtures and could be negative
tangent = manifoldPoint1.tangentImpulse
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".

Collision callbacks

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.
listener = b2.ContactListener()
function listener:PostSolve(contact, impulse)
  -- here we now we know what impulses are being applied
end
world:SetContactListener(listener)
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.

References:
Box2D API by Erin Catto