This topic covers rotating a body to face a specific angle. Like linear movement, rotation can be achieved by directly setting the angle or using torque/impulse methods. Direct angle setting means the body won't participate correctly in physics simulation.

For this example, we'll create one dynamic body without gravity and give it a distinct directional shape:

// Create world with default settings
const worldId = b2CreateWorld(b2DefaultWorldDef());

// Create body definition
const bodyDef = b2DefaultBodyDef();
bodyDef.type = DYNAMIC;
bodyDef.position = new b2Vec2(0, 10);

// Create hexagonal shape vertices
const vertices = [];
for (let i = 0; i < 6; i++) {
    const angle = -i/6.0 * 360 * DEGTORAD;
    vertices.push(new b2Vec2(Math.sin(angle), Math.cos(angle)));
}
vertices[0] = new b2Vec2(0, 4); // change one vertex to be pointy

// Create polygon shape
const hull = b2ComputeHull(vertices, vertices.length);
const polygon = b2MakePolygon(hull, 0);

// Create shape definition
const shapeDef = b2DefaultShapeDef();
shapeDef.density = 1;

// Create body and attach shape
const bodyId = b2CreateBody(worldId, bodyDef);
const shapeId = b2CreatePolygonShape(bodyId, shapeDef, polygon);

// Zero gravity
b2World_SetGravity(worldId, new b2Vec2(0, 0));

This example relies on elements of debug_draw.js which contains convenience functions for canvas access.

// create the debug draw environment
const m_drawScale = 10.0;
const m_draw = CreateDebugDraw(canvas, ctx, m_drawScale);

a pointer shape

To set a target rotation point using mouse input:

let clickedPoint = new b2Vec2(0, 0);

let mouseClick = false;
document.addEventListener('mousedown', (event) =>
{
    mouseClick = true;
});

document.addEventListener('mouseup', (event) =>
{
    mouseClick = false;
});

document.addEventListener('mousemove', function (event)
{
    // convert mouse click to screen coordinates taking into account any scrolling
    const ps = new b2Vec2(event.clientX - m_draw.positionOffset.x, event.clientY - m_draw.positionOffset.y);
    // convert screen coordinates into physics world coordinates, taking into account the drawing scale factor
    const pw = ConvertScreenToWorld(canvas, m_drawScale, ps);
    if (mouseClick) {
        clickedPoint = pw;
    }
});

a pointer with a small square target

Setting angle directly

const bodyAngle = b2Body_GetRotation(bodyId).angle;
const bodyPos = b2Body_GetPosition(bodyId);
const toTarget = new b2Vec2(
    clickedPoint.x - bodyPos.x,
    clickedPoint.y - bodyPos.y
);
const desiredAngle = Math.atan2(-toTarget.x, toTarget.y);

// For debugging
console.log(`Body angle: ${bodyAngle * RADTODEG}`);
console.log(`Target angle: ${desiredAngle * RADTODEG}`);

a pointer and some debug angle text

To align the body instantly

b2Body_SetTransform(bodyId, bodyPos, b2MakeRot(desiredAngle));
b2Body_SetAngularVelocity(bodyId, 0);

a rotated pointer facing the target

For gradual turning

let totalRotation = desiredAngle - bodyAngle;
const change = 1 * DEGTORAD; // allow 1 degree rotation per time step
const newAngle = bodyAngle + Math.min(change, Math.max(-change, totalRotation));
b2Body_SetTransform(bodyId, bodyPos, b2MakeRot(newAngle));

To handle angle wrapping

Have you noticed something odd when the target position is below the body? Angles on the left are positive values, and angles on the right are negative, spanning from -180 to 180. This means that when the target crosses directly below the body, the desired angle can jump from say, 179 to -179 which causes the body to do almost a full rotation (358 degrees) even though the target was only 2 degrees away! We could fix this by noting that the body should never need to rotate more than 180 degrees to face the right direction

// the modulus operator ('%') returns the remainder after a division
// here we use it to limit numbers to our desired range by wrapping them around
totalRotation = ((totalRotation + Math.PI) % (2 * Math.PI) + 2 * Math.PI) % (2 * Math.PI) - Math.PI;

Using torque

For a more physically realistic method, a torque can be applied to turn the body:

const nextAngle = bodyAngle + b2Body_GetAngularVelocity(bodyId) / 3.0; // 1/3 second
const totalRotation = desiredAngle - nextAngle;
b2Body_ApplyTorque(bodyId, totalRotation < 0 ? -10 : 10, true);

For instant rotation using torque

How about an intantaneous spin? As in the previous topic, we could try a very large force for one time step, along with the 'looking ahead' idea above. The equation to find the torque to apply is the same as the linear version but uses angular velocity and angular mass. Angular mass is known as rotational inertia.

const nextAngle = bodyAngle + b2Body_GetAngularVelocity(bodyId) / 60.0;
let totalRotation = desiredAngle - nextAngle;
totalRotation = ((totalRotation + Math.PI) % (2 * Math.PI) + 2 * Math.PI) % (2 * Math.PI) - Math.PI;
const desiredAngularVelocity = totalRotation * 60;
const torque = b2Body_GetInertiaTensor(bodyId) * desiredAngularVelocity / (1/60.0);
b2Body_ApplyTorque(bodyId, torque, true);

Note that this is not quite instantaneous, but it usually gets the body in the right rotation within 2-3 time steps which is often good enough for practical purposes.

Using an impulse

Instantaneous movement using impulses is the same as the above code, but without the time factor.

const nextAngle = bodyAngle + b2Body_GetAngularVelocity(bodyId) / 60.0;
let totalRotation = desiredAngle - nextAngle;
totalRotation = ((totalRotation + Math.PI) % (2 * Math.PI) + 2 * Math.PI) % (2 * Math.PI) - Math.PI;
const desiredAngularVelocity = totalRotation * 60;
const impulse = b2Body_GetInertiaTensor(bodyId) * desiredAngularVelocity;
b2Body_ApplyAngularImpulse(bodyId, impulse, true);

For gradual rotation using impulses

And for a gradual change, just limit the change in rotation allowed per time step:

const nextAngle = bodyAngle + b2Body_GetAngularVelocity(bodyId) / 60.0;
let totalRotation = desiredAngle - nextAngle;
totalRotation = ((totalRotation + Math.PI) % (2 * Math.PI) + 2 * Math.PI) % (2 * Math.PI) - Math.PI;
let desiredAngularVelocity = totalRotation * 60;
const change = 1 * DEGTORAD; // allow 1 degree rotation per time step
desiredAngularVelocity = Math.min(change, Math.max(-change, desiredAngularVelocity));
const impulse = b2Body_GetInertiaTensor(bodyId) * desiredAngularVelocity;
b2Body_ApplyAngularImpulse(bodyId, impulse, true);

Credits

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