Box2D has a number of 'joints' that can be used to connect two bodies together. These joints can be used to simulate interaction between objects to form hinges, pistons, ropes, wheels, pulleys, vehicles, chains, etc. Learning to use joints effectively helps to create a more engaging and interesting scene.

Let's take a quick look at the available joints, then go over their characteristics, then finally we'll make an example using a few of the most commonly used ones. Here are the joints in Box2D v3.0:

  • Revolute - a hinge or pin, where the bodies rotate about a common point
  • Distance - a point on each body will be kept at a fixed distance apart
  • Prismatic - the relative rotation of the two bodies is fixed, and they can slide along an axis
  • Wheel - useful for modelling vehicle suspension (renamed from Line joint)
  • Weld - holds the bodies at the same orientation
  • Motor - controls relative motion between two bodies
  • Mouse - pulls a point on one body to a location in the world

Creating a joint

Joints are created by setting up a definition object which is then used to create the joint instance. The Box2D world manages the actual construction of the joint instance, and when you are done with it you tell the world to destroy it. Here's how the typical creation of a joint goes:

// Set up the definition for a joint
const jointDef = b2DefaultXXXJointDef(); // XXX is the joint type
jointDef.bodyIdA = bodyIdA;
jointDef.bodyIdB = bodyIdB;
// Set other joint properties...

// Create the joint
const jointId = b2CreateXXXJoint(world, jointDef);

The main difference from v2.X is that instead of class instances, v3.0 uses IDs to reference joints. When creating a joint, you'll get back a jointId that you can use to control the joint later.

Joint definitions - common settings

Although each joint has different behavior, they share some common properties in their definitions:

  • bodyIdA - one of the bodies joined by this joint (required!)
  • bodyIdB - the other body joined by this joint (required!)
  • collideConnected - specifies whether the two connected bodies should collide with each other

The two body settings are obviously the bodies this joint will act on. In some cases it does make a difference which is which, depending on the joint type. See the discussion of each joint for details.

The collideConnected setting allows you to say whether the two bodies should still obey the normal collision rules or not. For example if you are making a rag-doll, you'll probably want to let the upper leg and lower leg segments overlap at the knee, so you would set collideConnected to false. If you are making an elevator platform, you'll probably want the platform to collide with the ground, so collideConnected would be true. The default value is false.

Here's an example:

const jointDef = b2DefaultRevoluteJointDef();
jointDef.bodyIdA = upperLegBody.bodyId;
jointDef.bodyIdB = lowerLegBody.bodyId;
jointDef.collideConnected = false;

Joint definitions - specific settings

After setting the common fields in a joint definition, you'll need to specify the details for the type of joint you are making. This commonly includes an anchor point on each body, limits on the range of movement, and motor settings. These need to be discussed in detail for each joint since they are used slightly differently, but here is a general overview of what these terms mean.

  • anchor points: Typically a point on each body is given as the location around which the bodies must interact. Depending on the joint type, this point will be the center of rotation, the locations to keep a certain distance apart, etc.
  • joint limits: Revolute and prismatic joints can be given limits, which places a restriction on how far the bodies can rotate or slide.
  • joint motors: Revolute, prismatic and wheel joints can be given motor settings, which means that instead of spinning or sliding around freely, the joint acts as if it had its own power source. The motor is given a maximum force or torque, and this can be used in combination with a target velocity to spin or slide bodies in relation to each other. If the velocity is zero, the motor acts like a brake because it aims to reduce any motion between the bodies.

Controlling joints and getting feedback

After a joint is created you can alter the parameters for its behavior such as changing the motor speed, direction or strength, enabling or disabling the limits. This can be very useful to build a range of interesting scenes and contraptions, for example wheels to drive a car, an elevator which moves up and down, a drawbridge that opens and closes.

You can also get information about what position the joint is at, how fast it is moving etc. This is useful if you want to use the activity of the joint in your game logic. You can also get the force and torque that the joint is applying to the bodies to keep them in the right place, which can be useful if you want to allow the joint to break when it sustains too much force.

Cleaning up

To destroy a joint, call:

b2DestroyJoint(jointId);

Joints are also automatically destroyed when one of the bodies they connect is destroyed. There is no point in having a joint which doesn't connect anything. This means that if you are changing joints during the simulation, you need to keep in mind the order in which you delete bodies and joints. For example note the differences between these situations:

// 'jointId' connects body1 and body2

// BAD!
b2DestroyBody(body1.bodyId); // jointId becomes invalid here
b2DestroyJoint(jointId); // crash

// OK
b2DestroyJoint(jointId);
b2DestroyBody(body1.bodyId);

Generally if you know the game logic, and when joints are created and how objects are likely to be destroyed, you can arrange your cleanup routines to avoid the first case above. However if you are working on a complex project you might find it easier to use the event system provided by Box2D. This allows you to receive notifications when any joint is destroyed so you know not to use it any more.

Trouble with joint positions

Based on the provided text and API reference, I'll maintain the original explanatory content while updating any code examples to use the Box2D v3.0 JavaScript API style. Since this chunk doesn't contain any code examples, I'll keep the text as-is since it remains accurate for the v3.0 API:

Sometimes you might find that a joint has difficulty keeping the two anchor points in the same place. This has to do with the way the bodies are corrected by the joint. The joint calculates the impulse necessary to push the bodies back into the right alignment, and this impulse is applied to each body with the heavier body being given less of the impulse, and the lighter body being given more.

If there are no other restrictions present (eg. where the two bodies are only interacting with each other) this is fine, and the bodies move as expected according to the impulses. However when another constraint is applied, this balancing of the impulse can be upset by the action of other impulses, usually those acting on the lighter of the two bodies. This is typically seen when a light body has two heavier bodies either joined to it or simply pressing against it. In fact, the same problem can be seen with simple collision resolution where a light body is sandwiched between two significantly heavier bodies, so this issue is not solely a problem with joints.

a heavy box on a light box, a car body held up by the wheels the unequal forces for a heavy object on a light object

Because it is light, it is affected most by the correcting impulse, and the heavier body on top is not corrected very much. The result is that it can take many time steps for the mis-alignment (or overlap in the case of a collision) to be resolved. Another very common situation where this occurs is when a chain is made from light segments, and then a heavy body is attached to the end - in that case the light bodies get 'stretched' rather than squashed as above, but the cause of the problem is the same.

The simplest way to overcome this problem is to make bodies that must interact with each other in this way have similar masses. If this seems unreasonable bear in mind that in the real world nothing is perfectly rigid so we never really see this situation occurring, and ideally I guess the object in the middle should be crushed somehow but that's a tricky thing to program. In the case where a heavy object hangs on a chain of light segments, this too in the real world results in either a deformation of the segments or a breakage of the chain altogether (fortunately that's not so tricky to program).

Details on how to use Box2D joints

While joints have some things in common, the differences between them are too great to cover in one topic. We will next cover two specific types of joint in greater detail.

Credits

This tutorial is adapted from an original piece of work created by Chris Campbell and is used under license.