PART 4



In this, the fourth in the tutorial series, we will improve the performance of the robot driver that was presented in the previous  tutorial.  TutorMan had good performance on two out of the six tracks that will be used for the April races.  On the other four, he was not able to complete the course.  The main thing to do first is to get him to stay on the track!

If you observe TutorMan try to negotiate various tracks, you will see that he runs off the track consistently if a tight turn follows a large radius turn.  He also runs off the track if a tight turn follows a short straightaway where the straightaway is entered at a high speed.

Both of these observations are easy to explain.  When the car is in a large radius turn, it naturally is going at a high rate of speed. Our corn_spd() function gives us a target speed proportional to the square root of the radius, like it should.  However, if the high speed curve is followed by a sharp corner, the car will enter the sharp corner at a speed appropriate to the high speed curve!  There is nothing in TutorMan's code to cause him to slow down for the corner.  You can observe this behavior on V03.TRK; Tutorman always goes off the track after the long, gradual right hand curve. On every straightaway, we have code to slow down for the corner.  The problem here is that the length of the part of the track where braking takes place is always equal to a fixed fraction of the length of the straightaway.  If it is a short straightaway, we will have a short braking section.  Since the acceleration section is also short, this would be OK if we entered the straightaway at a low speed.  But it often happens that the short straight is entered at a high speed, in which case it will still be going at a high speed when it reaches the braking section.  Again, the sharp curve will be entered at too high a speed, resulting in going off the track to the outside.  You can observe this behavior on ZANDVORT.TRK; Tutorman always goes off  the track in the second turn.

What we need is a more realistic way of deciding when to brake for the corner, and we also need to apply that rule on curves as well as straightaways.  The distance actually required to slow the car down for the curve depends on three things:

  1. How fast we are traveling,
  2. What speed we must have when we arrive at the corner, and
  3. Our rate of negative acceleration during braking.  Notice that the  length of the straightaway has no direct influence here; our former rule was totally inadequate!  What we need is a function that looks like this:

Once we have such a function, then we can compare our current distance from the next curve with this critical distance.  As long as we are farther than this distance from the next turn, we do not have to slow down. Otherwise, we must brake to adjust our speed for the next turn.

There is a well known formula for the distance traveled by a body with uniform acceleration during time T.  (Due, I believe, to Isaac Newton!)  Using the symbols chosen for our crit_dist() function, the formula is:

      dist = v0 * T + .5 * a_max * T * T
 
Now by the definition of uniform acceleration, and using dv to mean
the total change in speed during time T:

      dv = a_max * T       (where dv = v1 - v0)

T can be eliminated from those two equations by simply solving the
second for T, and substituting this for T in the first.  The result
is:
      dist = v0 * dv / a_max + .5 * dv * dv / a_max;

which we can calculate as:

      dist = (v0 + .5 * dv) * dv / a_max;

That is our desired formula for the required distance.
Our complete function can be written like this:

// calculates the critical distance necessary to bring a car from speed
// v0 to speed v1 when the braking acceleration is a_max, in ft per sec^2
// Speeds are in fps.  (a_max should be negative and v0 > v1)
double crit_dist(double v0, double v1, double a_max)
{
   double dv;

   dv = v1 - v0;
   if(dv > 0.0)          // this saves having such a test in the caller
      return(0.0);
   return (v0 + .5 * dv) * dv / a_max;
}
 
Now to apply this to our robot.  For the straightaways, instead of comparing s.to_end with some fraction of s.cur_len, we first compute this crit_dist() function, and compare s.to_end with that.  The first argument is s.v, our current speed.  The second is "speed", already computed, which is the target speed for the curve ahead.  The third argument will be another const parameter, which will replace ACCEL_FRACTION, which will no longer be used.  This is what we wind up with:

const double BRAKE_ACCEL = -31.0;     // acceleration when braking, ft/sec^2
            .
            .
            .
   speed = corn_spd(s.nex_rad + DIST_FROM_INSIDE); // target speed for next turn
            .
            .
            .
   if(s.cur_rad == 0.0) {             // If we are on a straightaway,
      if(s.to_end > crit_dist(s.v, speed, BRAKE_ACCEL))  // if far from the end,
         vc = s.v + 50.0;                           // pedal to the metal!
      else  {                    // otherwise, adjust speed for the coming turn:
            .
BRAKE_ACCEL could exceed 32.2 at high speed, due to air drag.  At low speed it could be less than 30.0.  This is one more parameter to experiment with.  (Yes, it could be made a function of s.v.  You could even calculate the true value by saving s.v from the previous call and seeing how much it changed.)

A number of other complexities arise when applying this approach to braking during curves.  The basic idea is still the same, we compare our distance to the end of the curve with a critical distance based on our current speed and the speed we must have at the end of the  curve.  If the curve is followed by a straightaway, or a curve of larger radius, we'll assume we don't have to slow down.  We are already using our cornering speed function to calculate a value for "speed", representing a target value for the curve we are in.  It will be necessary to also calculate the cornering speed for the next segment.  We will need another variable name, say "speed_next".  Furthermore, since the next segment may be a straightaway, we need to consider that, and give speed_next a large value in that case.

The next problem is: what should the braking acceleration be?  We are talking about braking during a high speed turn in order to slow down for another, sharper turn.  Clearly, we can't usually brake as hard as we would on a straightaway; we need to maintain some steering ability.  Our traction force vector is going to be used both for braking and centripetal acceleration.  The truth is that an optimal solution would require a much more sophisticated analysis.  What we will do is pick another const parameter for the braking acceleration during curves, and also another parameter for the amount of reduction in wheel speed that we use to cause braking.

One last thing to handle is that s.to_end is not a linear distance when the car is in a curve.  It is the angle in radians.  It can be converted to a curvilinear distance by multiplying it by the current radius.  We will assume that the current radius is s.cur_rad + DIST_FROM_INSIDE.
Putting everything together, We wind up with this:

const double BRK_CRV_ACC = -22.0;     // acceleration when braking in curve
const double BRK_CRV_SLIP = 4.0;      // tire slip for braking in curve
            .
            .
            .
// Modified cornering speed function - returns 250.0 if called for
// a straightaway, assuming that DIST_FROM_INSIDE has been added to radius.
// 250.0 is approximately the car's top speed.
double corn_spd(double radius)     // returns maximum cornering speed, fps
{
     double rad;                             // absolute value of the radius

     if(radius == DIST_FROM_INSIDE) //if called for straight, return top speed
        return 250.0;
 
     rad = radius < 0 ? -radius : radius;    // make radius positive

     return sqrt(rad * 32.2 * CORN_MYU);     // compute the speed
}
            .
            .
            .
   double to_end;     // distance to end of curve, feet
            .
            .
            .
   if(s.cur_rad == 0.0) {
      bias = 0.0;
      speed = corn_spd(s.nex_rad + DIST_FROM_INSIDE);
   }
   else  {
      speed = corn_spd(s.cur_rad + DIST_FROM_INSIDE);
      speed_next = corn_spd(s.nex_rad + DIST_FROM_INSIDE);
            .
            .
            .
   if(s.cur_rad != 0.0)  {       // If we are in a curve,
         // calculate vc to maintain speed in corner
         vc = .5 * (s.v + speed)/cos(alpha);
         // calculate distance to end of curve:
         if(s.cur_rad > 0.0)
            to_end = s.to_end * (s.cur_rad + DIST_FROM_INSIDE);
         else
            to_end = -s.to_end * (s.cur_rad - DIST_FROM_INSIDE);
         // compute required braking distance and compare:
         if(to_end <= crit_dist(s.v, speed_next, BRAKE_CURVE))
            vc -= BRK_CRV_SLP;
      }

With those changes incorporated into our robot it should be able to
get around almost any track.  The next tutorial will consist of another
compilable robot program, incorporating all of the above.