Revs on the BBC Micro

# The core driving model

## A breakdown of the driving model that powers the driving simulation

This article describes the core driving model in Revs. It covers the physics of the car when driving fully on the track, in gear, and on the ground (i.e. not in the air). It explains the different stages of the model, as listed in the overview of the driving model.

There are extra calculations involved when driving on grass, skidding, jumping, dropping, slipping the clutch or starting the engine. These are linked in the relevant sections below.

## Calculation 1: Calculate the rotation matrix --------------------------------------------

See code: GetRotationMatrix

The velocity of the player's car is stored in the following vector:

```  [ xPlayerSpeed ]
[ yPlayerSpeed ]
[ zPlayerSpeed ]
```

This velocity is stored with respect to the 3D world, so it's the player's speed in terms of longitude, latitude and elevation. The driving model is calculated from the point of view of the driver, rather than the outside world, so the first step is to rotate this velocity vector into the frame of reference of the player.

The driving model deals with elevation in the y-axis separately from the movement of the car on the ground, so for now we can ignore the y-axis. Also, the player's yaw angle gives us the rotation that we need, as the player's yaw angle is how much the car is rotated clockwise from the north-south axis in the 3D world. We can therefore switch frames of reference by multiplying the velocity vector by a rotation matrix that rotates through the yaw angle.

The standard rotation matrix for rotating around the y-axis by an angle a is:

```  [  cos(a)   0   sin(a) ]
[    0      1     0    ]
[ -sin(a)   0   cos(a) ]
```

We therefore need to calculate the cos and sin of the player's yaw angle, which we can do by calling the GetRotationMatrix routine. This puts the results into the cosYawAngle and sinYawAngle variables.

## Calculation 2: Rotate velocity into car's frame of reference ------------------------------------------------------------

See code: RotateCoordToCar

Now that we have the values we need for the rotation matrix, we can call the RotateCoordToCar routine to rotate the player's velocity vector from the 3D world coordinate system to the frame of reference of the player's car:

```  [ xVelocity ]   [ cosYawAngle   0   -sinYawAngle ]   [ xPlayerSpeed ]
[     -     ] = [      0        1         0      ] . [ yPlayerSpeed ]
[ zVelocity ]   [ sinYawAngle   0    cosYawAngle ]   [ zPlayerSpeed ]
```

We store the result in the xVelocity and zVelocity variables.

As mentioned above, we can ignore the y-axis as we are only interested in calculating the velocity along the horizontal plane containing the track. The y-axis is processed in the ApplyElevation routine in calculation 21 below.

Following this rotation, we can now work with the frame of reference for the car, in which the z-axis is pointing out of the nose of the car, and the x-axis runs from left to right.

## Calculation 3: Set speed-related variables ------------------------------------------

See code: ApplyDrivingModel

For the calculations in the rest of the model, we need to store a few values. First up, we store the current sideways velocity in xPrevVelocity:

```  xPrevVelocity = xVelocity
```

We also set the player's speed in (playerSpeedHi playerSpeedLo) to the current forward velocity, i.e. the speed along the z-axis:

```  playerSpeed = |zVelocity|
```

Next, we set the playerMoving flag, which determines whether the player is moving. We define "moving" as having a forward speed in (playerSpeedHi playerSpeedLo) of 16 or more, so we set it as follows:

```  playerMoving = (playerSpeed >= 16)
```

## Calculation 4: Set spin-related variables -----------------------------------------

See code: ApplySpinYaw

We also store the amount of yaw angle spin that is currently being applied to the car (i.e. the rate at which it is spinning):

```  xSpinVelocity = spinYawAngle * 0.52
```

Finally for this stage, we subtract the current spin from xVelocity, though this technically forms part of calculation 8 below, so we'll explain it there. This is the calculation:

```  xVelocity = xVelocity - (spinYawAngle * 0.34)
```

## Calculation 5: Calculate height of bumpy grass -----------------------------------------------

See code: ApplyGrassOrTrack

If we are driving on grass, there is an extra step here; see the deep dive on driving on grass for details.

## Calculation 6: Calculate wing and brake downward forces -------------------------------------------------------

See code: ApplyGrassOrTrack

If we are driving on grass, this step is slightly different; see the deep dive on driving on grass for details.

If we are driving on the track, then for each wing (front and rear), calculate the following downward force:

```  wingForce =   wingSetting * min(53, playerSpeedHi) * abs(zVelocity)
+ wingForceTrack
+ brakeForce

wingForce95 = wingForce * 0.95
```

where wingForceTrack is defined as 53 for each wing, and brakeForce is the downward force due to the brakes, as follows:

```  brakeForce = 0                      (if no brakes are being applied)

-zTyreForceBoth / 8    (front tyre if brakes are being applied)

zTyreForceBoth / 8     (rear tyre if brakes are being applied)```

zTyreForceBoth is from the previous call to the driving model (it is set in calculation 14), or 0 if this is the first call.

## Calculation 7: Calculate engine torque --------------------------------------

See code: ApplyEngine

Before calculating the engine torque, we do a number of checks to see whether the engine has any effect on the car's motion:

• If the engine is not running, jump to ProcessEngineStart to check for the "T" key press, start the engine as appropriate, and jump to the next stage.
• If heightAboveTrack > 0, then the tyres are not touching the track, so calculate the rev count, zero the engine torque and jump to the next stage.
• If a gear change key is being pressed, set bit 7 of clutchEngaged to indicate that the clutch is not engaged, calculate the rev count, zero the engine torque and jump to the next stage.
• If we are in neutral, calculate the rev count, zero the engine torque and jump to the next stage.

Otherwise, calculate the engine torque based on gear ratio, power and revs, setting the engineTorque, revCount and soundRevTarget variables accordingly. See modelling the engine for details.

## Calculation 8: Calculate rear tyre forces -----------------------------------------

See code: ApplySpinYaw

We did the following back in calculation 4, but it isn't relevant until now:

```  xVelocity = xVelocity - (spinYawAngle * 0.34)
```

We are going to be using xVelocity and zVelocity to calculate the tyre forces and skidding, so this subtracts the current spin velocity to give us the correct sideways velocity of the rear tyres to use in the calculation.

The spin is around the centre of gravity of the car, so we subtract the spin because with a clockwise spin, the rear tyres move backwards along the x-axis when the front tyres move forwards along the x-axis.

See code: ApplyTyresAndSkids, ApplyTyreForces

Before calculating the forces from the rear tyres, we do a number of checks to see whether the tyres have any effect on the car's motion:

• If heightAboveTrack >= 2, then the car is not on the ground and the tyres have no effect, so stop the tyres from squealing and jump to calculation 10.
• If xVelocity is non-zero and xVelocity and xTyreForceRear have the same sign:
• Set the high byte of xTyreForceNose = -xVelocity * 2^5
• Rotate a 1 into bit 7 of tyreSqueal for the rear tyres and jump to calculation 9.

If we get here then the rear tyres do affect the car's position and speed, so we now calculate the forces being applied by the rear tyres, and store them in xTyreForceRear and zTyreForceRear.

To start, we calculate the frictional force from the tyres moving sideways along the ground, with the force being proportional to the sideways speed (and in the opposite direction to the movement):

```  xTyreForceRear = -xVelocity * 2^5
```

See code: GetTyreForces

We now calculate the tyre forces, depending on whether the throttle is being applied.

• If the throttle is not being applied, we calculate the forward force from the rear tyres in zTyreForceRear as follows:
```  (NN MM) = scaled up |zVelocity|

(A T) = throttleBrake * wingForce

zTyreForceRear = max((A T, (NN MM)) * abs(-zVelocity)
```
where throttleBrake is 0 if the brakes are being applied. The abs(-zVelocity) ensures that the force is against the direction of travel along the z-axis.
• If the throttle is being applied, we calculate the forward force from the rear tyres in zTyreForceRear as follows:
```  (A T) = (throttleBrake * engineTorque) / 2

zTyreForceRear = (A T) * abs(gearNumber - 1)
```
where throttleBrake is the amount of throttle being applied, and abs(gearNumber - 1) is negative for reverse gear, and positive for all other gears.

We can now work out whether the rear tyres are squealing, by calculating the following:

```  A =   max(|xTyreForceRearHi|, |zTyreForceRearHi|)
+ min(|xTyreForceRearHi|, |zTyreForceRearHi|) / 2
```

and rotating a new bit 7 into tyreSqueal for the rear tyres as follows:

```  Clear if A <= wingForce

Set if A > wingForce
```

This sets tyreSqueal with details of whether the rear tyres are squealing, for use in the next step.

## Calculation 9: Apply rear tyre skidding ---------------------------------------

See code: ApplyTyresAndSkids

If the rear tyres are squealing (i.e. bit 7 of tyreSqueal is set) or the rear tyres were squealing in the last iteration of the main driving loop (i.e. bit 6 of tyreSqueal is set), then we apply the skidding calculations.

## Calculation 10: Calculate front tyre forces -------------------------------------------

See code: ApplyDrivingModel

Having calculated the forces from the rear tyres, we now move on to the front tyres. First we set the value of xVelocity to the previous velocity plus the spin velocity (both of which we stored above, in calculations 3 and 4):

```  xVelocity = xPrevVelocity + xSpinVelocity
```

The spin is around the centre of gravity of the car, so we add the spin this time, unlike the subtraction we did for the rear tyres.

See code: ApplySteeringSpeed

We now add the effect of the cornering force, which is proportional to the slip angle (i.e. the steering angle) at low angles:

```  zVelocity = zVelocity + xVelocity * steering

xVelocity = xVelocity - zVelocity * steering
```

See code: ApplyTyresAndSkids, ApplyTyreForces

Before calculating the forces from the front tyres, we do a number of checks to see whether the tyres have any effect on the car's motion:

• If heightAboveTrack >= 2, then the car is not on the ground and the tyres have no effect, so stop the tyres from squealing and jump to calculation 12.
• If xVelocity is non-zero and xVelocity and xTyreForceNose have the same sign:
• Set the high byte of xTyreForceNose = -xVelocity * 2^5
• Rotate a 1 into bit 7 of tyreSqueal for the front tyres and jump to calculation 9.

If we get here then the front tyres do affect the car's position and speed, so we now calculate the forces being applied by the front tyres, and store them in xTyreForceNose and zTyreForceNose.

To start, we calculate the frictional force from the tyres moving sideways along the ground, with the force being proportional to the sideways speed (and in the opposite direction to the movement):

```  xTyreForceNose = -xVelocity * 2^5
```

See code: GetTyreForces

We now calculate the tyre forces, depending on whether the throttle is being applied.

• If the throttle is not being applied, we calculate the forward force from the front tyres in zTyreForceNose as follows:
```  (NN MM) = scaled up |zVelocity|

(A T) = throttleBrake * wingForce * 3 / 4

zTyreForceNose = max((A T, (NN MM)) * abs(-zVelocity)
```
where throttleBrake is 0 if the brakes are being applied. The abs(-zVelocity) ensures that the force is against the direction of travel along the z-axis. We then out whether the front tyres are squealing, by calculating the following:
```  A =   max(|xTyreForceNoseHi|, |zTyreForceNoseHi|)
+ min(|xTyreForceNoseHi|, |zTyreForceNoseHi|) / 2
```
• If the throttle is being applied, we calculate the forward force from the front tyres in zTyreForceNose as zero:
```  zTyreForceNose = 0
```
and we work out whether the front tyres are squealing, by calculating the following:

```  A = |xTyreForceNoseHi|
```

Finally, we rotate a new bit 7 into tyreSqueal for the front tyres as follows:

```  Clear if A <= wingForce

Set if A > wingForce
```

This sets tyreSqueal with details of whether the front tyres are squealing, for use in the next step.

## Calculation 11: Apply front tyre skidding -----------------------------------------

See code: ApplyTyresAndSkids

If the front tyres are squealing (i.e. bit 7 of tyreSqueal is set) or the front tyres were squealing in the last iteration of the main driving loop (i.e. bit 6 of tyreSqueal is set), then we apply the skidding calculations.

## Calculation 12: Apply resistance due to steering ------------------------------------------------

See code: ApplySteeringForce

Turning the tyres creates resistance, so this is what we model next. The amount of steering is given in (steeringHi steeringLo), with 0 meaning no steering, so we can add the tyre resistance forces as follows:

```  xTyreForceNose = xTyreForceNose + zTyreForceNose * steering

zTyreForceNose = zTyreForceNose - xTyreForceNose * steering
```

The effect is cross-wise, so the forward force of the tyres in zTyreForceNose is reduced the more we steer (as turning creates resistance to the forward motion), while the sideways force of the tyres in xTyreForceNose is increased with more steering.

## Calculation 13: Calculate the spin delta ----------------------------------------

See code: ScaleTyreForces

We have now calculated the forces on the car from the tyres, in both the x-axis and the z-axis. These forces can be used to calculate the acceleration along those axes, but there is one more important force we need to calculate: the rotational force that causes the car to spin. This is a simple enough calculation - we simply calculate the difference between the sideways forces on the front and rear tyres, as follows:

```  spinYawDelta = (xTyreForceNose - xTyreForceRear) * 0.3
```

The delta in the car's yaw angle is another way of saying the rate of change in the car's spin, and this calculation changes the spin according to the balance between the sideways force on the front tyres (xTyreForceNose) and the sideways force from the rear tyres (xTyreForceRear). This is an angular force (also known as a rotational force or a moment). We can calculate the moment of an angular force by multiplying the force by the perpendicular distance to the pivot, and in this case the pivot is the car's centre of gravity, so the multiplication factor of 78 represents the average distances from the tyres to the centre of gravity of the car.

There is no spin generated by the forces in the z-axis, as forces in that direction move the car forwards or backwards, rather than spinning it.

## Calculation 14: Scale the forces --------------------------------

See code: ScaleTyreForces

We also take this opportunity to scale the forces down by a factor of 4:

```  xTyreForceNose = xTyreForceNose >> 2

zTyreForceNose = zTyreForceNose >> 2

xTyreForceRear = xTyreForceRear >> 2

zTyreForceRear = zTyreForceRear >> 2
```

We also make a note of the forward acceleration:

```  zTyreForceBoth = zPlayerAccelHi
```

We don't need this for now, but we pass it through to the next iteration's calculation, where we use it in calculation 6 when calculating the force of the brakes (if applied).

## Calculation 15: Convert tyre and wing forces into acceleration --------------------------------------------------------------

See code: ScaleTyreForces

Newton's second law of motion tells us that the acceleration produced by applying a force to an object is proportional to the force (or, to put it mathematically, F = ma). We have calculated the forces on the car, so now we need to convert them into acceleration, so we can update the car's velocity.

We start by converting the forces from the front and rear tyres into acceleration. The force from the rear tyre produces 1.5 times more acceleration than the acceleration from the front tyre, which makes sense when you think of the tyre sizes and weight distribution. This is codified in the driving model in the following:

```  xPlayerAccel = (xTyreForceRear * 1.5 + xTyreForceNose) * 1.6

zPlayerAccel = (zTyreForceRear * 1.5 + zTyreForceNose) * 1.6
```

The factor of 410 is presumably based on the mass of the car we are simulating, i.e. the m in F = ma.

## Calculation 16: Zero acceleration when jumping ----------------------------------------------

See code: ApplyDrivingModel

If heightAboveTrack >= 2, then the car is in the air and isn't touching the track, so the forces from the tyres can't affect the car's motion. In this case we set spinYawDelta, xPlayerAccel and zPlayerAccel to zero.

## Calculation 17: Apply drag --------------------------

See code: ApplyWingBalance

If we are driving on grass, this step is slightly different; see driving on grass for details.

We also have to cater for the sideways and forward drag that's produced by the car passing through the air. The sideways drag is proportional to both the car's forward speed in playerSpeedHi, and the car's sideways speed in xPrevVelocityHi, as follows:

```  xPlayerAccel = xPlayerAccel - playerSpeedHi * xPrevVelocityHi
```

The forward drag is more complicated, and depends on the wing settings. A higher wing setting creates more downward force, which gives us more grip, but it also creates more drag. The amount of drag is proportional to the square of the car's forward speed in playerSpeedHi, and to the drag from the wings. The latter is calculated as (wingBalance * playerSpeedHi + 2048), like this:

```  zPlayerAccel = zPlayerAccel -   playerSpeedHi
* (wingBalance * playerSpeedHi + 2048)
* abs(zVelocity)
```

The wingBalance factor calculates that the rear wings produce three times as much drag as the front wings at the same speeds, giving a result like this:

```  wingBalance = 60 + (rearWingSetting * 3 + frontWingSetting) / 2
```

So we finally have the player's acceleration vector in the x- and z-axes, taking all the tyre and aerodynamic forces into consideration.

## Calculation 18: Rotate velocity into world's frame of reference ---------------------------------------------------------------

See code: RotateCarToCoord

Now that we have calculated the player's acceleration vector, we need to rotate it back into the 3D world coordinate system, so we can update the car's coordinates and velocity back in the 3D world. We do this as follows, returning the results in xAcceleration and zAcceleration:

```  [ xAcceleration ]   [  cosYawAngle   0   sinYawAngle ]   [ xPlayerAccel ]
[       -       ] = [       0        1        0      ] . [       -      ]
[ zAcceleration ]   [ -sinYawAngle   0   cosYawAngle ]   [ zPlayerAccel ]
```

We can rotate back into the 3D world coordinate system by multiplying by the inverse of the matrix that we used in calculation 2. The inverse of a unit matrix is the transpose of the matrix, so all we need to do to get the inverse matrix is swap the sinYawAngle and -sinYawAngle values.

## Calculation 19: Update player velocity --------------------------------------

See code: UpdateVelocity

The main aim of the driving model is to update the player's velocity and 3D coordinates. First, we update the player's velocity along the ground by adding the acceleration that we just calculated:

```  xPlayerSpeed = xPlayerSpeed + xAcceleration << 5

zPlayerSpeed = zPlayerSpeed + zAcceleration << 3
```

We also need to apply the change in the yaw angle that we calculated, so the speed of the car's spin updates:

```  spinYawAngle = spinYawAngle + spinYawDelta << 3
```

## Calculation 20: Update player coordinates -----------------------------------------

See code: ApplyDeltas

Having updated the velocity, we can update the player's coordinates by simply adding the updated velocity, as the car moves and rotates by its velocity in each iteration of the driving loop:

```  xPlayerCoord = xPlayerCoord + xPlayerSpeed * 2

zPlayerCoord = zPlayerCoord + zPlayerSpeed * 2
```

We also apply the updated yaw angle rate of change to the car's yaw angle, which spins the car by the correct amount:

```  playerYawAngle = playerYawAngle + spinYawAngle
```

## Calculation 21: Apply jumps and drops -------------------------------------

See code: ApplyElevation

The final step is to calculate the car's elevation, or its y-coordinate, which we have ignored so far. There's quite a lot of calculation involved, so see the deep dive on jumps and drops for details.

The ApplyElevation routine does contains one calculation that isn't related to elevation, though, so we'll cover it here:

```  carSpeedHi for the player's car = playerSpeedHi * 2.13
```

This sets the car's forward speed in carSpeedHi, depending on the forward speed that we calculated above. playerSpeedHi is measured in mph while carSpeedHi is measured in 1/256 of a segment, so this multiplication converts from one unit to another, with 120 mph corresponding to one segment per iteration (as 120 * 2.13 = 255).

And that brings us to the end of the Revs driving model. It is impressively detailed for a 1985 8-bit driving game...