Jumping over the verge and dropping down from the retrieval crane
This article details the calculations that the driving model performs when the car is jumping or being dropped from the crane. It is designed to be read alongside the core driving model calculations.
The following is done as part of calculation 21 in the core driving model. The code for the following is in the ApplyElevation routine, and the aim of this calculation is to calculate the y-coordinate of the car (i.e. its elevation).
Applying lift to the nose
-------------------------
See code: ApplyElevation (Part 1 of 5)
We start by calculating the amount that the car nose moves up or down, according to whether we are accelerating or braking. This is stored in the liftFromTorque variable, which is in the range -5 to +3. A positive value of liftFromTorque means the nose is being pulled up, and the horizon and all 3D objects get drawn lower on the screen.
The value of liftFromTorque is only changed if the car is on the ground. The logic is as follows:
- The horizon goes down when we are not in reverse, we are applying the throttle and engine torque is non-zero (i.e. we pull the nose of the car up by incrementing liftFromTorque, to a maximum of 3).
- The horizon goes up when we are not in reverse, we are applying the brakes and we are moving (i.e. we push the nose of the car down by decrementing liftFromTorque, to a minimum of -5).
- Otherwise, we gradually bring the nose back to the normal level of zero, with the value incrementing towards zero at twice the rate that it decrements towards zero (so the nose rises at twice the speed that it falls, as the suspension springs are more powerful than gravity.
The value of liftFromTorque is used in the calculation of the player's pitch angle below, which affects the position of the horizon line.
Calculate the player's heading
------------------------------
See code: ApplyElevation (Part 2 of 5)
Next up, we calculate the values of playerHeading and playerSideways, which are to do with the direction the player is facing on the track.
- playerHeading is the player's yaw angle, relative to the direction of the track, where a heading of 0 means the player is pointing straight along the track.
- playerSideways contains the following information:
- If playerSideways < 40, then the player's car is facing forwards or backwards along the track
- If playerSideways >= 40, then the player's car is facing sideways, relative to the direction of the track
Calculate the player's pitch angle
----------------------------------
See code: ApplyElevation (Part 3 of 5)
We now calculate playerPitchAngle, the player's pitch angle. We start by setting A to the elevation change of the car due to the sideways angle of the car in the current segment, and then we calculate the angle as follows:
playerPitchAngle = ( A + bumpyGrassHeight + liftFromTorque + yJumpHeight + playerPitchAngle) / 2
So playerPitchAngle contains its previous value, plus four factors that affect the car height, and then we halve the sum before storing the result in playerPitchAngle. This approach slows down the rate of change in playerPitchAngle by 50%, so the horizon doesn't judder as much as it would if we just set playerPitchAngle to the sum of the four other factors
Incidentally, the value of playerPitchAngle affects everything that is drawn on-screen. The GetObjPitchAngle routine, which calculates the pitch angle of a 3D object, contains the following code when returning the pitch angle in LL:
SEC SBC playerPitchAngle STA LL
This subtracts playerPitchAngle from the pitch angle that's returned. This means that higher values of playerPitchAngle result in lower pitch angles for all objects, moving them all down the screen (as higher pitch angles are higher up the screen). And because the same routine is used to calculate pitch angles for track sections and track segments, which in turn are used to determine the position of the horizon in horizonLine, changing the value of playerPitchAngle also moves the horizon line up and down by the same amount.
Or, to put it another way, increasing any of the elements in the above sum - such as increasing the lift of the nose in liftFromTorque, or jumping higher in yJumpHeight - will push everything in the track view down the screen, just as you would expect.
We also take this opportunity to calculate the spin pitch angle. This contains the rate of change in the pitch angle (so it's the amount of spin around the x-axis). The calculation is as follows:
spinPitchAngle = playerPitchAngle - previous value of playerPitchAngle
So the spin pitch angle is literally the change in the player's pitch angle between this iteration and the last one.
Apply gravity
-------------
See code: ApplyElevation (Part 4 of 5)
We now apply gravity to the car. During the previous steps in the driving model, the values of yGravityDelta and yJumpHeight can be set to implement a jump; calculation 5 is a good example, where the jump height is set if we hit the track verge. We now take those values and apply gravity to them.
We start by decrementing and clipping yGravityDelta, which is the distance in the y-axis that the car would fall if the track wasn't there:
yGravityDelta = max(yGravityDelta - 4, -56)
If there aren't any other jumps or bounces happening, and the car is on the ground, then yGravityDelta gets set to 0 at the start of the ApplyElevation routine, so the above sets yGravityDelta to -4. If any jumps or bounces are in progress, then this decreases yGravityDelta by 4 on each iteration, simulating the downward acceleration due to gravity's pull.
If the car falls past ground level, then we calculate the following to make the car bounce upwards by half the rate that it's falling, and apply a small amount of yaw:
yGravityDelta = |yGravityDelta| / 2 yJumpHeight = |yGravityDelta| / 4 spinYawAngleHi = spinYawAngleHi >> 1 with bit 7 set heightAboveTrack = 1
and we make the crash/contact sound to indicate that we've just hit the ground.
If, on the other hand, the car is still above ground, we apply gravity as follows:
heightAboveTrack = heightAboveTrack + yGravityDelta
which changes the height of the car above the track by the gravity delta (which will push the car up if it's been set to bounce up, or pull the car down otherwise).
Note that while the value of heightAboveTrack is typically set by the driving model routine in this way, when we crash, heightAboveTrack gets set to 127 by the CheckForCrash routine. If this is an Amateur or Professional race then the race ends then and there, and if this is a practice or qualifying lap then ResetVariables gets called, which sets heightAboveTrack back to 0. But if this is a Novice race, then the main driving loop is restarted without calling ResetVariables, so the value of 127 is retained and when we rejoin the race, we do so from this height above the track, and the gravity routines here simulate being dropped from the retrieval crane.
Calculate the car's y-coordinate
--------------------------------
See code: ApplyElevation (Part 5 of 5)
The final step is to calculate the new y-coordinate (i.e. the elevation) of the car within the 3D world, as follows:
(yPlayerCoordTop yPlayerCoordHi) = (ySegmentCoordIHi ySegmentCoordILo) + carProgress * yTrackSegmentI + heightAboveTrack * 4 + 172
This adds the following elements to get the y-coordinate in (yPlayerCoordTop yPlayerCoordHi):
- (ySegmentCoordIHi ySegmentCoordILo) is the y-coordinate of the current track segment
- carProgress * yTrackSegmentI is the proportion of the height change across the segment for a car that is carProgress of the way through that segment
- heightAboveTrack * 4 is the value of heightAboveTrack that's been calculated throughout the driving model
- 172 is added to ensure that the calculated y-coordinate is always positive, as otherwise having large negative values of the other elements might give us a negative y-coordinate, which would be incorrectly interpreted as an extremely high elevation
And that brings us to the end of the jump and drop calculations.