Hello All - This is Tutorial 2:
In the previous tutorial we showed how you might accelerate at full throttle as long as s.to_end exceeds some LARGE_FRACTION of s.cur_len. (This applies to any straightaway.)
When the car passes that point, you might as well put on the brakes. You should have accelerated as long as possible, and now you have to slow down for the turn. You can lock the wheels, like this:
vc = 0.0;
Which will give you maximum deceleration. As long as the wheels are locked it won't matter how alpha is set; you will continue in a straight line in whatever direction you were heading. If you need to steer, then you better let the wheels roll. For instance, suppose you checked the dead_ahead flag at this time. (when s.dead_ahead is non-zero, then another car is approximately right in front of you.) In order to do some kind of steering, to avoid hitting it, but still slow down rapidly, you could use this:
vc = .95 * s.v;
The wheel is now sliding at 5 percent of the cars speed, which will give plenty of deceleration. (see the friction() function) But it is also rolling at 95 percent of the cars speed, so it will still function well for steering. Again, this value of .95 might be better set at .91 or .98; that will probably only make a very small difference. Eventually, when your car is very competitive, very small differences can become very important. Racing is like that!
How should you steer if dead_ahead is non-zero? One way is to change the value of "lane", assuming you are using the steering method outlined in tutorial 1. A change of one or two car widths may be enough; you might pick the direction at random, or choose where there is more room.
Now you are nearing the turn. You must have some idea of how fast to take the turn. This could be obtained by trial and error, but we can get a pretty good idea using some high school physics. This will give us the three relations that:
centripetal acceleration = speed squared / radius of curvature
centripetal force = Mass * centripetal acceleration
traction force = Mass * g * coefficient of friction
A little simple algebra leads to the very practical result that:
speed = the square root of (coef. of friction * radius * g)
If the radius is in feet and we use g =32.2 ft/(sec*sec) this will give us the speed in ft/sec. This relation gives us the theoretical maximum cornering speed if we know the radius and the coef. of friction. We can take the radius to be a little larger than the inner radius of the turn, or maybe a lot larger if you can figure out a really good path. We can assume a value a little less than 1.0 for the coef. of friction, because, as was stated in tutorial 1, the slip rate will be vc * sine(alpha). (That is only true if there is not much tangential acceleration, i.e you are going around the corner at more-or-less constant speed.) vc will be close to s.v in this case, and only a few degrees of alpha will give enough slippage to generate over .9 for the coef. of friction, as long as s.v is not very slow.
The result of all that is the following very useful function:
double corn_spd(double radius) // returns maximum
cornering speed, fps
double rad; // absolute value of the radius
rad = radius < 0 ? -radius : radius; // make radius positive
return sqrt(rad * 32.2 * CORN_MYU); // compute the speed
Here, CORN_MYU is your estimate of the coefficient of friction due to cornering traction. (BTW, RARS version 0.5 can show this value on the screen in real time, along with some other numbers.) .95 is a decent value for CORN_MYU.
Now, when we are coming toward a turn, we can use s.nex_rad, which is its inner radius, to call the corn_spd() function. Then we will know how fast we are supposed to enter the turn. With that data we can compare our speed, s.v, to the cornering speed. As long as s.v exceeds the cornering speed, we continue braking. When we reach the point where s.v is less than the cornering speed, then we need to accelerate again, or at least maintain speed. Putting this all together, this is what we do from the point where we begin braking until we enter the turn:
speed = corn_spd(s.nex_rad + DIST_FROM_INSIDE) //
compute target speed
if(s.v > speed)
vc = .95 * s.v; // braking
vc = 1.05 * s.v; // accelerating when below speed
if(s.dead_ahead) // Change the lane a little if someone's
if(s.to_lft > s.to_rgt) // in your way.
lane -= DELTA_LANE; // lane must be a static variable
lane += DELTA_LANE;
This will slow us down until we hit cornering speed, and then keep us at that speed. It will also attempt to steer away from a car in our path as we approach the corner, assuming we are using the steering method described in tutorial 1.
The above algorithm suffers from rather drastic acceleration oscillations once the
desired speed is reached, since the tire never approaches the speed of the track; it is
always going 5% faster or 5% slower. Nothing terrible happens as a result of this, except
that steering suffers. This is not good if passing is required. A
smoother way is:
if(s.v > 1.02 * speed) // if we're 2% too fast,
vc = .95 * s.v; // brake hard.
else if(s.v < .98 * speed) // if we're 2% too slow,
vc = 1.05 * speed; // accelerate hard.
else // if we are very close to speed,
vc = .5 * (s.v + speed); // approach the speed gently.
The last formula, which computes vc as the arithmetic mean of s.v and the target speed, makes the wheel slip approach zero as s.v approaches the target speed. Furthermore the slip always has the correct sign to bring s.v closer to the target speed. At very high speeds, air drag will cause s.v to remain a little below the target speed.
Once we reach the end of the straight, and enter the corner, we should be traveling at about the right speed, and we only need to maintain a circular path. The speed should be maintained at "speed". We can maintain the speed using the same formula as above, except that the resulting vc should be divided by the cosine of alpha to account for vector effects. There exists a separate writeup of the vector relations between alpha, s.v, vc, tire slip, and the traction force vector. I can send that to those interested. Also, the implementation can be found in CARZ.Z, function move_car(), beginning with the label VC: and ending with tan_a = (Ft * temp - D) / M;
The same steering servo algorithm that was described in tutorial 1 can continue to be used throughout the curve, but two modifications are very desirable. First, a value should be chosen for lane. For long curves you usually want to keep to the inside, although it might be even better to make lane a function of s.to_end. A simple way is to set lane to a certain fraction of the width from the inside, i.e.:
width = s.to_lft + s.to_rgt;
if(s.cur_rad > 0.0) // turning left
lane = .2 * width;
else if(s.cur_rad < 0.0) // turning right
lane = .8 * width;
s.cur_rad is the radius of the inner rail of the
curve we are on.
A negative sign is used to indicate a right turn.
Would you believe that this paragraph is a secret message embedded in the tutorial? Yes it is!
The second important addition to the steering servo is a bias term. This is an estimate of the required alpha value, or, the alpha that you get when s.vn is zero and s.to_left == lane. The bias is positive for left turns, negative for right turns. Furthermore, it is small for high speed turns but has large magnitude for low speed turns. A reasonable formula for the alpha bias is:
bias = atan(BIG_SLIP / speed);
Where BIG_SLIP is a slip rate large enough to get the desired lateral force. A good value for BIG_SLIP might be in the range of 12.0 to 20.0. The atan() is theoretically correct, but in practice just BIG_SLIP / speed is close enough; there won't be much difference. Without the bias added to alpha, the car will drift far from the desired lane value.
In tutorial 3 we will put all of the code from the first two tutorials
together into a complete robot.