## Preface

In part 1 we have shown how to make a car(truck) glide on a track made out of 3D triangles.

We were able to steer the car and it was “glued” to the track to make it seem like there are physics involved.

In this part we are going to have an actual physics simulation(to some degree).

## The Models

The model of the track remains the same as in part 1(a set of 3D triangles).

The car model is made of 5 points: 4 for the wheels and 1 for the bottom center. Just like in part 1.

However, we will also maintain variables to keep the orientation of the car and the gravity velocity of each of the 4 wheels(in addition to the car’s gravity velocity).

## Steering

Since we now maintain the orientation of the car from the previous frame we can’t just assume the steering vector is calculated aligned to the xz plane.

To calculate the new steering vector we rotate the Look and Right vectors by a delta of an angle on the Look x Right plane:

Look = LastRight*sin(DeltaAngle)+ LastLook*cos(DeltaAngle); Right = LastRight*cos(DeltaAngle)- LastLook*sin(DeltaAngle);

The delta of the angle is the angular speed multiplied by the frame’s time delta.

The forward velocity is calculated the same as in part 1 but notice the Look vector might not be on the xz plane this time.

## Gravity

This time we are also going to take gravity into account.

Our gravity is the following vector (0, -9.8, 0).

It is a vector pointing downwards with an acceleration of 9.8 meters per square second.

We compose the car’s velocity from two components. The steering velocity from above and the Gravity velocity.

The reason we separate the two is because it would be easier to set the gravity velocity to 0 whenever a point in the model hits the ground or track.

We also need to maintain a separate gravity velocity for each of the 5 points in the car model to make the physics simulation of the car work as if we had an object with volume.

## The Simulation

After we calculated the Look and Right orientation vectors and the steering velocity vector we need to apply gravity.

For each of the 5 gravity velocity vectors we add the Gravity vector multiplied by the frame’s time delta.

CurrentGravityVelocity=CurrentGravityVelocity+Gravity*t; for (unsigned int i=0; i<WheelsGravityVelocity.size(); i++) WheelsGravityVelocity[i]=WheelsGravityVelocity[i]+Gravity*t;

The next step is to calculate the current position of the wheels and the position of the car center.

For the car center position, we add the current steering velocity vector and the current gravity velocity vector multiplied by the frame’s time delta.

Like so:

Pos = Pos+(Look*CurrentFlatSpeed+CurrentGravityVelocity)*t;

For each of the 4 wheels we calculate the wheel’s position relative to the car’s center using the wheels base dimensions and the Look and Right orientation vectors.

We then add the car’s center position to each of the 4 wheels positions.

The last step is to add the steering velocity and the gravity velocity multiplied by the time delta. The steering velocity is the same as it was for the car’s center but notice the gravity velocity might be unique for each wheel point(We saved them in their own variables).

for (unsigned int i=0; i<Wheels.size(); i++) Wheels[i] = Right*WheelsDelta[i].x+Look*WheelsDelta[i].z+Pos+(Look*CurrentFlatSpeed+WheelsGravityVelocity[i])*t;

In a similar fashion to part 1 we now check which triangles each one of the 5 points of the model intersect with.

The difference is that instead of setting the points to the intersection height of the respective triangles, we only set them to the intersection height in case their own height is lower.

In addition we set to zero the Gravity velocity of each point only if it’s height was adjusted by a triangle.

Now we have 5 points of the car displaced into new heights. The car’s center point will be used for the new position, but like in part 1, we need to calculate the new orientation from the 4 wheel points.

The current car model simulates a rigid body. However while adjusting the wheels’ heights the car’s wheel base is now deformed.

We will restore the original form of the wheel base by treating the 4 wheels as if they have springs among themselves(a total of 6 springs).

This will make the 4 wheel points simulate the wheels base as a if it was a rigid body.

In order to restore the original form of the wheels base we go over all the 6 springs and adjust them to be closer to their original length.

We iterate over this process for ten times and at the end we would get something closer to the original form.

for (unsigned int k=0; k<10; k++) { for (unsigned int i=0; i<Wheels.size(); i++) for (unsigned int j=i+1; j<Wheels.size(); j++) { Graphics2D::Position v = Wheels[i]-Wheels[j]; Graphics2D::Position center = (Wheels[i]+Wheels[j])*0.5; double l = (WheelsDelta[i]-WheelsDelta[j]).Length(); double radius = (0.1*l+0.9*v.Length())/2.0; Wheels[i] = (Wheels[i]-center).Normalize()*radius+center; Wheels[j] = (Wheels[j]-center).Normalize()*radius+center; } }

Now that we have the wheel points placed on the wheels base frame we can calculate the new Look and Right orientation vectors in a similar fashion we did in part 1.

Look = (Wheels[0]-Wheels[3]).Normalize(); Right = (Wheels[1]-Wheels[0]).Normalize();

## Conclusion

We now have a more physically based simulation that also support falling off from edges.

The result of this simulation can be seen in this video:

For the sake of completion I am adding the entire code for the update function.

void Update (double t) { double WheelFactor = 0; if (Input.GetLeft()) WheelFactor = -1; else if (Input.GetRight()) WheelFactor = 1; if (Input.GetThrust()) CurrentFlatSpeed += MaxFlatSpeed*t/AccelLatency; CurrentFlatSpeed = std::max(std::min(CurrentFlatSpeed, MaxFlatSpeed), 0.0); double DeltaAngle = WheelFactor*t; Graphics2D::Position Look = LastRight*sin(DeltaAngle)+ LastLook*cos(DeltaAngle); Graphics2D::Position Right = LastRight*cos(DeltaAngle)- LastLook*sin(DeltaAngle); CurrentGravityVelocity=CurrentGravityVelocity+Gravity*t; for (unsigned int i=0; i<WheelsGravityVelocity.size(); i++) WheelsGravityVelocity[i]=WheelsGravityVelocity[i]+Gravity*t; CurrentVelocity = Look*CurrentFlatSpeed+CurrentGravityVelocity; CarParms->SetLook (Look, Graphics2D::Position(0, 1, 0)); std::vector<Graphics2D::Position> Wheels; Wheels.resize(WheelsDelta.size()); for (unsigned int i=0; i<Wheels.size(); i++) Wheels[i] = Right*WheelsDelta[i].x+Look*WheelsDelta[i].z+Pos+(Look*CurrentFlatSpeed+WheelsGravityVelocity[i])*t; Pos = Pos+CurrentVelocity*t; if (Pos.y<0.0) CurrentGravityVelocity.y = std::max(0., CurrentGravityVelocity.y); Pos.y = std::max(0.0, Pos.y); unsigned int StartX = std::min((unsigned int)(std::max((Pos.x-Min.x)/(Max.x-Min.x), 0.0)), TrackGrid[0].size()-1); unsigned int StartZ = std::min((unsigned int)(std::max((Pos.z-Min.z)/(Max.z-Min.z), 0.0)), TrackGrid.size()-1); std::list<unsigned int>::iterator q; for (q = TrackGrid[StartZ][StartX].begin(); q != TrackGrid[StartZ][StartX].end(); q++) { const math::Ray r(float3(Pos.x, 100.0, Pos.z), float3(0, -1, 0)); float d = 0; math::float3 Point; if (TrackGeometry[*q].Intersects(r, &d, &Point)) { if (r.pos.y-d>=Pos.y) { CurrentGravityVelocity.y = std::max(0., CurrentGravityVelocity.y); Pos.y = r.pos.y-d; } } } CarParms->SetPosition (Pos); // std::vector<bool> IsWheelContact; // IsWheelContact.resize(4, false); for (unsigned int i=0; i<Wheels.size(); i++) { Graphics2D::Position p = Wheels[i]; unsigned int StartX = std::min((unsigned int)(std::max((p.x-Min.x)/(Max.x-Min.x), 0.0)), TrackGrid[0].size()-1); unsigned int StartZ = std::min((unsigned int)(std::max((p.z-Min.z)/(Max.z-Min.z), 0.0)), TrackGrid.size()-1); std::list<unsigned int>::iterator q; for (q = TrackGrid[StartZ][StartX].begin(); q != TrackGrid[StartZ][StartX].end(); q++) { const math::Ray r(float3(p.x, 100.0, p.z), float3(0, -1, 0)); float d = 0; math::float3 Point; if (TrackGeometry[*q].Intersects(r, &d, &Point)) { if (r.pos.y-d>=Wheels[i].y) { WheelsGravityVelocity[i].y = std::max(0., WheelsGravityVelocity[i].y); // IsWheelContact[i] = true; Wheels[i].y = r.pos.y-d; } } } if (FloorHeight>=Wheels[i].y) { WheelsGravityVelocity[i].y = std::max(0., WheelsGravityVelocity[i].y); // IsWheelContact[i] = true; Wheels[i].y = FloorHeight; } } for (unsigned int k=0; k<10; k++) { for (unsigned int i=0; i<Wheels.size(); i++) for (unsigned int j=i+1; j<Wheels.size(); j++) { Graphics2D::Position v = Wheels[i]-Wheels[j]; Graphics2D::Position center = (Wheels[i]+Wheels[j])*0.5; double l = (WheelsDelta[i]-WheelsDelta[j]).Length(); double radius = (0.1*l+0.9*v.Length())/2.0; Wheels[i] = (Wheels[i]-center).Normalize()*radius+center; Wheels[j] = (Wheels[j]-center).Normalize()*radius+center; } } Look = (Wheels[0]-Wheels[3]).Normalize(); Right = (Wheels[1]-Wheels[0]).Normalize(); LastLook = Look; LastRight = Right; Graphics2D::Position Up = Look.Cross(Right); CarParms->SetLook(Look, Up); }