The revolute joint can be thought of as a hinge, a pin, or an axle. An anchor point is defined on each body, and the bodies will be moved so that these two points are always in the same place, and the relative rotation of the bodies is not restricted.

Revolute joints can be given limits so that the bodies can rotate only to a certain point. They can also be given a motor so that the bodies will try to rotate at a given speed, with a given torque. Common uses for revolute joints include:

  • wheels or rollers
  • chains or swingbridges (using multiple revolute joints)
  • rag-doll joints
  • rotating doors, catapults, levers

Creating a revolute joint

A revolute joint is created by first setting the desired properties in a revolute joint definition object, then passing that to createJoint to get a joint ID. We saw in the joints overview that all joint definitions have some properties in common - the two bodies to be joined, and whether they should collide with each other. So we'll set those first:

const jointDef = b2DefaultRevoluteJointDef();
jointDef.bodyIdA = bodyA.bodyId;
jointDef.bodyIdB = bodyB.bodyId;
jointDef.collideConnected = false;

Then we come to a bunch of properties which are specific to revolute joints:

  • localAnchorA - the point in body A around which it will rotate
  • localAnchorB - the point in body B around which it will rotate
  • referenceAngle - an angle between bodies considered to be zero for the joint angle
  • enableLimit - whether the joint limits will be active
  • lowerAngle - angle for the lower limit
  • upperAngle - angle for the upper limit
  • enableMotor - whether the joint motor will be active
  • motorSpeed - the target speed of the joint motor
  • maxMotorTorque - the maximum allowable torque the motor can use

Let's take a look at what these do in more detail.

Local anchors

The local anchor for each body is a point given in local body coordinates to specify what location of the body will be at the center of the rotation. For example, if body A has a 2x2 square polygon shape, and bodyB has a circular shape, and you want the circle to rotate about it's center at one corner of the square...

the connections between bodies using local anchors

...you would need local anchors of (1,1) and (0,0) respectively. So the code for this case would be:

jointDef.localAnchorA = new b2Vec2(1, 1);
jointDef.localAnchorB = new b2Vec2(0, 0);

Note that it is not necessary to have actually have a shape at the joint anchor position. In the example above, the anchor for bodyA could just as easily be made (2,2) or any other location without any problems at all, even if the circle and the box are not touching. Joints connect bodies, not shapes.

You can access the anchor points after creating a joint by using b2RevoluteJoint_GetLocalAnchorA() and b2RevoluteJoint_GetLocalAnchorB(). Remember the returned locations are in local body coordinates, the same as you set in the joint definition.

Reference angle

The reference angle allows you to say what the 'joint angle' between the bodies is at the time of creation. This is only important if you want to use b2RevoluteJoint_GetAngle() later in your program to find out how much the joint has rotated, or if you want to use joint limits.

Typically, you will set up the bodies so that the initial rotation is considered to be the zero 'joint angle'. Here is an example of what to expect from b2RevoluteJoint_GetAngle() in the typical case, and the case where a non-zero referenceAngle is given.

joints which start at different angles

So a typical example is simply as below (the default value is zero anyway so this doesn't actually do anything).

jointDef.referenceAngle = 0;

Note that the joint angle increases as bodyB rotates counter-clockwise in relation to bodyA. If both bodies were moved or rotated in the same way, the joint angle does not change because it represents the relative angle between the bodies. For example, the case below will return the same values for b2RevoluteJoint_GetAngle as the right-hand case above.

the previous image is rotated, the joint has the same angle

Also, please remember that all angles in Box2D are dealt with in radians, I am just using degrees here because I find them easier to relate to :p

Revolute joint limits

With only the properties covered so far the two bodies are free to rotate about their anchor points indefinitely, but revolute joints can also be given limits to restrict their range of rotation. A lower and upper limit can be specified, given in terms of the 'joint angle'.

Suppose you want the joint in the example above to be restricted to rotating within 45 degrees of it's initial setup angle.

a joint showing the upper and lower rotation limits

Remember that counter-clockwise rotation of bodyB means an increase in the joint angle, and angles are in radians:

jointDef.enableLimit = true;
jointDef.lowerAngle = -45 * Math.PI / 180; // Convert degrees to radians
jointDef.upperAngle = 45 * Math.PI / 180;

The default value for enableLimit is false. You can also get or set these limit properties for a joint after it has been created, by using these functions:

// alter joint limits
b2RevoluteJoint_EnableLimit(jointId, enabled);
b2RevoluteJoint_SetLimits(jointId, lower, upper);

// query joint limits
b2RevoluteJoint_IsLimitEnabled(jointId);
b2RevoluteJoint_GetLowerLimit(jointId);
b2RevoluteJoint_GetUpperLimit(jointId);

Some things to keep in mind when using joint limits:

  • The enableLimits settings affects both limits, so if you only want one limit you will need to set the other limit to a very high (for upper limit) or low (for lower limit) value so that it is never reached.
  • A revolute joint's limits can be set so that it rotates more than one full rotation, for example a lower/upper limit pair of -360,360 would allow two full rotations between limits.
  • Setting the limits to the same value is a handy way to 'clamp' the joint to a given angle. This angle can then be gradually changed to rotate the joint to a desired position while staying impervious to bumping around by other bodies, and without needing a joint motor.
  • Very fast rotating joints can go past their limit for a few time steps until they are corrected.
  • Checking if a joint is currently at one of its limits is pretty simple:
    const atLowerLimit = b2RevoluteJoint_GetAngle(jointId) <= b2RevoluteJoint_GetLowerLimit(jointId);
    const atUpperLimit = b2RevoluteJoint_GetAngle(jointId) >= b2RevoluteJoint_GetUpperLimit(jointId);

Revolute joint motor

The default behaviour of a revolute joint is to rotate without resistance. If you want to control the movement of the bodies you can apply torque or angular impulse to rotate them. You can also set up a joint 'motor' which causes the joint to try to rotate at a specific angular velocity. This is useful if you want to simulate powered rotation such as a car wheel or drawbridge type door.

The angular velocity specified is only a target velocity, meaning there is no guarantee that the joint will actually reach that velocity. By giving the joint motor a maximum allowable torque, you can control how quickly the joint is able to reach the target velocity, or in some cases whether it can reach it at all. The behavior of the torque acting on the joint is the same as in the forces and impulses topic. A typical setting might look like this:

jointDef.enableMotor = true;
jointDef.maxMotorTorque = 20;
jointDef.motorSpeed = 360 * Math.PI / 180; // 1 turn per second counter-clockwise

The default value for enableMotor is false. You can also get or set these motor properties for a joint after it has been created, by using these functions:

// alter joint motor
b2RevoluteJoint_EnableMotor(jointId, enabled);
b2RevoluteJoint_SetMotorSpeed(jointId, speed);
b2RevoluteJoint_SetMaxMotorTorque(jointId, torque);

// query joint motor
b2RevoluteJoint_IsMotorEnabled(jointId);
b2RevoluteJoint_GetMotorSpeed(jointId);
b2RevoluteJoint_GetMotorTorque(jointId);

Some things to keep in mind when using joint motors:

Here's the JavaScript conversion of the provided text chunk, using the Box2D v3.0 API style:

  • With a low max torque setting, the joint can take some time to reach the desired speed. If you make the connected bodies heavier, you'll need to increase the max torque setting if you want to keep the same rate of acceleration.
  • The motor speed can be set to zero to make the joint try to stay still. With a low max torque setting this acts like a brake, gradually slowing the joint down. With a high max torque it acts to stop the joint movement very quickly, and requires a large force to move the joint, kind of like a rusted up wheel.
  • The driving wheels of a car or vehicle can be simulated by changing the direction and size of the motor speed, usually setting the target speed to zero when the car is stopped.

Revolute joint example

Okay, let's make some revolute joints in the testbed to try out these settings and see how they work. First lets make as simple a joint as possible. We'll make one like in the first diagram on this page, using a square and a circle shape. Since we have already covered many examples I will no longer be showing the full listing for all the code, but in most cases it's probably convenient to start with a basic 'fenced in' type scene as in some of the previous topics to stop things flying off the screen.

Into the scene we will first need to create the box and the circle bodies. It is very common to place the bodies at the position they will be joined in, but for the purpose of demonstration I will make the bodies start a little apart from each other so we can watch how the joint behaves in this situation.

// Body definition - common parts
const bodyDef = b2DefaultBodyDef();
bodyDef.type = b2BodyType.b2_dynamicBody;

// Create box shape
const boxShape = b2MakeBox(2, 2); // Half-width, half-height of the box
const boxDef = b2DefaultShapeDef();
boxDef.density = 1;

// Make box a little to the left
bodyDef.position = new b2Vec2(-3, 10);
const bodyIdA = b2CreateBody(world, bodyDef);
const shapeA = b2CreatePolygonShape(bodyIdA, boxDef, boxShape);

// Create circle shape
const circleShape = {radius: 2};
const circleDef = b2DefaultShapeDef();
circleDef.density = 1;

// And circle a little to the right
bodyDef.position = new b2Vec2(3, 10);
const bodyIdB = b2CreateBody(world, bodyDef);
const shapeB = b2CreateCircleShape(bodyIdB, circleDef, circleShape);

I have left out a few details such as storing the body IDs as variables, but you are way smarter than to be scratching your head about that by now right? Next we make a revolute joint using the first few properties covered above:

const jointDef = b2DefaultRevoluteJointDef();
jointDef.bodyIdA = bodyIdA;
jointDef.bodyIdB = bodyIdB;
jointDef.collideConnected = false;
jointDef.localAnchorA = new b2Vec2(2, 2); // the top right corner of the box
jointDef.localAnchorB = new b2Vec2(0, 0); // center of the circle
const joint = b2CreateRevoluteJoint(world, jointDef);

Running this you should see the box and circle joined together with the center of the circle at one corner of the box, freely rotating. If you have the 'draw joints' checkbox checked, you will see blue lines are drawn between the body position and the anchor position. You might like to also add something like this in the Step() function so you can confirm the joint angle in real time:

debugDraw.DrawString(5, textLine, `Current joint angle: ${b2RevoluteJoint_GetAngle(joint) * 180 / Math.PI} deg`);
textLine += 15;
debugDraw.DrawString(5, textLine, `Current joint speed: ${b2RevoluteJoint_GetMotorSpeed(joint) * 180 / Math.PI} deg/s`);
textLine += 15;

If you pause the simulation and restart it, then click 'single step', you will be able to see that for a brief moment, the two bodies are in their initial setup positions, and then the joint constraints take effect.

two bodies start separated with a link between their anchors, they jump to connect at the joint

Remember that since joints cannot create a perfect constraint like in the real world, in some cases you might find that the joined bodies can stray from their correct position, as mentioned in the joints overview topic.

Try moving the anchor positions to see how you can place the pivot point in different locations. For example, try these:

// place the bodyB anchor at the edge of the circle 
jointDef.localAnchorB = {x: -2, y: 0};

// place the bodyA anchor outside the shape
jointDef.localAnchorA = new b2Vec2(4, 4);

Now lets set some joint limits. Following the example above, we'll make it able to move within the range -45 to 45 degrees.

b2RevoluteJoint_EnableLimit(joint, true);
b2RevoluteJoint_SetLimits(joint, -45 * Math.PI / 180, 45 * Math.PI / 180);

You should see the rotation of the bodies restricted. If you put the square body into a corner to keep it still, and pull on the circle body so that it pushes against one of the limits, you will be able to see how the joint angle can sometimes go a little bit over the limits when it has a strong force against it, as mentioned above.

Next, let's add a joint motor, also as in the example above. It's more interesting to be able to switch the direction of the motor at runtime, so you could also add a variable to hold the current motor direction, and use the Keyboard() function to switch it as in some of the previous topics.

b2RevoluteJoint_EnableMotor(joint, true);
b2RevoluteJoint_SetMaxMotorTorque(joint, 5);
b2RevoluteJoint_SetMotorSpeed(joint, 90 * Math.PI / 180); // 90 degrees per second

This setting aims to rotate the joint 90 degrees per second, which means it should be able to get from one limit to the other in one second. However, the torque is a little low for the rotational inertia of the bodies involved. If you implemented the keyboard switching of the motor direction, switch directions back and forward and you will see that it takes a little time to speed up.

wheel attached to square with text displaying angle and speed

If you change the max torque setting to a higher value, around 60, you will see that the joint can accelerate fast enough so that it does actually reach 90 degrees per second before hitting the other limit. Try disabling the limit so you can see how the motor will keep the joint at a stable speed. Add another wheel and you have a simple car!

Simple chain example

Since chains are quite a common use for revolute joints, we'll give that a try too. A chain is just a bunch of bodies joined together, so they are quite simple to make. First we'll try a loose chain flopping around in the world.

It's best to use a loop to make a chain because the contents are repetitive and with a loop we just change one number to get a longer chain. In one iteration of the loop we need to create a new body as a link in the chain, and attach it to the previous link. To start, let's create the bodies we'll need first:

// Body and shape definitions common to all chain links
const bodyDef = b2DefaultBodyDef();
bodyDef.type = DYNAMIC;
bodyDef.position = {x: 5, y: 10};

const shapeDef = b2DefaultShapeDef();
shapeDef.density = 1;

const boxShape = b2MakeBox(1, 0.25);

// Create first link
let link = b2CreateBody(world, bodyDef);
const shape = b2CreatePolygonShape(link, shapeDef, boxShape);

// Use same definitions to create multiple bodies
for (let i = 0; i < 10; i++) {
    const newLink = b2CreateBody(world, bodyDef);
    const newShape = b2CreatePolygonShape(newLink, shapeDef, boxShape);

    // ... joint creation will go here ...

    link = newLink; // prepare for next iteration
}

This is a good example of how useful it can be to define a body or shape once, and then use the definition multiple times :) Notice that all the bodies are in the same place, so when the test starts you'll get a wacky situation where they push each other out of the way initially.

many boxes overlapping each other

Now let's think about where the joint anchors will go. Suppose we want them to be at the end of each chain link, preferably centered rather than on a corner, and inset a little from the end so that when the link bends it doesn't make any large gaps appear on the outer side.

close-up of chain links with anchor positions at each end

The code to do this is pretty easy, it's just a few extra lines:

// Set up the common properties of the joint before entering the loop
const jointDef = b2DefaultRevoluteJointDef();
jointDef.localAnchorA = new b2Vec2(0.75, 0);
jointDef.localAnchorB = new b2Vec2(-0.75, 0);

// inside the loop, only need to change the bodies to be joined
revoluteJointDef.bodyIdA = link;
revoluteJointDef.bodyIdB = newLink;
b2CreateRevoluteJoint(worldId, revoluteJointDef);

a chain constructed from rectangles connected by revolute joints at each end

Right at the beginning of the simulation, the link bodies are still all piled on top of each other so for a proper game you would want to place the links in a more sensible position to start with, but the anchor positions will be the same. Notice that the collideConnected property (default is false) means that each link in the chain will not collide with it's neighboring links, but it will still collide with all the other links.

Finally, let's try attaching one end of the chain to a grounded body. Make a dynamic body with a circle shape, and set up a revolute joint to connect it to a static body at it's center. The testbed already has a static body at (0,0) which we can use for this.

// body with circle shape
const circleShape = {
    m_radius: 2
};
shapeDef.shape = circleShape;
const chainBase = b2CreateBody(worldId, bodyDef);
b2CreateCircleShape(chainBase, shapeDef, circleShape);

// a revolute joint to connect the circle to the ground
revoluteJointDef.bodyIdA = groundBody; // provided by testbed
revoluteJointDef.bodyIdB = chainBase;
revoluteJointDef.localAnchorA = new b2Vec2(4, 20); // world coords, because groundBody is at (0,0)
revoluteJointDef.localAnchorB = new b2Vec2(0, 0); // center of circle
b2CreateRevoluteJoint(worldId, revoluteJointDef);

// another revolute joint to connect the chain to the circle
revoluteJointDef.bodyIdA = link; // the last added link of the chain
revoluteJointDef.bodyIdB = chainBase;
revoluteJointDef.localAnchorA = new b2Vec2(0.75, 0); // the regular position for chain link joints, as above
revoluteJointDef.localAnchorB = new b2Vec2(1.75, 0); // a little in from the edge of the circle
b2CreateRevoluteJoint(worldId, revoluteJointDef);

a ball-and-chain arrangement in box2d

Credits

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