Halt! You should have a good understanding of the basic tutorials before venturing further.

The discussion of how 'top down' car physics might be implemented in Box2D comes up fairly often, so I thought I would give it a try and make a topic for it. Usually a top-down car is modelled in a zero-gravity world, represented by one body for the chassis and four separate bodies for the wheels. Depending on how realistic a simulation is required it might be good enough to just use one body for the chassis and not worry about having separate wheels.

In either case the crux of the problem is preventing a body from moving in one local axis (tire should not slide sideways) while still allowing it to move freely in the other local axis (tire should be able move back and forwards). This in itself is not such a difficult feat, but the trick is getting it to feel nice for the user when they control the car. If the lateral velocity is simply killed completely the car will feel like it's on rails, and we might actually want to allow the car to skid in some situations, and behave differently on various surfaces etc. Before we get started you might like to take a look at Doug Koellmer's excellent implementation of top-down cars in Flash: qb2DemoReel.swf (click the 'Next demo' button a couple of times). This is the kind of thing we're aiming for.

The basic procedure is to find the current lateral velocity of a body and apply an impulse that will cancel out that velocity. We will start with just one body to represent a tire, and later attach four of these to another body for a more complex simulation. Since all the tires do the same thing we can make a class for them. Here is the starting point, a class which creates a body and sets it up with a simple box shape.

class TDTire {
    constructor(world) {
        // Create body
        const bodyDef = b2DefaultBodyDef();
        bodyDef.type = DYNAMIC;;
        this.bodyId = b2CreateBody(world, bodyDef);

        // Create box shape
        const shapeDef = b2DefaultShapeDef();
        shapeDef.density = 1.0;

        const box = b2MakeBox(0.5, 1.25);
        this.shapeId = b2CreatePolygonShape(this.bodyId, shapeDef, box);

        // Store reference to this class instance
        b2Body_SetUserData(this.bodyId, this);
    }

    destroy() {
        b2DestroyBody(this.bodyId);
    }
}

Note that we've set the user data of the created body to this class instance, so the physics body and the game logic class both have a reference to each other.

Killing lateral velocity

To cancel out the lateral velocity we first need to know what it is. We can find it by projecting the current velocity of the body onto the current normal vector for the 'sideways' direction of the tire. Let's say that in the local coordinates of the tire (0,1) will be forwards and (1,0) will be rightwards. We can use GetWorldVector on these to get the current orientation of these directions in world coordinates.

Box2D Top-down car physics

For example suppose the tire is rotated a little, and moving upwards as shown in the next diagram. We want to 'project' the blue vector onto the red one to see how long it would be if it was only going in the red vector's direction.

Box2D Top-down car physics

So we can add a function like this to the tire class:

getLateralVelocity() {
currentRightNormal = b2Body_GetWorldVector(this.bodyId, new b2Vec2(1, 0));
    const velocity = b2Body_GetLinearVelocity(this.bodyId);
    const dot = b2Dot(currentRightNormal, velocity);
    return new b2Vec2(currentRightNormal.x * dot, currentRightNormal.y * dot);
}
updateFriction() {
    const lateralVelocity = this.getLateralVelocity();
    const impulse = new b2Vec2(-lateralVelocity.x * b2Body_GetMass(this.bodyId), -lateralVelocity.y * b2Body_GetMass(this.bodyId));

    const worldCenter = b2Body_GetWorldCenterOfMass(this.bodyId);
    b2Body_ApplyLinearImpulse(this.bodyId, impulse, worldCenter, true);

    // Apply angular damping
    const angularVelocity = b2Body_GetAngularVelocity(this.bodyId);
    const angularImpulse = 0.1 * b2Body_GetInertiaTensor(this.bodyId) * -angularVelocity;
    b2Body_ApplyAngularImpulse(this.bodyId, angularImpulse, true);

    // Apply drag force
    const currentForward = this.getForwardVelocity();
    const currentForwardSpeed = Math.sqrt(currentForward.x * currentForward.x + currentForward.y * currentForward.y);
    const dragForceMagnitude = -2 * currentForwardSpeed;

    const dragForce = new b2Vec2(currentForward.x * dragForceMagnitude / currentForwardSpeed, currentForward.y * dragForceMagnitude / currentForwardSpeed);

    b2Body_ApplyForceToCenter(this.bodyId, dragForce, true);
}

The value 0.1 is just something I decided on by playing around with it a bit to get something that looked like what I remember seeing the last time I spun a car tire around :) If we killed the rotation completely (try it) the tire looks like it is on a rail and it can't go anywhere but in a straight line. Another reason not to completely kill the rotation is that pretty soon we will want to let the user turn this body to drive it around.

Of course all you smart people out there automatically knew that getForwardVelocity() is the same as getLateralVelocity() but works with a local vector of (0,1) instead of (1,0) right?

Here's the JavaScript conversion of the provided code chunk:

// Tire class method for updating drive
function updateDrive(controlState) {
    // Find desired speed
    let desiredSpeed = 0;
    switch (controlState & (TDC_UP | TDC_DOWN)) {
        case TDC_UP:
            desiredSpeed = this.m_maxForwardSpeed;
            break;
        case TDC_DOWN:
            desiredSpeed = this.m_maxBackwardSpeed;
            break;
        default:
            return; // do nothing
    }

    // Find current speed in forward direction
    const currentForwardNormal = b2Body_GetWorldVector(this.m_body, {x: 0, y: 1});
    const forwardVelocity = this.getForwardVelocity();
    const currentSpeed = b2Dot(forwardVelocity, currentForwardNormal);

    // Apply necessary force
    let force = 0;
    if (desiredSpeed > currentSpeed) {
        force = this.m_maxDriveForce;
    } else if (desiredSpeed < currentSpeed) {
        force = -this.m_maxDriveForce;
    } else {
        return;
    }

    const center = b2Body_GetWorldCenter(this.m_body);
    const forceVec = new b2Vec2(force * currentForwardNormal.x,
                               force * currentForwardNormal.y);
    b2Body_ApplyForce(this.m_body, forceVec, center, true);
}

Play around with the speed and force values to get something you like. At the start of the topic I wasn't thinking about dimensions too much and my tire is a rather unrealistic one meter wide, so those speeds are not real-world values either.

Now that the tire can move back and forwards, let's also make it turn by applying some torque when the a/d keys are pressed. Because our end-goal is to attach these tires to a car body, this part of the program will be dropped soon so it's just a crude way to get some turning happening so we can test the next part - skidding and surfaces. On the other hand if you were actually intending to model the car as a single body, you would want to refine this to be more sensible, eg. not letting the car turn unless it is moving etc.

function updateTurn(controlState) {
    let desiredTorque = 0;
    switch (controlState & (TDC_LEFT | TDC_RIGHT)) {
        case TDC_LEFT:
            desiredTorque = 15;
            break;
        case TDC_RIGHT: 
            desiredTorque = -15;
            break;
        default:
            // nothing
    }
    b2Body_ApplyTorque(this.m_body, desiredTorque, true);
}

Allowing skidding

At this point we have a controllable body which behaves very well according to our original plan of killing the lateral velocity. This is all very well if you want to simulate slot-cars which stick to their track like glue, but it feels a bit more natural if the car can skid a bit. Unfortunately this is really really hard... haha just kidding. Actually we have already done it - remember how when we killed the lateral velocity we killed it completely, right? We simply calculated the necessary impulse and applied it, like a boss. That's not very realistic because it means the tire will never slip sideways. So all we need to do is restrict that impulse to some maximum value, and the tire will slip when the circumstances require a greater correction than allowable. This is only one extra statement in the updateFriction function:

// In updateFriction, lateral velocity handling section
const impulse = new b2Vec2(
    this.m_body.GetMass() * -this.getLateralVelocity().x,
    this.m_body.GetMass() * -this.getLateralVelocity().y
);

const impulseLength = Math.sqrt(impulse.x * impulse.x + impulse.y * impulse.y);
if (impulseLength > maxLateralImpulse) {
    const scale = maxLateralImpulse / impulseLength;
    impulse.x *= scale;
    impulse.y *= scale;
}

const center = b2Body_GetWorldCenter(this.m_body);
b2Body_ApplyLinearImpulse(this.m_body, impulse, center, true);

I found that a value of 3 for the maxLateralImpulse would allow only a very small amount of skidding when turning at high speeds, a value of about 2 gave an effect like a wet road, and a value of 1 reminded me of a speedboat turning on water. These values will need to be adjusted when the wheels are joined to the car chassis anyway, so don't get too fussy with them just yet.

Setting up varying surfaces (complex user data part 1)

To define certain areas of the scene as different surfaces we'll need to make some shapes on the 'ground' body and use a contact listener to keep track of when the wheel is touching them. This is very similar to the jumpability topic where we use a sensor shape attached to the bottom of the player to check what they are standing on. The only difference this time is that the shape on the ground is a sensor (because we need to drive over it) and the player (car) is solid because we want it to crash into stuff.

So we could set a user data tag as a simple integer like in the 'jumpability' topic to mark certain shapes as ground areas, and then whenever our contact listener gets a BeginContact/EndContact we can check that tag to see if the tire has entered/left the ground area. However, that kind of simple method only works when you can be absolutely sure that the user data set in the shape is an integer tag. In a proper game you are likely to have many kinds of shapes bumping into each other.

It would be nice to have more information than just a single integer in the user data, eg. as well as the surface friction type we might like to know if the car went off course or into the audience stands etc. We also would like to be able to change this info without needing Box2D's SetUserData function every time, for example if some aspect of the ground area was to change over time (eg. wet area gradually dries up).

There are various ways you could handle this. It's not really a whole lot to do with the topic at hand, but since I haven't covered it in detail anywhere else yet I will take this opportunity to show the way I often do it. I'm not sure if there is any typical or recommended method, but this usually works ok. We create a generic class to use for shape data:

// Types of shape user data
const FixtureUserDataType = {
    CAR_TIRE: 'carTire',
    GROUND_AREA: 'groundArea'
};

// Base class for shape user data
class FixtureUserData {
    constructor(type) {
        this.type = type;
    }

    getType() {
        return this.type;
    }
}

// Class for car tire shape data
class CarTireFUD extends FixtureUserData {
    constructor() {
        super(FixtureUserDataType.CAR_TIRE);
    }
}

// Class for ground area shape data
class GroundAreaFUD extends FixtureUserData {
    constructor(frictionModifier, outOfCourse) {
        super(FixtureUserDataType.GROUND_AREA);
        this.frictionModifier = frictionModifier;
        this.outOfCourse = outOfCourse;
    }
}

We will not be using the 'out of course' setting of the ground areas in this topic, I just added that to make it clear that now you can put a whole bunch of information in the shape user data. Here is how you would set up a couple of static ground area shapes and set their shape user data with the class above:

const bodyDef = b2DefaultBodyDef();
const groundBody = b2CreateBody(world, bodyDef);

const polygonShape = new b2Polygon();
const shapeDef = b2DefaultShapeDef();
shapeDef.isSensor = true;

// First ground area
b2MakeBox(9, 7, polygonShape);
const transform1 = {
    p: new b2Vec2(-10, 15),
    q: b2MakeRot(20 * Math.PI / 180)
};
const groundAreaFixture1 = b2CreatePolygonShape(groundBody, shapeDef, polygonShape);
b2Shape_SetUserData(groundAreaFixture1, new GroundAreaFUD(0.5, false));

// Second ground area
b2MakeBox(9, 5, polygonShape);
const transform2 = {
    p: new b2Vec2(5, 20),
    q: b2MakeRot(-40 * Math.PI / 180)
};
const groundAreaFixture2 = b2CreatePolygonShape(groundBody, shapeDef, polygonShape);
b2Shape_SetUserData(groundAreaFixture2, new GroundAreaFUD(0.2, false));

Box2D Top-down car physics

You might have noticed that we have used object creation for the user data but we didn't keep a reference to it. In JavaScript, we don't need to worry about manually managing memory like in C++, as the garbage collector will handle cleanup of these objects when they are no longer referenced.

Fortunately Box2D already provides what we need. The Box2D world has a pre-solve callback which we can use to make the engine call a function before resolving contacts. Let's look at how to set this up. We need to create a pre-solve callback function and set it in the world:

// Create pre-solve callback function
function preSolve(world, contact, oldManifold) {
    const shapeA = contact.GetFixtureA();
    const shapeB = contact.GetFixtureB();
    const userDataA = shapeA.GetUserData();
    const userDataB = shapeB.GetUserData();

    if (userDataA && userDataB) {
        if (userDataA.type === 'CAR_TIRE' && userDataB.type === 'GROUND_AREA') {
            handleTireGroundContact(shapeA, shapeB);
        } else if (userDataB.type === 'CAR_TIRE' && userDataA.type === 'GROUND_AREA') {
            handleTireGroundContact(shapeB, shapeA);
        }
    }
}

// Set the pre-solve callback in world creation
const worldDef = b2DefaultWorldDef();
const world = b2CreateWorld(worldDef);
b2World_SetPreSolveCallback(world, preSolve, null);

Now we can clean up bodies when destroying the world:

function destroyWorld() {
    if (world) {
        b2DestroyWorld(world);
        world = null;
    }
}

For this small scene it's not such a big win but in more complex scenes it can be quite helpful. There are some things to be careful of when using this method - for example if you destroy a shape explicitly yourself with b2DestroyShape this callback will not be called. Check out the section on 'Implicit destruction' in the documentation for more details.

Handling varying surfaces (complex user data part 2)

Now that we have shapes set up with various kinds of user data, we'll need to handle these cases when things collide. This is another area where there is no official or best way to handle things, but I will continue with the way I often do it which works ok.

Contact handling has been covered in other topics so I won't go into the details here. What we are trying to do is set up a function to handle each case of contact between the types of shapes we have so that we can concentrate on the game logic, eg. in this scene we only have two types so one function will suffice:

// Global scope
function handleTireGroundContact(tireShape, groundAreaShape) {
    const tire = tireShape.GetBody().GetUserData();
    const groundArea = groundAreaShape.GetUserData();

    // Update tire properties based on ground area
    tire.addGroundArea(groundArea);
}

Alternatively you could make this a method of the tire class and pass it just the ground shape parameter, letting the tire do whatever it needs to do. You could also have a method in the ground area class (if we had one) to let it do something, just as long as the tire and the ground don't start telling each other what to do - that would get messy. I feel it is clearer to have one point of control which tells both of the entities involved what to do.

Here's how we track what surface the tire is currently on:

class Tire {
    constructor() {
        this.groundAreas = new Set();
        this.currentTraction = 1;
    }

    addGroundArea(groundArea) {
        this.groundAreas.add(groundArea);
        this.updateTraction();
    }

    removeGroundArea(groundArea) {
        this.groundAreas.delete(groundArea);
        this.updateTraction();
    }

    updateTraction() {
        if (this.groundAreas.size === 0) {
            this.currentTraction = 1;
        } else {
            // Find area with highest traction
            this.currentTraction = 0;
            for (const area of this.groundAreas) {
                if (area.frictionModifier > this.currentTraction) {
                    this.currentTraction = area.frictionModifier;
                }
            }
        }
    }
}

I should point out here that since the tire class holds references to ground area shape user data there could be trouble if you delete a body that the tire is currently on top of, because the shape's user data will become invalid. Ideally, every tire in the world should get a removeGroundArea call for the ground area shape user data which got deleted.

Almost there... now we just need to fill in the handleTireGroundContact function with the logic to kick this off. The implementation is made more straightforward due to the fact that we know what type of shapes we have been given and we can make some assumptions without needing to check everything:

function handleTireGroundContact(tireShape, groundAreaShape) {
    const tire = tireShape.GetBody().GetUserData();
    const groundArea = groundAreaShape.GetUserData();
    tire.addGroundArea(groundArea);
}

Giving this a try should result in the current highest traction surface being reported.

various tire traction scenarios

To finish off, use the current traction variable of the tire class to modify the amount of friction, drag and power applied in the updateFriction and updateDrive functions of the tire class. To keep things simple I just multiplied each final value given to the force/impulse functions by the traction variable. As long as we keep the traction variable between zero and one this should make sense.

Putting it together

So we have a nice tire that can drive and slide around. Depending on what you're making, with a bit of tweaking this might be enough for some games, but I don't think it's what you really came here for... the fun really starts when we put four of these tires on a car body to act independently. Fortunately the hard parts have all been covered already, and all we need to do now is set up the body and direct the control input a bit differently.

Let's start with a car with four fixed wheels, then we can handle the steering after that. We'll need a class to represent a car:

class Car {
    constructor(world) {
        // Create car body
        const bodyDef = b2DefaultBodyDef();
        bodyDef.type = DYNAMIC;;
        this.body = b2CreateBody(world, bodyDef);

        // Create car shape with b2Vec2 vertices
        const vertices = [
            new b2Vec2(1.5, 0),
            new b2Vec2(3, 2.5),
            new b2Vec2(2.8, 5.5),
            new b2Vec2(1, 10),
            new b2Vec2(-1, 10),
            new b2Vec2(-2.8, 5.5),
            new b2Vec2(-3, 2.5),
            new b2Vec2(-1.5, 0)
        ];

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

        const polygon = b2MakePolygon(vertices, vertices.length);
        this.shape = b2CreatePolygonShape(this.body, shapeDef, polygon);
    }
}

the car body shape

Next the tires are created and attached. Eventually we will want to turn the front wheels so we should keep a reference to the front wheel joints. For now we'll just keep all the joints fixed by setting the same value for lowerAngle and upperAngle. The back wheels could also be weld joints.

// Create and attach tires to the car body
// Keep references to front wheel joints for steering
class Car {
    constructor(world) {
        this.tires = [];
        this.flJoint = null;
        this.frJoint = null;

        // Create revolute joint definition
        const jointDef = {
            bodyA: this.body,
            enableLimit: true,
            lowerAngle: 0, // With both these at zero...
            upperAngle: 0, // ...the joint will not move
            localAnchorB: new b2Vec2(0, 0), // Joint anchor in tire is always center
        };

        // Create front left tire
        const tire = new Tire(world);
        jointDef.bodyB = tire.body;
        jointDef.localAnchorA = new b2Vec2(-3, 8.5);
        this.flJoint = b2CreateRevoluteJoint(world, jointDef);
        this.tires.push(tire);

        // Other tires created similarly...
    }

wheels attached

Clearly, this fine machine is modeled on uh... a Ferrari. One of those old ones, you know. No it doesn't look like a frog, I don't see any resemblance at all. Anyway, let's add a function to take the input from the main loop and direct it to the wheels:

    // Update function to handle tire friction and drive forces
    update(controlState) {
        // Update tire friction
        for (let tire of this.tires) {
            tire.updateFriction();
        }

        // Update tire drive forces
        for (let tire of this.tires) {
            tire.updateDrive(controlState);
        }
    }
}

Oh dear. I just wasted ten minutes playing with that, and I can't even steer yet... I suppose that's a good sign :) To get a nicer acceleration and top speed with this heavier body, I changed the characteristics of the tires to:

// Tire characteristics for better acceleration and top speed
const maxForwardSpeed = 250;
const maxBackwardSpeed = -40;
const maxDriveForce = 300;

To turn the front wheels we could use a joint motor, but without any other linkage between them they could become out of sync with each other, especially with the rough treatment we are about to give them. For this example I decided to control the steering by setting the joint limits, which forces the tires into a specific angle. It's a little unorthodox but works fine and lets us directly control the rate of turn independently from anything else happening to the tire (remember how we killed the angular velocity of the tires?), instead of setting motor speed and torque and hoping for the best.

// Steering control using joint limits
function updateSteering(controlState) {
    const lockAngle = 40 * DEGTORAD;
    const turnSpeedPerSec = 320 * DEGTORAD; // From lock to lock in 0.25 sec
    const turnPerTimeStep = turnSpeedPerSec / 60.0;
    let desiredAngle = 0;

    // Determine desired steering angle based on input
    if (controlState & TDC_LEFT) {
        desiredAngle = lockAngle;
    } else if (controlState & TDC_RIGHT) {
        desiredAngle = -lockAngle;
    }

    // Get current angle and calculate required turn
    const angleNow = b2RevoluteJoint_GetAngle(this.flJoint);
    let angleToTurn = desiredAngle - angleNow;
    angleToTurn = Math.max(Math.min(angleToTurn, turnPerTimeStep), -turnPerTimeStep);
    const newAngle = angleNow + angleToTurn;

    // Apply new angle limits to both front wheels
    b2RevoluteJoint_SetLimits(this.flJoint, newAngle, newAngle);
    b2RevoluteJoint_SetLimits(this.frJoint, newAngle, newAngle);
}

40 degrees may be a little too much... we'll see.

the steering lock angle at maximum

Playing with this you'll find that it's great for doing huge 'drift' powerslides, something you might like every now and then, but it's too drifty to feel like it's on a tarmac road. I think this is due to the extra mass of the car body we added. I found that to get something I was satisfied with I needed to give the front and back wheels different characteristics, and even then it was not quite as much fun as I had hoped. To do this properly would take quite a bit of tweaking and tuning, probably involving different friction and power settings at different speeds, but I'll leave that to you :)

Credits

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