Академический Документы
Профессиональный Документы
Культура Документы
Contents
15
1.3
Software . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 1.3.1 1.3.2 1.3.3 Checking OpenGL/DRI . . . . . . . . . . . . . . . . . 17 Checking GLUT . . . . . . . . . . . . . . . . . . . . . . 17 Checking Libpng . . . . . . . . . . . . . . . . . . . . . 18
1.4
1.5
Plib Installation . . . . . . . . . . . . . . . . . . . . . . . . . . 19 1.5.1 1.5.2 1.5.3 Checking Plib . . . . . . . . . . . . . . . . . . . . . . . 19 Download and Unpack Plib . . . . . . . . . . . . . . . 20 Compiling and Installing Plib . . . . . . . . . . . . . . 20 3
1.6
OpenAL Installation . . . . . . . . . . . . . . . . . . . . . . . 1.6.1 1.6.2 1.6.3 Checking for OpenAL . . . . . . . . . . . . . . . . . . Download and Unpack OpenAL . . . . . . . . . . . . Compiling and Installing OpenAL . . . . . . . . . . .
21 21 21 22 22 22 22 23 23 23 24 25 25 27 29 29 29 30 30 31 31 31 31
1.7
TORCS Installation . . . . . . . . . . . . . . . . . . . . . . . . 1.7.1 1.7.2 1.7.3 Unpacking Source Packages . . . . . . . . . . . . . . . Building TORCS . . . . . . . . . . . . . . . . . . . . . Installing TORCS . . . . . . . . . . . . . . . . . . . . .
1.8
TORCS Setup . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.8.1 1.8.2 1.8.3 Permissions . . . . . . . . . . . . . . . . . . . . . . . . Environment . . . . . . . . . . . . . . . . . . . . . . . Command Line Options . . . . . . . . . . . . . . . . .
1.9
Feedback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Introduction 2 Build a Very Simple Robot 2.1 Robot Skeleton . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.1.1 2.1.2 2.2 Generate the Robot Skeleton . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . . .
Build the Robot . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2.1 2.2.2 Compile and Install Your Robot . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . . .
2.3
3.2
Comments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 3.2.1 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.2.7 3.2.8 3.2.9 Performance . . . . . . . . . . . . . . . . . . . . . . . . 39 Boolean Expressions . . . . . . . . . . . . . . . . . . . 39 Portability . . . . . . . . . . . . . . . . . . . . . . . . . 40 For Loops . . . . . . . . . . . . . . . . . . . . . . . . . 40 Constants in Classes . . . . . . . . . . . . . . . . . . . 40 Magic Numbers . . . . . . . . . . . . . . . . . . . . . . 40 Numerical Problems . . . . . . . . . . . . . . . . . . . 41 Adding up Numbers . . . . . . . . . . . . . . . . . . . 41 Special Numbers . . . . . . . . . . . . . . . . . . . . . 41
3.3.2 3.4
Adding Cars . . . . . . . . . . . . . . . . . . . . . . . .
43 45 45 46 46 47 47 47 47 51 54 54 54 55 55 55 56 56 57 57 58 59 59
Basic Implementation . . . . . . . . . . . . . . . . . . . . . . . 3.4.1 3.4.2 3.4.3 3.4.4 Initialization . . . . . . . . . . . . . . . . . . . . . . . . Shutdown . . . . . . . . . . . . . . . . . . . . . . . . . Downloads . . . . . . . . . . . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . . .
3.5
Finishing Implementation . . . . . . . . . . . . . . . . . . . . 3.5.1 3.5.2 3.5.3 3.5.4 3.5.5 3.5.6 Overview . . . . . . . . . . . . . . . . . . . . . . . . . The Driver Class Denition driver.h . . . . . . . . . . Changing bt.cpp . . . . . . . . . . . . . . . . . . . . . Changing the Makele . . . . . . . . . . . . . . . . . . Downloads . . . . . . . . . . . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . . .
3.6
Improving Getting Unstuck . . . . . . . . . . . . . . . . . . . 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 Current Version . . . . . . . . . . . . . . . . . . . . . . Look Inside Criteria . . . . . . . . . . . . . . . . . . . Additional Conditions . . . . . . . . . . . . . . . . . . The Implementation . . . . . . . . . . . . . . . . . . . Downloads . . . . . . . . . . . . . . . . . . . . . . . . Feedback . . . . . . . . . . . . . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . . .
Basic Braking . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 4.2.1 4.2.2 4.2.3 4.2.4 4.2.5 Braking Distance . . . . . . . . . . . . . . . . . . . . . 63 Implementation . . . . . . . . . . . . . . . . . . . . . . 64 Testdrive . . . . . . . . . . . . . . . . . . . . . . . . . . 66 Downloads . . . . . . . . . . . . . . . . . . . . . . . . 66 Summary . . . . . . . . . . . . . . . . . . . . . . . . . 66 67
5.2
5.3
Steering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 5.3.1 5.3.2 5.3.3 5.3.4 The Target Point . . . . . . . . . . . . . . . . . . . . . 71 Heading Toward the Target Point . . . . . . . . . . . . 73 Finishing Implementation . . . . . . . . . . . . . . . . 73 Testdrive . . . . . . . . . . . . . . . . . . . . . . . . . . 74 7
Downloads . . . . . . . . . . . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . . .
74 74 74 74 75 75 75 76 76 77 77 77 78 78 78 78 79 79 79 81 81 81 82
Reduce the Lap Time . . . . . . . . . . . . . . . . . . . . . . . 5.4.1 5.4.2 5.4.3 5.4.4 Implementation . . . . . . . . . . . . . . . . . . . . . . Testdrive . . . . . . . . . . . . . . . . . . . . . . . . . . Downloads . . . . . . . . . . . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . . .
5.5
Stay on the Track . . . . . . . . . . . . . . . . . . . . . . . . . 5.5.1 5.5.2 5.5.3 5.5.4 Implementation . . . . . . . . . . . . . . . . . . . . . . Testdrive . . . . . . . . . . . . . . . . . . . . . . . . . . Downloads . . . . . . . . . . . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . . .
5.6
Trajectories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.6.1 5.6.2 5.6.3 5.6.4 5.6.5 5.6.6 The Optimal Trajectory? . . . . . . . . . . . . . . . . . Heuristic Trajectories . . . . . . . . . . . . . . . . . . . Geometric Trajectories . . . . . . . . . . . . . . . . . . Machine Learning Approaches . . . . . . . . . . . . . Feedback . . . . . . . . . . . . . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . . .
6 Using Custom Car Setups 6.1 What is a Car Setup . . . . . . . . . . . . . . . . . . . . . . . . 6.1.1 6.1.2 Qualifying Setup . . . . . . . . . . . . . . . . . . . . . Racing Setup . . . . . . . . . . . . . . . . . . . . . . . 8
Loading Custom Car Setups . . . . . . . . . . . . . . . . . . . 83 6.2.1 6.2.2 6.2.3 Introduction . . . . . . . . . . . . . . . . . . . . . . . . 83 Loading Setups . . . . . . . . . . . . . . . . . . . . . . 83 Summary . . . . . . . . . . . . . . . . . . . . . . . . . 84 . . . . . . . . . . . . . . . . 84
6.3
6.4
Car Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.4.7 6.4.8 6.4.9 Setup File Format . . . . . . . . . . . . . . . . . . . . . 87 Settings Constraints . . . . . . . . . . . . . . . . . . . 87 Properties . . . . . . . . . . . . . . . . . . . . . . . . . 87 Initial Fuel . . . . . . . . . . . . . . . . . . . . . . . . . 87 Spoilers . . . . . . . . . . . . . . . . . . . . . . . . . . 88 Gearbox . . . . . . . . . . . . . . . . . . . . . . . . . . 88 Differential . . . . . . . . . . . . . . . . . . . . . . . . 89 Brakes . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 Wheels . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
6.5
Add Custom Properties . . . . . . . . . . . . . . . . . . . . . 6.5.1 6.5.2 6.5.3 6.5.4 6.5.5 Introduction . . . . . . . . . . . . . . . . . . . . . . . . Implementation . . . . . . . . . . . . . . . . . . . . . . Accessing Subsections . . . . . . . . . . . . . . . . . . Downloads . . . . . . . . . . . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . . .
92 92 92 93 93 93 94 94 96 97 98 98 98 99 99
6.6
Setup the Car . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.6.6 Test Drive 1 . . . . . . . . . . . . . . . . . . . . . . . . Test Drive 2 . . . . . . . . . . . . . . . . . . . . . . . . Test Drive 3 . . . . . . . . . . . . . . . . . . . . . . . . Downloads . . . . . . . . . . . . . . . . . . . . . . . . Feedback . . . . . . . . . . . . . . . . . . . . . . . . . . Summary . . . . . . . . . . . . . . . . . . . . . . . . .
7 Pit Logo and Painting Your Car 7.1 The Look . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.1.1 7.1.2 7.1.3 7.2
Creating the Pit Logo . . . . . . . . . . . . . . . . . . . . . . . 102 7.2.1 7.2.2 7.2.3 7.2.4 Anatomy of the Pit Logo . . . . . . . . . . . . . . . . . 102 Create the Logo . . . . . . . . . . . . . . . . . . . . . . 102 Changing the Makele . . . . . . . . . . . . . . . . . . 103 Summary . . . . . . . . . . . . . . . . . . . . . . . . . 103 10
7.3
Painting the Car . . . . . . . . . . . . . . . . . . . . . . . . . . 104 7.3.1 7.3.2 7.3.3 7.3.4 7.3.5 7.3.6 7.3.7 7.3.8 Overview . . . . . . . . . . . . . . . . . . . . . . . . . 104 Download the Default Texture . . . . . . . . . . . . . 104 Modify the Default Texture . . . . . . . . . . . . . . . 104 Save the Texture in the SGI rgb Format . . . . . . . . 104 Deploy the Texture . . . . . . . . . . . . . . . . . . . . 105 Downloads . . . . . . . . . . . . . . . . . . . . . . . . 105 Feedback . . . . . . . . . . . . . . . . . . . . . . . . . . 105 Summary . . . . . . . . . . . . . . . . . . . . . . . . . 105 107
Collecting Data About Opponents . . . . . . . . . . . . . . . 107 8.1.1 8.1.2 8.1.3 8.1.4 The Opponent Class . . . . . . . . . . . . . . . . . . . 107 The Opponents Class . . . . . . . . . . . . . . . . . . . 108 The opponent.h File . . . . . . . . . . . . . . . . . . . 108 Summary . . . . . . . . . . . . . . . . . . . . . . . . . 110
8.2
Collecting Data About Opponents Continued . . . . . . . . . 110 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 The opponents.cpp File . . . . . . . . . . . . . . . . . 110 Modifying driver.h . . . . . . . . . . . . . . . . . . . . 114 Compiling opponent.cpp . . . . . . . . . . . . . . . . 115 Downloads . . . . . . . . . . . . . . . . . . . . . . . . 115 Summary . . . . . . . . . . . . . . . . . . . . . . . . . 115
8.3
Changes in driver.h . . . . . . . . . . . . . . . . . . . . 116 Implementation in driver.cpp . . . . . . . . . . . . . . 116 Testdrive . . . . . . . . . . . . . . . . . . . . . . . . . . 118 Downloads . . . . . . . . . . . . . . . . . . . . . . . . 118 Summary . . . . . . . . . . . . . . . . . . . . . . . . . 118
Side Collision Avoidance . . . . . . . . . . . . . . . . . . . . . 118 8.4.1 8.4.2 8.4.3 8.4.4 Changes in driver.h . . . . . . . . . . . . . . . . . . . . 118 Implementation in driver.cpp . . . . . . . . . . . . . . 119 Downloads . . . . . . . . . . . . . . . . . . . . . . . . 120 Summary . . . . . . . . . . . . . . . . . . . . . . . . . 120
8.5
8.6
Finishing Overtaking . . . . . . . . . . . . . . . . . . . . . . . 122 8.6.1 8.6.2 8.6.3 8.6.4 8.6.5 Implementation . . . . . . . . . . . . . . . . . . . . . . 123 Testdrive . . . . . . . . . . . . . . . . . . . . . . . . . . 124 Downloads . . . . . . . . . . . . . . . . . . . . . . . . 124 Feedback . . . . . . . . . . . . . . . . . . . . . . . . . . 124 Summary . . . . . . . . . . . . . . . . . . . . . . . . . 124 125
Introduction . . . . . . . . . . . . . . . . . . . . . . . . 126 The Spline Implementation . . . . . . . . . . . . . . . 127 The Pit Path . . . . . . . . . . . . . . . . . . . . . . . . 129 Summary . . . . . . . . . . . . . . . . . . . . . . . . . 132
The Pit Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 9.4.1 9.4.2 The Header File . . . . . . . . . . . . . . . . . . . . . . 133 Summary . . . . . . . . . . . . . . . . . . . . . . . . . 136
9.5
Pit Utility Functions . . . . . . . . . . . . . . . . . . . . . . . . 136 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.5.6 Introduction . . . . . . . . . . . . . . . . . . . . . . . . 136 The toSplineCoord Method . . . . . . . . . . . . . . . 136 The getPitOffset Method . . . . . . . . . . . . . . . . . 136 The setPitstop Method . . . . . . . . . . . . . . . . . . 137 The isBetween Method . . . . . . . . . . . . . . . . . . 138 Summary . . . . . . . . . . . . . . . . . . . . . . . . . 138
9.6
Pit Strategy Functions . . . . . . . . . . . . . . . . . . . . . . 138 9.6.1 9.6.2 9.6.3 9.6.4 9.6.5 9.6.6 9.6.7 Introduction . . . . . . . . . . . . . . . . . . . . . . . . 138 The update Method . . . . . . . . . . . . . . . . . . . 139 The getFuel Method . . . . . . . . . . . . . . . . . . . 140 The getRepair Method . . . . . . . . . . . . . . . . . . 141 The Makele . . . . . . . . . . . . . . . . . . . . . . . 141 Downloads . . . . . . . . . . . . . . . . . . . . . . . . 142 Summary . . . . . . . . . . . . . . . . . . . . . . . . . 142
9.7
Changes in the Header File . . . . . . . . . . . . . . . 142 The Implementation . . . . . . . . . . . . . . . . . . . 143 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . 145 Summary . . . . . . . . . . . . . . . . . . . . . . . . . 146
Put it All Together . . . . . . . . . . . . . . . . . . . . . . . . . 146 9.8.1 9.8.2 9.8.3 9.8.4 9.8.5 9.8.6 9.8.7 9.8.8 9.8.9 Introduction . . . . . . . . . . . . . . . . . . . . . . . . 146 Changes in driver.h . . . . . . . . . . . . . . . . . . . . 146 Creation and Destruction of the Pit Object . . . . . . . 147 Changes in Driver::drive(tSituation *s) . . . . . . . . . 147 The Pit Callback . . . . . . . . . . . . . . . . . . . . . . 147 Change the Target Point Computation . . . . . . . . . 148 Change the Update . . . . . . . . . . . . . . . . . . . . 149 Test Drive . . . . . . . . . . . . . . . . . . . . . . . . . 149 Final Remarks . . . . . . . . . . . . . . . . . . . . . . . 150
9.8.10 Downloads . . . . . . . . . . . . . . . . . . . . . . . . 150 9.8.11 Feedback . . . . . . . . . . . . . . . . . . . . . . . . . . 150 9.8.12 Summary . . . . . . . . . . . . . . . . . . . . . . . . . 150 A The Math of Cubic Splines 151
A.1 Problem Statement . . . . . . . . . . . . . . . . . . . . . . . . 151 A.2 Algorithm Overview . . . . . . . . . . . . . . . . . . . . . . . 152 A.3 Spline Interpolation . . . . . . . . . . . . . . . . . . . . . . . . 152 A.4 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
14
1.1 Conventions
During the installation you have to run several commands in a shell. If a command starts with a hash (#) you should run this command as superuser (root), if it starts with a $ you should run it as normal user.
16
works well and what does not. Your z-buffer should have a depth of 24 bits or more to avoid glitches.
1.2.1 K6-III-400
K6-III-400MHz (66MHz memory bus) with 256MB RAM, motherboard Asus TX-97E, graphics card Nvidia Riva TNT with 16MB RAM (PCI). I ran it with 16 bit color depth and came to the following conclusions: This system is ok to practice without opponents (8-45 frames per second). Running races with robots alone is possible, but driving manually is difcult because the 6-20 frames per second (with 4 opponents) are not enough for a smooth ride. So the system is the bare minimum sufcient for developing your driver (robot).
1.2.2 SMP-PIII-550MHz
Two PIII-550MHz (100MHz memory bus) with 512 MB RAM, motherboard Asus P2B-DS, graphics tested with GeForce DDR, GeForce 2 GTS, GeForce 3. I ran it with 32 bit color depth. TORCS runs almost as good on the GeForce DDR like on the GeForce 3, so you dont need the latest and greatest graphics card. What is more important is working AGP, recent drivers and a fast CPU. TORCS runs just a single process (no threads), so it doesnt take advantage of SMP systems. In practice mode, I get more than 100 frames per second and in races with 20 cars 10-50 frames per second. This system works very well for developing a driver (robot) and to drive manual races with up to 10 opponents.
1.2.3 PIII-800MHz
PIII-800MHz (100MHz memory bus) with 512 MB RAM, motherboard Asus P2B, GeForce 2 GTS, 32 Bit color depth. Runs almost always smooth above 20 frames per second, so with 800MHz or more you should be able to enjoy racing. 16
1.3. Software
17
1.3 Software
You need a working OpenGL/DRI driver, development tools and some additional libraries. Most often you can go into your Linux distribution setup and simply click something like development machine and all necessary tools will be installed. You need gcc, header les for OpenGL, GLUT, GLU, XFree86 and libc. OpenGL driver setup is most often supported by the distribution, consult the documentation. Below we will check if the basic requirements are fulllled. If you have already compiled OpenGL applications and you are sure your hardware is set up correct, skip the rest of this page.
If thats not the case you have to check your OpenGL setup.
The result should look something like this (this depends on the package names): mesaglut-3.4.2-42 mesaglut-devel-3.4.2-42 17
18
If the result lists nothing you have to install GLUT. Use the tool provided from your distribution. Make sure that you also install glut.h. If just one package shows up, you can list the package content and search for glut.h:
$ rpm q l NAME | grep g l u t . h
where NAME means in the above example mesaglut or mesaglut-devel, without version numbers. If nothing shows up, glut.h is not installed, so search your distribution and install it.
The result should look something like this (this depends on the package names):
libpng 2.1.0.12 153
If the result lists nothing you have to install libpng with header les (eg. png.h).
19
20
$ rpm qa | grep p l i b
In case we nd plib 1.8.3 everything is ne and you can skip the rest of this section. If you nd plib in any other version (e. g. 1.4.2., 1.5.0 or 1.6.1) you have to remove the packages: plib-1.4.2-30 plib-examples-1.4.1-32 Remove rst the plib-examples package and then plib (you need to be root):
# rpm e p l i b examples # rpm e p l i b
There is no danger in removing plib, because it contains just static libraries, so if something goes wrong you can force the remove.
20
21
If something fails you need to resolve it. In case the congure script complained about something you can nd some additional information in the le cong.log. The cause for problems are usually missing header les or libraries, wrong versions or multiple versions installed.
In case you nd an installed version uninstall it. Remove rst the openal-devel package and then openal (you need to be root):
# rpm e openaldevel # rpm e openal
There is a certain danger in removing OpenAL, because it is a shared library. If the package management system refuses to remove it, try to stick with the installed version, perhaps TORCS will compile with it (but you will expirience some sound glitches with that).
21
22
If something fails you need to resolve it. In case the congure script complained about something you can nd some additional information in the le cong.log. The cause for problems are usually missing header les or libraries, wrong versions or multiple versions installed.
Optionally you can pass enable-debug (to build a debug version) and/or without-xrandr (does not use xrandr extension) as argument to congure. If there are any problems reported, you have to resolve them now. Remember the cong.log le which contains detailed information. If you get stuck, dont hesitate and mail to the torcs-users mailing list. Now start building: 22
23
# make >& e r r o r . l o g
When compiling is done, we check the error.log for... errors, yep. TORCS doesnt stop on compile errors, so you have to be careful. You can search the le for the string error, if there is no hit, everything is ne. Because congure checkes very well there will be most often no errors, which is good, or quite bizare errors, which is not so good. In case you have SuSE 7.3 and upgraded to XFree86 4.2.0 from SuSE, you have a library in two versions, which will cause a linking error against GLU. Simply delete the le /usr/X11R6/lib/libGLU.a (the right version is in /usr/lib). This is rather strange, because its a static library.
23
24
# # # # # chown R uname : ugroup t o r c s cd /usr / l o c a l / s h a r e/games chown R uname : ugroup t o r c s cd /usr / l o c a l / l i b chown R uname : ugroup t o r c s
If the sound doesnt work, you have perhaps to put your user in a group like audio. Look up your distribution manual for more information about sound. Youre nished with administrator stuff, so you can go back to your normal user account.
1.8.2 Environment
Now we have to set up some environment variables. That you dont need to type this all the time, you put it best in your shells rc le. For bash on SuSE that is .bashrc in your home directory. Copy the following on the end of the le. Dont forget to put a newline after the last line in the le:
e x p o r t LD LIBRARY PATH=$LD LIBRARY PATH : / usr / l o c a l / l i b e x p o r t TORCS BASE=/usr / s r c / t o r c s / t o r c s 1 . 2 . 4 e x p o r t MAKE DEFAULT=$TORCS BASE/Maked e f a u l t .mk
Open a new terminal with a shell to check if the .bashrc did its job. Check if the variables have the right values. We will do now a nal check if everything is set up correct by recompiling a single driver (remember, you are working now as normal user in a shell where the above variables are set):
$ $ $ $ $ cd $TORCS BASE cd s r c / d r i v e r s/ t a n h o j make c l e a n make make i n s t a l l
If TORCS hangs or something went wrong, double check the stuff and look up the torcs mailing list archives. And you know, if you get stuck... the force may be with you. On the ofcial TORCS site is a nice guide how to set up your joystick with TORCS. Look at Sections, How-To Drive. 24
1.9. Feedback
25
1.9 Feedback
Let me know if you read this chapter and your thoughts about it. Please send me also spelling, grammar, math and code corrections. Thank you for the feedback.
25
Requirements
You need to have TORCS installed exactly according to the installation1 section (if you have not read the installation instructions, then do it before you start, even if TORCS runs perfectly). It helps if you already know a programming language and a bit about data structures like linked lists.
1
27
28
You have to write your robot in C or C++, but thats easy if you already know another programming language, because you just need very easy stuff like basic data types, structures, pointers, arrays and functions. So if you are not familiar with C or C++, simply read further and look up a C or C++ tutorial on demand. You can nd such tutorials on the internet, pick one that ts your needs.
Resources
There is a reference manual and much more on the ofcial TORCS site. Let us know about your robot and make it available, so that the world can enjoy your work. If you want discuss about TORCS, mail to the torcs-users mailing list.
28
You have to tell the script the robots name, your name, the car you choose, and optional a description and if you want GPL headers. You can look up the available cars with
$ l s /usr / l o c a l / s h a r e/games/ t o r c s / c a r s 155DTM 206W10 306W61 360modena
1
29
30
The result depends on what TORCS car packages you have installed. To follow the tutorial you should choose cg-nascar-rwd for the car. It is very easy to change that later to your favourite car, so dont worry. I would run now the robotgen script with
$ cd $TORCS BASE $ . / robotgen n b t a Bernhard Wymann c cgnascar rwd gpl Generation Generating Generating Generating Generating Generating o f r o b o t b t author Bernhard Wymann s r c / d r i v e r s/ b t / M a k e f i l e . . . done s r c / d r i v e r s/ b t / b t . xml . . . done s r c / d r i v e r s/ b t / b t . cpp . . . done s r c / d r i v e r s/ b t / b t . def . . . done s r c / d r i v e r s/ b t / b t . dsp . . . done
You should run the script with your name and a fancy name for your robot. Keep in mind on further instructions to replace bt by your robots name. To check if everything worked we compile and install now your driver. Dont worry about the les, Ill explain them on demand.
2.1.2 Summary
You have generated the les. You are aware to replace bt with your robots name.
30
31
I would suggest using an editor which supports at least syntax highlighting, easy to use are nedit and kate (choose C++ mode). I prefer kate.
It should now be compiled. If you get errors, check the installation and the environment variables. If everything worked ne install your robot with
$ make i n s t a l l
In case you get errors here check if the compilation worked and if the permissions are correct in /usr/local/share/games/torcs. If everything worked ne, start TORCS and choose practice, choose your robot and start a practice session. Here it is and stays and stays and... Cool!
2.2.2 Summary
You know where your robots sources are located. You know how to compile and install your robot. You know how to start TORCS and run a practice session. The build environment is set up properly. You decided which editor you want to use.
32
segments are usually short, so a turn that looks like a big turn or a long straight is most often split into much smaller segments. The segments are organized as linked list in the memory. A straight segment has a width and a length, a turn has a width, a length and a radius, everyting is measured in the middle of the track. All segments connect tangentially to the next and previous segments, so the middle line is smooth. There is much more data available, the structure tTrack is dened in $TORCS BASE/export/include/track.h.
32
33
The distance to the middle is measured in meters, so the values are too big to add it directly to the steering angle. So we divide it rst through the width of the track and multiply it with a tuning constant. Now we put all the stuff together:
f l o a t angle ; co n st f l o a t SC = 1 . 0 ; a n g l e = RtTrackSideTgAngleL (&( car t r k P o s ) ) car yaw ; > > NORM PI PI ( a n g l e ) ; / / p u t t h e a n g l e b a c k i n t h e r a n g e fr om PI t o PI a n g l e = SC car t r k P o s . toMiddle/car t r k P o s . seg > > >width ;
We need also to change to the rst gear and apply a bit of accelerator pedal. The accelerator and brakes ranges from [0..1], the steer value from [-1.0..1.0]. Now this should raise a question. In the upper code snippet I computed a steering angle, so how do we convert this to [-1.0..1.0]? For that there is a constant in the tCarElt struct:
car s t e e r L o c k >
This value denes the angle of 100 % steering (1.0), so we need to divide our steering angle by car > steerLock.
car t r l >c car t r l >c car t r l >c car t r l >c . s t e e r = a n g l e / car s t e e r L o c k ; > . gear = 1 ; / / f i r s t g e a r . accelCmd = 0 . 3 ; / / 30% a c c e l e r a t o r p e d a l . brakeCmd = 0 . 0 ; / / no b r a k e s
You can see that we return all our values in the car >ctrl structure to TORCS. Insert this code in the bt.cpp le in your robots source directory into the drive function. It should look like this after you changed it:
/ D r iv e d u r i n g r a c e . / s t a t i c void d r i v e ( i n t index , t C a r E l t car , t S i t u a t i o n s ) { memset(& car t r l , 0 , s i z e o f ( t C a r C t r l ) ) ; >c f l o a t angle ; co n st f l o a t SC = 1 . 0 ; a n g l e = RtTrackSideTgAngleL (&( car t r k P o s ) ) car yaw ; > > NORM PI PI ( a n g l e ) ; / / p u t t h e a n g l e b a c k i n t h e r a n g e fr om PI t o PI a n g l e = SC car t r k P o s . toMiddle/car t r k P o s . seg > > >width ; / / s e t up t h e v a l u e s t o r e t u r n car t r l . s t e e r = a n g l e / car s t e e r L o c k ; >c > car t r l . gear = 1 ; / / f i r s t g e a r >c car t r l . accelCmd = 0 . 3 ; / / 30% a c c e l e r a t o r p e d a l >c
33
34
car t r l . brakeCmd = 0 . 0 ; / / no b r a k e s >c }
Compile and install your robot, and play a bit with it. In case you dont understand the algorithm, take a sheet of paper and a pencil and draw the situation with the track and the car, this will help. You will discover some problems which you have to solve later. Change some of the values, run it on different tracks, add better steering correction, whatever you like. You dont need to restart TORCS every time you did make install, because if you start a new practice session, your robot module is reloaded. If you practice very often you should have an xosview or something similar running, because TORCS has memory leaks, so you have to restart it from time to time...
2.3.4 Downloads
In case you got lost, you can download my robot for TORCS 1.2.0 or later.
2.3.5 Summary
You know the drive function and the le bt.cpp. You know how to access car data. You know where to look up the structure of tCarElt and tTrack. You know about the robottools. You know how to return your data to TORCS. You know about about dynamic loading of your robot. You know about the memory leaks.
35
to introduce now. As you will see in the next chapter, your robot module is able to handle up to 10 robots. Thats like e. g. the Damned robots are implemented. I already mentioned that your robot is able to pit. You can then tell the simulation how much fuel you need and how much damage you want to repair. We will deal with pit stops in a later chapter, because its not that easy to stop in the pit. Keep in mind, this is just a tutorial, so the solutions presented here are not perfect. But it should be a good starting point for you. The coming chapters deal with the following topics: Chapter 2 will discuss how to get unstuck and how to deal with more than one car. In chapter 3 we will start braking and gear changing. In chapter 4 we try to steer better. We also need a way to setup the cars, e. g. spoilers, dampers and more. Thats chapter 5. Paint your car and a pit logo in chapter 6. We overtake and try to avoid collisions in chapter 7. And nally in chapter 8 we will do pit stops.
2.4.1 Feedback
Let me know if you read this chapter and your thoughts about it. Please send me also spelling, grammar, math and code corrections. Thank you for the feedback.
35
38
The algorithm to drive is then: If we are stuck get unstuck else drive normal.
/ / counter s t a t i c i n t s t u ck = 0 ; / check i f the car i s s tu ck / bool i s S t u c k ( t C a r E l t c a r ) { f l o a t a n g l e = RtTrackSideTgAngleL (&( car t r k P o s ) ) car yaw ; > > NORM PI PI ( a n g l e ) ; / / a n g l e s m a l l e r t h a n 30 d e g r e e s ? i f ( f a b s ( a n g l e ) < 3 0 . 0 / 1 8 0 . 0 PI ) { s t u ck = 0 ; return f al se ; } i f ( s t u ck < 1 0 0 ) { s t u ck ++; return f al se ; } else { return true ; } }
/ D r iv e d u r i n g r a c e . / s t a t i c void d r i v e ( i n t index , t C a r E l t car , t S i t u a t i o n s ) { f l o a t angle ; co n st f l o a t SC = 1 . 0 ; memset(& car t r l , 0 , s i z e o f ( t C a r C t r l ) ) ; >c i f ( isStuck ( car ) ) { a n g l e = RtTrackSideTgAngleL (&( car t r k P o s ) ) + car yaw ; > > NORM PI PI ( a n g l e ) ; / / p u t t h e a n g l e b a c k i n t h e r a n g e fr om PI t o PI car t r l . s t e e r = a n g l e / car s t e e r L o c k ; >c > car t r l . gear = 1; / / r e v e r s e g e a r >c car t r l . accelCmd = 0 . 3 ; / / 30% a c c e l e r a t o r p e d a l >c car t r l . brakeCmd = 0 . 0 ; / / no b r a k e s >c } else { a n g l e = RtTrackSideTgAngleL (&( car t r k P o s ) ) car yaw ; > > NORM PI PI ( a n g l e ) ; / / p u t t h e a n g l e b a c k i n t h e r a n g e fr om PI t o PI a n g l e = SC car t r k P o s . toMiddle/ car t r k P o s . seg > > >width ; car t r l . s t e e r = a n g l e / car s t e e r L o c k ; >c > car t r l . gear = 1 ; / / f i r s t g e a r >c car t r l . accelCmd = 0 . 3 ; / / 30% a c c e l e r a t o r p e d a l >c car t r l . brakeCmd = 0 . 0 ; / / no b r a k e s >c } }
Try to understand why this algorithm works sometimes and implement it in bt.cpp. There is also a case where the algorithm fails, we will x that in chapter 2.6, but try to nd out whats wrong with it. The simulation timestep is 0.02 seconds, so the angle needs to be for 2 (0.02 times 100) 38
3.2. Comments
39
seconds greater than 30 degrees, till we start getting unstuck. We will stop getting unstuck, when the angle becomes smaller than 30 degrees.
3.1.2 Downloads
In case you got lost, you can download my robot for TORCS 1.2.0 or later. Summary You have implemented the above stuff and played with it. You have found the bug in isStuck(tCarElt * car), eventually.
3.2 Comments
This section covers some selected topics related to coding for TORCS.
3.2.1 Performance
Because there are several robots in the simulation, we have to take care that our robot doesnt eat up too much performance. Look at the previous listing, I call there 3 times RtTrackSideTgAngleL. Thats very bad, because there are very expensive utility functions, so you should keep the number of calls minimal. So whereever you can recycle an expensive computation, do it (thats a bit oversimplied, I know). There are perhaps also some pieces in the code which doesnt need to be evaluated on every simulation step, so do it just every 10th or whatever is appropriate.
40
3.2.3 Portability
You have seen that TORCS runs on Windows, Linux/x86 and Linux/ppc. So you have to take care for portability. There are also some issues with Visual C++ 6.0.
In fact the variable i should disappear after the for loop, but it doesnt in VC++. So to make VC++ happy, write
int i ; f o r ( i = 0 ; i < 1 0 0 ; i ++) { . . . } f o r ( i = 7 ; i > 0 ; i ) { . . . }
3.2. Comments
41
3.2.10 Summary
You keep an eye on performance. You try to write code which is easy to understand. You try to write portable code. Avoid meaningless numbers, use constants instead. Be aware of oating point numbers. 41
42
denes the index of the driver. Because we can have more than one driver per module, each one is uniquely identied by its index. You will receive it e. g. in your
s t a t i c void d r i v e ( i n t index , t C a r E l t car , t S i t u a t i o n s )
function from TORCS, so you know for which car you need to drive. It has to match with the index you assign to modInfo[i].index in bt.cpp, but you will see that later. The next two lines contain the name and description of you driver, they have to match with the modInfo[i] entries (dont ask me why it has to be dened here and in the bt.cpp).
< a t t s t r name=name v a l = b t 1 ></a t t s t r > < a t t s t r name= desc v a l = ></a t t s t r >
The next three entries are your teams name, your name and the car to use. 42
43
< a t t s t r name= team v a l = berniw ></a t t s t r > < a t t s t r name= author v a l = Bernhard Wymann></a t t s t r > < a t t s t r name= c a r name v a l = cgnascar rwd></a t t s t r >
Now you can choose a racing number and dene the color of the drivers entry in the leaders board (if you dont know how to enable the leaders board during the race, hit F1 for help).
<attnum name= r a c e number v a l = 61 ></attnum> <attnum name= red v a l = 1 . 0 ></attnum> <attnum name= green v a l = 0 . 0 ></attnum> <attnum name= blue v a l = 1 . 0 ></attnum> </s e c t i o n > </s e c t i o n > </s e c t i o n > </params>
43
44
<params name= b t type= r o b o t d e f > <s e c t i o n name= Robots > <s e c t i o n name= index > <s e c t i o n name= 0 > < a t t s t r name=name v a l = b t 1 ></a t t s t r > < a t t s t r name= desc v a l = ></a t t s t r > < a t t s t r name=team v a l = berniw ></a t t s t r > < a t t s t r name= author v a l = Bernhard Wymann></a t t s t r > < a t t s t r name= c a r name v a l = cgnascar rwd></a t t s t r > <attnum name= r a c e number v a l = 61 ></attnum> <attnum name= red v a l = 1 . 0 ></attnum> <attnum name= green v a l = 0 . 0 ></attnum> <attnum name= blue v a l = 1 . 0 ></attnum> </s e c t i o n > <s e c t i o n name= 1 > < a t t s t r name=name v a l = b t 2 ></a t t s t r > < a t t s t r name= desc v a l = ></a t t s t r > < a t t s t r name=team v a l = berniw ></a t t s t r > < a t t s t r name= author v a l = Bernhard Wymann></a t t s t r > < a t t s t r name= c a r name v a l = x j 220></a t t s t r > <attnum name= r a c e number v a l = 62 ></attnum> <attnum name= red v a l = 1 . 0 ></attnum> <attnum name= green v a l = 0 . 0 ></attnum> <attnum name= blue v a l = 1 . 0 ></attnum> </s e c t i o n > <s e c t i o n name= 2 > < a t t s t r name=name v a l = b t 3 ></a t t s t r > < a t t s t r name= desc v a l = ></a t t s t r > < a t t s t r name=team v a l = berniw ></a t t s t r > < a t t s t r name= author v a l = Bernhard Wymann></a t t s t r > < a t t s t r name= c a r name v a l =155DTM></a t t s t r > <attnum name= r a c e number v a l = 63 ></attnum> <attnum name= red v a l = 1 . 0 ></attnum> <attnum name= green v a l = 0 . 0 ></attnum> <attnum name= blue v a l = 1 . 0 ></attnum> </s e c t i o n > </s e c t i o n > </s e c t i o n > </params>
Summary
You know how to dene cars in bt.xml. You know you can dene up to ten cars. To follow the tutorial you have chosen the indices 0, 1 and 2. 44
45
3.4.1 Initialization
First we dene how many robots we want to handle (sorry about mixing up the terms cars, drivers and robots) and the size of the buffer to generate the names.
# define BUFSIZE 20 # define NBBOTS 3
We also need an array of char pointers to remember the memory locations of the names to free them on shutdown.
s t a t i c char botname [NBBOTS ] ;
/ / / / / /
Now we loop NBBOTS times to initialize all robots. You can see how the name is assembled in the buffer. We have to store the pointers to the 45
46
names because strdup(buffer) will allocate memory on the heap, which we have to release on shutdown (there is no garbage collector in C or C++). In case you dont want such systematic names, like bt 1, bt 2, bt 3, you can also implement them as static character arrays:
s t a t i c char botname [NBBOTS] = { t i g e r , penguin , r a t } ;
This has also the advantage that you dont need to free memory on shutdown. The advantage of the rst method is, if you want to add a driver, you just need to increment NBBOTS. Remember that the names should match with the names dened in the bt.xml le.
3.4.2 Shutdown
We have also to change the shutdown of the module to free the memory on the heap. Because shutdown is called for every robot, we need just to release the memory of botname with index i.
/ C a l l e d b e f o r e t h e module i s u n l o a d e d / s t a t i c void shutdown ( i n t index ) { f r e e ( botname [ index ] ) ; }
You should now implement the above stuff. In case you try it out, keep in mind, that if you run more than one robot of this module, they will share the stuck variable. So there could be weird effects. Anyway, try a quickrace with your three robots to check if everything is ok so far.
3.4.3 Downloads
In case you got lost, you can download my robot for TORCS 1.2.0 or later. 46
47
3.4.4 Summary
You understand how initialization and shutdown works. You know how to add more cars. You have implemented it.
47
48
t h e Free Software Foundation ; e i t h e r v e r s ion 2 o f t h e License , or ( a t y ou r o p t i o n ) any l a t e r v e r s i o n . / # ifndef # define DRIVER H DRIVER H
With this dene we make sure that the le content is just included once. There is also a matching #endif on the end of the le. If you want to learn more about such # stuff, search for information about your C preprocessor.
# include # include # include # include # include # include # include # include # include # include <s t d i o . h> <s t d l i b . h> <s t r i n g . h> <math . h> <t g f . h> <t r a c k . h> <c a r . h> <raceman . h> <r o b o t t o o l s . h> <r o b o t . h>
c l a s s Dr i v e r { public : Dr i v e r ( i n t index ) ; / c a l l b a c k f u n c t i o n s c a l l e d fr om TORCS / void i n i t T r a c k ( t T r a ck t , void carHandle , void carParmHandle , t S i t u a t i o n s ) ; void newRace ( t C a r E l t car , t S i t u a t i o n s ) ; void d r i v e ( t C a r E l t car , t S i t u a t i o n s ) ; i n t pitCommand ( t C a r E l t car , t S i t u a t i o n s ) ; void endRace ( t C a r E l t car , t S i t u a t i o n s ) ;
First we include all the stuff we need. Then we dene the constructor, which has the robot index as argument. It will store this index, so the following functions initTrack, newRace, drive and pitCommand dont need it anymore. These are public, because they are called then from bt.cpp and should nally do the work. Shutdown is not implemented here, because we need to call the destructor on shutdown to free the memory. In case you allocate memory with new, dont forget to add a destructor with code to release it.
private : / u t i l i t y f u n c t i o n s / bool i s S t u c k ( t C a r E l t c a r ) ; void update ( t C a r E l t car , t S i t u a t i o n s ) ; / per r ob ot g l o b a l data / i n t s t u ck ; f l o a t trackangle ;
48
49
You know already isStuck, we move it also into our class. Update is supposed to prepare data we need in more than one place in our robot like trackangle and angle. You can also see the nice names for the constants here. MAX UNSTUCK ANGLE tells you more than 30.0, doesnt it?
# endif / / DRIVER H
This closes the leading #ifndef and rst implementation of driver.h. The Driver Class Implementation driver.cpp Now we will put concrete implementations of the above dened stuff in driver.cpp.
/ file created copyright : d r i v e r . cpp : Thu Dec 20 0 1 : 2 1 : 4 9 CET 2002 : ( C ) 2002 B e r n h a r d Wymann
/ / T h i s program i s f r e e s o f t w a r e ; you c an r e d i s t r i b u t e i t and / o r m o d i f y i t u n d e r t h e t e r m s o f t h e GNU G e n e r a l P u b l i c L i c e n s e a s p u b l i s h e d by t h e Free Software Foundation ; e i t h e r v e r s ion 2 o f t h e License , or ( a t y ou r o p t i o n ) any l a t e r v e r s i o n . / # include d r i v e r . h
Here are the constants implemented which are already dened in driver.h. We do it this way for compatibility with VC++ 6.0. You should put at least 49
50
In the constructor we save the index for later use. Probably we can remove the INDEX later, because we dont need it.
/ C a l l e d f o r e v e r y t r a c k c h a n g e o r new r a c e . / void Dr i v e r : : i n i t T r a c k ( t T r a ck t , void carHandle , void carParmHandle , t S i t u a t i o n s ) { track = t ; carParmHandle = NULL; } / S t a r t a new r a c e . / void Dr i v e r : : newRace ( t C a r E l t car , t S i t u a t i o n s ) { MAX UNSTUCK COUNT = i n t ( UNSTUCK TIME LIMIT/RCM MAX DT ROBOTS ) ; s t u ck = 0 ; }
We dont dig into initTrack yet, this will follow in the chapter about how to load custom car setups. In newRace we init our stuck counter variable with 0. Did you recognize that now every driver instance has its own stuck variable? We also replace the former 100 with MAX UNSTUCK COUNT, which is computed based on the desired delay in UNSTUCK TIME LIMIT and the simulation timestep.
/ D r iv e d u r i n g r a c e . / void Dr i v e r : : d r i v e ( t C a r E l t car , t S i t u a t i o n s ) { update ( car , s ) ; memset(& car t r l , 0 , s i z e o f ( t C a r C t r l ) ) ; >c i f ( isStuck ( car ) ) { car t r l . s t e e r = a n g l e / car s t e e r L o c k ; >c > car t r l . gear = 1; / / r e v e r s e g e a r >c car t r l . accelCmd = 0 . 3 ; / / 30% a c c e l e r a t o r p e d a l >c car t r l . brakeCmd = 0 . 0 ; / / no b r a k e s >c } else { f l o a t s t e e r a n g l e = a n g l e car t r k P o s . toMiddle/car t r k P o s . seg > > >width ; car t r l >c car t r l >c car t r l >c car t r l >c } } . steer = steerangle . gear = 1 ; / / f i r s t . accelCmd = 0 . 3 ; / / . brakeCmd = 0 . 0 ; / / / car s t e e r L o c k ; > gear 30% a c c e l e r a t o r p e d a l no b r a k e s
50
51
You are already familiar with drive from bt.cpp. New is the update of the data and the use of precomputed variables instead of expensive RtTrackSideTgAngleL() calls.
/ S e t p i t s t o p commands . / i n t Dr i v e r : : pitCommand ( t C a r E l t car , t S i t u a t i o n s ) { r e t u r n ROB PIT IM ; / r e t u r n i m m e d i a t e l y / } / End o f t h e c u r r e n t r a c e / void Dr i v e r : : endRace ( t C a r E l t car , t S i t u a t i o n s ) { } / Update my p r i v a t e d a t a e v e r y t i m e s t e p / void Dr i v e r : : update ( t C a r E l t car , t S i t u a t i o n s ) { t r a c k a n g l e = RtTrackSideTgAngleL (&( car t r k P o s ) ) ; > a n g l e = t r a c k a n g l e car yaw ; > NORM PI PI ( a n g l e ) ; } \ begin { l s t l i s t i n g } We w i l l d i s c u s s pitCommand i n t h e p i t s t o p ch a p t e r . In endRace you can put code which should run a f t e r t h e r a c e . Update does a s s i g n t h e c u r r e n t v a l u e s t o a n g l e and t r a c k a n g l e . Put i n here updates t o d r i v e r v a r i a b l e s which you would l i k e t o perform on every s i m u l a t i o n t i m e s t e p . \ begin { l s t l i s t i n g } / C h e c k i f I m s t u c k / bool Dr i v e r : : i s S t u c k ( t C a r E l t c a r ) { i f ( f a b s ( a n g l e ) < MAX UNSTUCK ANGLE) { s t u ck = 0 ; return f al se ; } i f ( s t u ck < MAX UNSTUCK COUNT) { s t u ck ++; return f al se ; } else { return true ; } }
This is also already well known. Changes are the use of constants and the precomputed angle. That nishes the driver.cpp.
52
/ file created copyright : b t . cpp : Thu Dec 12 0 2 : 3 4 : 3 1 CET 2002 : (C ) 2002 B e r n h a r d Wymann
/ / T h i s program i s f r e e s o f t w a r e ; you c an r e d i s t r i b u t e i t and / o r m o d i f y i t u n d e r t h e t e r m s o f t h e GNU G e n e r a l P u b l i c L i c e n s e a s p u b l i s h e d by t h e Free Software Foundation ; e i t h e r v e r s ion 2 o f t h e License , or ( a t y ou r o p t i o n ) any l a t e r v e r s i o n . / # i f d e f WIN32 # include <windows . h> # endif # include # include # include # include # include # include # include # include # include # include <s t d i o . h> <s t d l i b . h> <s t r i n g . h> <math . h> <t g f . h> <t r a c k . h> <c a r . h> <raceman . h> <r o b o t t o o l s . h> <r o b o t . h>
52
53
f o r ( i = 0 ; i < NBBOTS ; i ++) { s p r i n t f ( b u f f e r , b t %d , i + 1 ) ; botname [ i ] = strdup ( b u f f e r ) ; modInfo [ i ] . name = botname [ i ] ; modInfo [ i ] . desc = ; modInfo [ i ] . f c t I n i t = I n i t F u n c P t ; modInfo [ i ] . g f Id = ROB IDENT ; modInfo [ i ] . index = i; } return 0 ; } / Module i n t e r f a c e i n i t i a l i z a t i o n . / s t a t i c i n t I n i t F u n c P t ( i n t index , void pt ) { t R o b o t I t f i t f = ( t R o b o t I t f ) pt ; / c r e a t e r ob ot i n s t a n c e f o r index / d r i v e r [ index ] = new Dr i v e r ( index ) ;
/ / / / / /
53
54
Like you can see most functions route now the call to the corresponding driver method. Shutdown does now also delete the instance of driver index.
to
SOURCES = ${ROBOT} . cpp d r i v e r . cpp
3.5.5 Downloads
In case you got lost, you can download my robot for TORCS 1.2.0 or later.
3.5.6 Summary
You know how the robot works so far. You know how to add source les. You have implemented it. 54
55
Figure 3.1: sketch of skidding First I will show you what the problem is with the current isStuck() function. Lets have a look at the sketch on the left. Assume the car comes from bottom left and wants to drive a left turn. It is too fast and starts oversteering, so the angle between the track tangent and the car becomes bigger and bigger. At a certain point the angle is big enough to start incrementing stuck. When the car hits the wall with its back stuck is 100 and we try to get unstuck. So we are with the back at the wall and try to back up. So we are stuck again... Look careful at the sketch, it shows us also the solution for the problem. The front of the car looks inside toward the track middle. But like you can see, there is never such a situation where we need to get unstuck. You simply can drive forward to come back on the track.
56
Figure 3.2: sketch of inside criteria toward the middle. This criteria holds always. You can paint the areas for the different parts of our expressions into the circle to understand it better. So if fabs(angle) MAX UNSTUCK ANGLE is true we try to drive forward, else we check if we look outside and the stuck is already big enough. If that all is fulllled, we return true to get unstuck. If not we just increment stuck.
56
57
Change also in driver.cpp this part of the drive function to apply more throttle to back up.
i f ( isStuck ( car ) ) { car t r l . s t e e r = a n g l e / car s t e e r L o c k ; >c > car t r l . gear = 1; / / r e v e r s e g e a r >c car t r l . accelCmd = 0 . 5 ; / / 50% a c c e l e r a t o r p e d a l >c car t r l . brakeCmd = 0 . 0 ; / / no b r a k e s >c } else {
Put the constants at the start of driver.cpp. Reduce also MAX UNSTUCK ANGLE.
co n st f l o a t Dr i v e r : : MAX UNSTUCK SPEED = 5 . 0 ; co n st f l o a t Dr i v e r : : MIN UNSTUCK DIST = 3 . 0 ; / [m/ s ] / / [m] /
3.6.5 Downloads
In case you got lost, you can download my robot for TORCS 1.2.0 or later.
3.6.6 Feedback
Let me know if you read this chapter and your thoughts about it. Please send me also spelling, grammar, math and code corrections. Thank you for the feedback. 57
58
3.6.7 Summary
You understand how getting unstuck works. You know about every expression why it is necessary. You have implemented it.
58
= m.g.. 59
60
Figure 4.1: speed formula Of course m: current car mass v: current speed r: radius : friction coefcient = v = g..r All speed junkies get now sick because of the section title. But keep in mind, if you want to race, use TORCS or go to the race track, please dont use public roads. But thats not the subject, I want to discuss the physical speed limits... Like you know from experience, you cant pass a turn with any speed, there are limits which can be described with physical laws. Of course we need to know how fast we can pass a turn. The force that pushes the car outside the turn is called centrifugal force. Thats the left part of the top equation on the sketch. To hold the car on the track, we need to compensate it. Our tires will do that job with friction on the track. The maximum possible speed in a turn results if you solve the equation. You should notice that we assume that all wheels have the same mu and the load is equally distributed over all wheels. We further simplify with the friction coefcient mu, in reality its a complex function which depends on the rel60
61
ative speed vector between the surfaces, the current load, the shape of the patch, the temperature, ... This leads to the following function:
/ Compute t h e a l l o w e d s p e e d on a s e g m e n t / f l o a t Dr i v e r : : getAllowedSpeed ( t T r a ck S e g segment ) { i f ( segment >type == TR STR ) { r e t u r n FLT MAX ; } else { f l o a t mu = segment u r f a c e F r i c t i o n ; >s >k r e t u r n s q r t (muGsegment a d i u s ) ; >r } }
The function gets a pointer to the segment in question. If its a straight segment there is no limit, else we compute the limit according to the above formula. You can improve the function e. g. with taking into account the center of gravity of the car.
4.1.2 Distance
For braking we need also to know distances. Here is a function that computes the distance of the car to the end of the current segment (remember the track segments or look up chapter 1.3). I put this into a function, because car- trkPos.toStart contains depending on the segment type the arc or the length, so we need to convert it sometimes to the length (arc times radius = length).
/ Compute t h e l e n g t h t o t h e end o f t h e s e g m e n t / f l o a t Dr i v e r : : getDistToSegEnd ( t C a r E l t c a r ) { i f ( car t r k P o s . seg > >type == TR STR ) { r e t u r n car t r k P o s . seg e n g t h car t r k P o s . t o S t a r t ; > >l > } else { r e t u r n ( car t r k P o s . seg r c car t r k P o s . t o S t a r t ) car t r k P o s . seg a d i u s ; > >a > > >r } }
4.1.3 Accelerator
We need also to know for a given speed how much accelerator pedal we have to apply. If we tell TORCS to accelerate 100% (1.0), it tries to run the engine at the maximum allowed rpm (car- enginerpmRedLine). We can compute the angular speed of the wheel with the speed and the wheel 61
62
radius. This has to be equal like the desired rpm divided by the gear ratio. So we nally end up with an equation for the desired rpm, the pedal value is then rpm/enginerpmRedLine. If the given speed is much higher than the current speed, we will accelerate full.
/ Compute f i t t i n g a c c e l e r a t i o n / f l o a t Dr i v e r : : g e t A cce l ( t C a r E l t c a r ) { f l o a t allowedspeed = getAllowedSpeed ( car t r k P o s . seg ) ; > f l o a t gr = car g e a r R a t i o [ car g e a r + car g e a r O f f s e t ] ; > > > f l o a t rm = car enginerpmRedLine ; > i f ( allowedspeed > car s p e e d x + FULL ACCEL MARGIN) { > return 1 . 0 ; } else { r e t u r n allowedspeed / car wheelRadius (REAR RGT) gr /rm ; > } }
and in driver.h.
s t a t i c co n st f l o a t G; s t a t i c co n st f l o a t FULL ACCEL MARGIN ;
4.1.5 Summary
You respect speed limits on public roads. You know the physics of driving through turns. You have implemented it. 62
63
2 m.v2 2
= m.g..
with v2 = 0 = s =
2 v1 2.g.
Like you have seen in the previous section, there is a physical speed 63
64
limit in the turns. Imagine the following situation: we drive on a straight with speed v1, and in distance d is a turn. We can compute the possible speed (we call it v2) in the turn with the getAllowedSpeed() method, which we have developed in the previous chapter. If we make some assumptions, we can compute the required minimal braking distance s. Now if s is greater or equal than d we have to start braking (you see we brake as late as possible, and even a bit later). But how do we compute this distance? Look a the top formula on the sketch. This time we work with energy equations. Our car has a certain amount of kinetic energy, and we want to reach a lower energy state. We have to burn the energy difference in our brakes. You can solve the equation for s, which is our braking distance. We assume in this equation, that mu is constant over s, what is actually wrong e. g. at mixed tracks. But dont worry, it will work quite well anyway, but here is room for improvement. There is a special case of the formula, where v2 equals zero. We need that to compute the maximum braking distance. The idea is that in the worst case we have to stop, so checking this distance is sufcient.
4.2.2 Implementation
Here is the code that computes if we need to brake.
f l o a t Dr i v e r : : g e t B r a k e ( t C a r E l t c a r ) { t T r a ck S e g s e g p t r = car t r k P o s . seg ; > f l o a t cu r r e n t s p e e d s q r = car s p e e d x car s p e e d x ; > > f l o a t mu = s e g p t r u r f a c e F r i c t i o n ; >s >k f l o a t maxlookaheaddist = cu r r e n t s p e e d s q r / ( 2 . 0 muG ) ;
maxlookaheddist is the distance we have to check (formula with special case v2 = 0).
f l o a t l o o k a h e a d d i s t = getDistToSegEnd ( c a r ) ;
lookaheaddist holds the distance we have already checked. First we check if we need to brake for a speed limit on the end of the current segment.
f l o a t allowedspeed = getAllowedSpeed ( s e g p t r ) ; i f ( allowedspeed < car s p e e d x ) r e t u r n 1 . 0 ; >
64
65
Compute the allowed speed on the current segment. We check our speed, and if we are too fast we brake, else we continue with the algorithm. Here you can improve the return value, its a bit tough to brake full (e. g. make it dependent on the speed difference).
s e g p t r = s e g p t r e x t ; >n while ( l o o k a h e a d d i s t < maxlookaheaddist ) {
The rst line moves segptr to the next segment. The guard of the loop checks if we have already checked far enough.
allowedspeed = getAllowedSpeed ( s e g p t r ) ; i f ( allowedspeed < car s p e e d x ) { >
Compute the allowed speed on the *segptr segment. If the allowed speed is smaller than the current speed, we need to investigate further.
f l o a t allowedspeedsqr = allowedspeed allowedspeed ; f l o a t b r a k e d i s t = ( cu r r e n t s p e e d s q r allowedspeedsqr ) / ( 2 . 0 muG ) ;
Here the magic check is done. If the required distance to brake is greater than the current distance we need to brake. This works because the simulation timestep is small, so we fail the point in the worst case with 2.0 meters. So to x that you can add always 2 meters to the brakedist, or better a speed dependent value.
return 1 . 0 ; } } l o o k a h e a d d i s t += s e g p t r e n g t h ; >l s e g p t r = s e g p t r e x t ; >n } return 0 . 0 ; }
The remaining code is straightforward. If we decided to brake we return 1. If we loop further we update the lookaheaddist and switch to the next segment. A comment to the return value: 1 means apply full brakes, so we have later to adjust the pressure in the brake system, that the wheels dont lock up immediately or we have enough pressure at all. You will see this on the xj-220, the wheels will smoke very nice... A hint: it should also be possible to compute the right pressure and do an automatic set up of that value. 65
66
Now we need also to change the drive method to call getAccel and getBrake.
car t r l . gear = 4 ; >c car t r l . brakeCmd = g e t B r a k e ( c a r ) ; >c i f ( car t r l . brakeCmd == 0 . 0 ) { >c car t r l . accelCmd = g e t A cce l ( c a r ) ; >c } else { car t r l . accelCmd = 0 . 0 ; >c }
We have to switch into a higher gear to check the stuff. We just accelerate if we dont brake. And nally you need to update driver.h with the interface of getBrake.
f l o a t getBrake ( t C arE lt car ) ;
4.2.3 Testdrive
Do some test runs with the different cars. Enable the debug vector (press g) to watch the return values of the robot. With cg-nascar-rwd you should recognize that the braking is very ashy on high speeds, try to explain why. The lap times are now: mixed-2: 1:36:66, 4 damage. e-track-2: 1:53:85, 0 damage. e-track-4: 2:19:08, 4 damage.
4.2.4 Downloads
In case you got lost, you can download my robot for TORCS 1.2.0 or later.
4.2.5 Summary
You have understood the physics of braking. You have implemented it. 66
5.1.1 Implementation
I will simply show you the vector class v2d here. Look up the implementation for details. We implement operators for vector addition, subtraction, negation, multiplication with a scalar and the dot product. Further we want also be able to normalize the vector, to rotate it around a center and to get its length. Put the following code in a new le called linalg.h.
/ file created copyright : linalg .h : Wed F e b 18 0 1 : 2 0 : 1 9 CET 2003 : ( C ) 2003 B e r n h a r d Wymann
67
68
/ T h i s program i s f r e e s o f t w a r e ; you c an r e d i s t r i b u t e i t and / o r m o d i f y i t u n d e r t h e t e r m s o f t h e GNU G e n e r a l P u b l i c L i c e n s e a s p u b l i s h e d by t h e Free Software Foundation ; e i t h e r v e r s ion 2 o f t h e License , or ( a t y ou r o p t i o n ) any l a t e r v e r s i o n . / # ifndef # define LINALG H LINALG H
c l a s s v2d { public : / c o n s t r u c t o r s / v2d ( ) {} v2d ( co n st v2d &s r c ) { t h i s >x = s r c . x ; t h i s >y = s r c . y ; } v2d ( f l o a t x , f l o a t y ) { t h i s >x = x ; t h i s >y = y ; } / o p e r a t o r s / v2d& o p e r a t o r =( co n st v2d &s r c ) ; / assignment / v2d o p e r a t o r +( co n st v2d &s r c ) co n st ; / a d d i t i o n / v2d operator (void ) co n st ; / n e gation / v2d operator ( co n st v2d &s r c ) co n st ; / s u b t r a c t i o n / v2d o p e r a t o r ( co n st f l o a t s ) co n st ; / m u lt iply with s c a l a r / f l o a t o p e r a t o r ( co n st v2d &s r c ) co n st ; / d o t p r o d u c t / f r i e n d v2d o p e r a t o r ( co n st f l o a t s , co n st v2d & s r c ) ; / m e t h od s / f l o a t l e n ( void ) co n st ; void normalize ( void ) ; f l o a t d i s t ( co n st v2d &p ) co n st ; f l o a t co s a l p h a ( co n st v2d &p2 , co n st v2d &c e n t e r ) co n st ; v2d r o t a t e ( co n st v2d &c , f l o a t a r c ) co n st ; / data / float x ; float y ; }; / assignment / i n l i n e v2d& v2d : : o p e r a t o r =( co n st v2d &s r c ) { x = s rc . x ; y = s rc . y ; return t h i s ; } / add t h i s + s r c ( v e c t o r a d d i t i o n ) / i n l i n e v2d v2d : : o p e r a t o r +( co n st v2d &s r c ) co n st { r e t u r n v2d ( x + s r c . x , y + s r c . y ) ; } / n e gation o f t h i s / i n l i n e v2d v2d : : operator (void ) co n st { r e t u r n v2d(x , y ) ; } / c om p u t e t h i s s r c ( v e c t o r s u b t r a c t i o n ) / i n l i n e v2d v2d : : operator ( co n st v2d &s r c ) co n st {
68
69
69
70
5.1.2 Summary
You know how to deal with vectors. You implemented the above stuff in linalg.h.
5.2.1 Implementation
Put the following code also into linalg.h. Like you can see the straights are described with two 2D-vectors. One holds a point on the straight and the other the direction. Because we normalize the direction in the constructor we can simplify the computation of the distance to a point.
class Straight { public : / c o n s t r u c t o r s / S t r a i g h t ( ) {} S t r a i g h t ( f l o a t x , f l o a t y , f l o a t dx , f l o a t dy ) { p . x = x ; p . y = y ; d . x = dx ; d . y = dy ; d . normalize ( ) ; } S t r a i g h t ( co n st v2d &anchor , co n st v2d &d i r ) { p = anchor ; d = d i r ; d . normalize ( ) ; } / m e t h od s / v2d i n t e r s e c t ( co n st S t r a i g h t &s ) co n st ; f l o a t d i s t ( co n st v2d &p ) co n st ; / data / v2d p ; v2d d ; }; / i n t e r s e c t i o n p o i n t o f t h i s and s / i n l i n e v2d S t r a i g h t : : i n t e r s e c t ( co n st S t r a i g h t &s ) co n st { f l o a t t = (d . x ( s . p . yp . y )+d . y ( p . xs . p . x ) ) / ( d . x s . d . yd . y s . d . x ) ; r e t u r n s . p + s . d t ; } / d i s t a n c e o f p o i n t s fr om s t r a i g h t t h i s / i n l i n e f l o a t S t r a i g h t : : d i s t ( co n st v2d &s ) co n st { v2d d1 = s p ;
/ p o i n t on t h e s t r a i g h t / / d i r e c t i o n o f the s t r a i g h t /
70
5.3. Steering
v2d d3 = d1 dd1d ; r e t u r n d3 . l e n ( ) ; } # endif / / LINALG H
71
5.2.2 Summary
You know how to deal with straights. You implemented the above stuff in linalg.h.
5.3 Steering
Figure 5.1: steering with lookahead to middle In this section we will improve the steering with a heuristic approach. In a nutshell, the presented method still drives on the middle of the track, but now we will steer toward a point ahead of the car. The disadvantage of this simple method is that we cant drive with the biggest possible radius through turns like you can see easily on the sketch. This drawing doesnt match exactly with the method presented below, because the lookahead distance will be measured on the middle of the track.
72
lookahead meters, there is our target point. Put the following code into driver.cpp.
v2d Dr i v e r : : g e t T a r g e t P o i n t ( ) { t T r a ck S e g seg = car t r k P o s . seg ; > f l o a t lookahead = LOOKAHEAD CONST + car s p e e d x LOOKAHEAD FACTOR; >
We compute the lookahead distance with a heuristic. Like you can see lookahead grows with the speed.
f l o a t l e n g t h = getDistToSegEnd ( ) ; while ( l e n g t h < lookahead ) { seg = seg e x t ; >n l e n g t h += seg e n g t h ; >l }
This loop nds the segment which contains the target point.
l e n g t h = lookahead l e n g t h + seg e n g t h ; >l v2d s ; s . x = ( seg e r t e x [ TR SL ] . x + seg e r t e x [ TR SR ] . x ) / 2 . 0 ; >v >v s . y = ( seg e r t e x [ TR SL ] . y + seg e r t e x [ TR SR ] . y ) / 2 . 0 ; >v >v
Now we compute the distance of the target point to the start of the segment found and the starting point itself. From that we are able to compute the target point in the global coordinate system. We have to distinguish between straights and turns.
i f ( seg >type == TR STR ) { v2d d ; d . x = ( seg e r t e x [ TR EL ] . x seg e r t e x [ TR SL ] . x )/ seg e n g t h ; >v >v >l d . y = ( seg e r t e x [ TR EL ] . y seg e r t e x [ TR SL ] . y )/ seg e n g t h ; >v >v >l r e t u r n s + d l e n g t h ;
For the straight we compute the starting point on the middle of the track and the direction vector. From that we can get the nal point and return it.
} else { v2d c ; c . x = seg e n t e r . x ; >c c . y = seg e n t e r . y ; >c f l o a t a r c = l e n g t h /seg a d i u s ; >r f l o a t a r c s i g n = ( seg >type == TR RGT ) ? 1 : 1 ; arc = arc arcsign ; return s . r o t a t e ( c , arc ) ; } }
For the turns its a bit different. First we get the rotation center of the segment. Next we compute from the length and the radius the angle of 72
5.3. Steering
73
the target point relative to the segments starting point. Finally we rotate the starting point around the center with the computed angle. At the start of driver.cpp we need to dene the constants.
co n st f l o a t Dr i v e r : : LOOKAHEAD CONST = 1 7 . 0 ; co n st f l o a t Dr i v e r : : LOOKAHEAD FACTOR = 0 . 3 3 ; / [m] / / [ 1 / s ] /
t a r g e t A n g l e = a t a n 2 ( t a r g e t . y car pos Y , t a r g e t . x car pos X ) ; > > t a r g e t A n g l e = car yaw ; > NORM PI PI ( t a r g e t A n g l e ) ; r e t u r n t a r g e t A n g l e / car s t e e r L o c k ; > }
with
car t r l . s t e e r = g e t S t e e r ( ) ; >c
73
74
5.3.4 Testdrive
Do some test runs with the different cars. The car drives now very nice on most of the tracks. Try also e-track-3 and have a look on the rst left turn after the nish line. In the next chapter we will implement a heuristic to pass turns of that type faster.
5.3.5 Downloads
In case you got lost, you can download my robot for TORCS 1.2.0 or later.
5.3.6 Summary
You have implemented the above stuff. You know how to steer with a heuristic approach.
5.4.1 Implementation
Replace getAllowedSpeed in driver.cpp with the following version:
/ Compute t h e a l l o w e d s p e e d on a s e g m e n t / f l o a t Dr i v e r : : getAllowedSpeed ( t T r a ck S e g segment ) {
74
75
No changes so far. In the next part we compute the remaining angle (arc) of the turn. We need a loop because large turns can be composed from a lot of small turns.
f loat arc = 0 . 0 ; t T r a ck S e g s = segment ; while ( s >type == segment >type && a r c < PI / 2 . 0 ) { a r c += s r c ; >a s = s e x t ; >n }
Because we divide the radius by this angle it makes no sense to have values greater than 1.0, so we normalize it.
a r c /= PI / 2 . 0 ; f l o a t mu = segment u r f a c e F r i c t i o n ; >s >k
5.4.2 Testdrive
Now we come close to some opponents with the lap times. There are just a few seconds on some tracks left. In the next section we will x the newly introduced stability problem.
5.4.3 Downloads
In case you got lost, you can download my robot for TORCS 1.2.0 or later.
5.4.4 Summary
You are really hot to beat some opponents. 75
76
5.5.1 Implementation
Here follows the implementation, put it in driver.cpp.
/ Hold c a r on t h e t r a c k / f l o a t Dr i v e r : : f i l t e r T r k ( f l o a t a c c e l ) { t T r a ck S e g seg = car t r k P o s . seg ; > i f ( car s p e e d x < MAX UNSTUCK SPEED) r e t u r n a c c e l ; >
If we are very slow do nothing. Then check if we are on a straight. If thats the case, check both sides.
i f ( seg >type == TR STR ) { f l o a t tm = f a b s ( car t r k P o s . toMiddle ) ; > f l o a t w = seg >width/WIDTHDIV ; i f ( tm > w) r e t u r n 0 . 0 ; e l s e r e t u r n a c c e l ;
If we are outside and more than w from the middle away set accelerator to zero.
} else { f l o a t tm = f a b s ( car t r k P o s . toMiddle ) ; > f l o a t w = seg >width/WIDTHDIV ; i f ( tm > w) r e t u r n 0 . 0 ; e l s e r e t u r n a c c e l ; } } }
76
77
to
car t r l . accelCmd = f i l t e r T C L ( f i l t e r T r k ( g e t A cce l ( ) ) ) ; >c
5.5.2 Testdrive
What you got till now is not too bad. Youve written a robot that is able to master most of the tracks. We also reduced the lap times very much: * mixed-2: 1:14:70, 0 damage. * e-track-2: 1:24:45, 0 damage. * e-track-4: 1:49:70, 0 damage.
5.5.3 Downloads
In case you got lost, you can download my robot for TORCS 1.2.0 or later.
5.5.4 Summary
You have implemented and understood an almost competitive robot. Congratulations:-) 77
78
5.6 Trajectories
5.6.1 The Optimal Trajectory?
In case you know how to compute the optimal trajectory very fast (optimal means minimal lap time) please let me know... I skimmed quite a lot of papers about that subject (the robotics community deals with similiar problems) but from there I didnt get much useful information. If I remember right there has been proved that nding the optimal trajectory in three dimensions is in NP. But because our tracks are quite at a two dimensional approximation would be sufcient. There have been written some papers about that subject and if I remember right there are also solutions available, but just for a mass point robot (no aerodynamics involved) and quite expensive to compute (or should I say simply slow). If you are interested the papers are available in the ACM online library.
5.6. Trajectories
79
where the spline leaves the track rst and insert an additional point on the track. You repeat that till the whole path is on the track. That works quite well (never hit the public). The next try was to wrap clothoids trough the turns. There is still an implementation of that available in berniw, but not very well rened. Another experiment (for that tutorial) was to work with the biggest arcs you can t into the turns. It is not yet rened enough for public release and too complicated for the tutorial.
5.6.5 Feedback
Let me know if you read this chapter and your thoughts about it. Please send me also spelling, grammar, math and code corrections. Thank you for the feedback.
5.6.6 Summary
You know about other approaches. You have an almost competitive car.
79
82
6.1.4 Summary
You know what a car setup is. You know that for different goals different setups are required. You know how to change the skill level of your cars. 82
83
First we compute a pointer to the tracks lename from the whole path. The lename starts after the last /, so we need to add 1 to the location found with strrchr.
char b u f f e r [ 2 5 6 ] ; / ge t a p o i n t e r to the f i r s t char o f the t r a c k file n am e / char trackname = s t r r c h r ( t r a ck >filename , / ) + 1 ;
Depending on the race type, we want to load the tting setup. For that we assemble the required path to our setup le relative to the /usr/local/share/games/torcs directory. 83
84
switch ( s raceType ) { > ca se RM TYPE PRACTICE : s p r i n t f ( b u f f e r , d r i v e r s/ b t/%d/ p r a c t i c e/%s , INDEX , trackname ) ; break ; ca se RM TYPE QUALIF : s p r i n t f ( b u f f e r , d r i v e r s/ b t/%d/ q u a l i f y i n g/%s , INDEX , trackname ) ; break ; ca se RM TYPE RACE : s p r i n t f ( b u f f e r , d r i v e r s/ b t/%d/ r a c e/%s , INDEX , trackname ) ; break ; default : break ; }
Now we try to load the setup le. After that we check if the le has been loaded. If not we assemble the path to the default setup le and try to read it. If you are not sure about the setup le name you need for a certain track you can add a printf or cout to print the lename to the terminal.
carParmHandle = GfParmReadFile ( b u f f e r , GFPARM RMODE STD ) ; i f ( carParmHandle == NULL) { s p r i n t f ( b u f f e r , d r i v e r s/ b t/%d/ d e f a u l t . xml , INDEX ) ; carParmHandle = GfParmReadFile ( b u f f e r , GFPARM RMODE STD ) ; } }
Because we havent created any setups yet the loader will fail for now. In the next section we will create the directory structure for the setups.
6.2.3 Summary
You know where the default setup les are located. You know how to create custom setup les. You have implemented the above setup loader.
85
For another car replace 0 with its index and do it the same way.
to
SHIPSUBDIRS = 0
When you want to add more subdirectories append them to this line, e. g. 0 1. Now create the Makele in directory 0 with the following content:
ROBOT SHIPDIR SHIP SHIPSUBDIRS = = = = bt d r i v e r s/${ROBOT}/0 d e f a u l t . xml p r a ct ice qualifying race
PKGSUBDIRS = ${SHIPSUBDIRS } s r c r o b o t sbt PKGFILES = $ ( s h e l l f i n d maxdepth 0 type f p r i n t ) s r c r o b o t sbt PKGDIR = ${PACKAGE}${VERSION}/$ ( s u b s t ${TORCS BASE} , , $ ( s h e l l pwd ) ) i n cl u d e ${MAKE DEFAULT}
Like you can see it deploys the le default.xml and the subdirectories practice, qualifying and race. In this subdirectories we need also Makeles to deploy the setup les. Here is the Makele for the 0/practice directory.
ROBOT SHIPDIR SHIP = bt = d r i v e r s/${ROBOT}/0/ p r a c t i c e = $ ( s h e l l f i n d . xml maxdepth 0 type f p r i n t )
85
86
s r c r o b o t sbt PKGFILES = $ ( s h e l l f i n d maxdepth 0 type f p r i n t ) s r c r o b o t sbt PKGDIR = ${PACKAGE}${VERSION}/$ ( s u b s t ${TORCS BASE} , , $ ( s h e l l pwd ) ) i n cl u d e ${MAKE DEFAULT}
s r c r o b o t sbt PKGFILES = $ ( s h e l l f i n d maxdepth 0 type f p r i n t ) s r c r o b o t sbt PKGDIR = ${PACKAGE}${VERSION}/$ ( s u b s t ${TORCS BASE} , , $ ( s h e l l pwd ) ) i n cl u d e ${MAKE DEFAULT}
s r c r o b o t sbt PKGFILES = $ ( s h e l l f i n d maxdepth 0 type f p r i n t ) s r c r o b o t sbt PKGDIR = ${PACKAGE}${VERSION}/$ ( s u b s t ${TORCS BASE} , , $ ( s h e l l pwd ) ) i n cl u d e ${MAKE DEFAULT}
Before the Makeles work there has to be at least one XML le in every directory. Put this stripped down default setup of cg-nascar-rwd into the le 0/default.xml. Copy it also to the following locations:
$ cp 0/ d e f a u l t . xml 0/ p r a c t i c e /gt r a ck 3.xml $ cp 0/ d e f a u l t . xml 0/ q u a l i f y i n g /gt r a ck 3.xml $ cp 0/ d e f a u l t . xml 0/ r a c e /gt r a ck 3.xml
This should be enough for a litte test run. Do a make install, check if it works without errors and look up in the target directory if everything is in place. In the next sections we will work with these les.
6.3.3 Downloads
In case you got lost, you can download my robot for TORCS 1.2.0 or later.
6.3.4 Summary
You know the directory structure. You have created all les. You have checked if the installation works. 86
87
6.4.3 Properties
You can nd all mentioned properties in the le 0/default.xml (relative to the bt directory).
87
88
</s e c t i o n >
The amount of initial fuel for the setup. Perhaps you want to compute that setting at startup instead of putting it into a setup le. Feel free to improve it. For qualifying and short races you want just the minimal amount of fuel you need, for longer races it will depend on your strategy (but we need to implement pit stops rst).
6.4.5 Spoilers
<s e c t i o n name= Rear Wing> <attnum name= a n g l e u n i t =deg min= 0 max= 30 v a l = 10 /> </s e c t i o n >
Figure 6.1: spoiler angle of attack This section denes the angle of attack of the rear spoiler relative to the oor. When you increase the angle of attack you get more downforce and drag. If the race track has very long straights and just a few narrow turns you want a small angle of attack to reduce the drag. If there are many turns you want a big angle of attack to increase the downforce, so you can drive faster through the turns. For some cars there is also a front wing section available. The front spoiler helps to avoid understeering if you set up the rear spoiler with a high angle of attack.
6.4.6 Gearbox
<s e c t i o n name= Gearbox> <s e c t i o n name= g e a r s > <s e c t i o n name= r > <attnum name= r a t i o min=3 max=0 v a l = 2.0/> </s e c t i o n > ...
88
89
The gearbox section denes the properties of the gears. The r subsection species the ratio of the reverse gear. The reverse gear ratio needs to be negative. The wheel velocity is proportional to the engine rpm divided by the ratios, so for the highest gear you need the least value. For quick starts you need a very high value for the rst gear.
6.4.7 Differential
<s e c t i o n name= Rear D i f f e r e n t i a l > < a t t s t r name= type i n =SPOOL, FREE , LIMITED SLIP v a l =LIMITED SLIP /> </s e c t i o n >
Here the differential type is dened. The Free type distributes the power without any control, so it is the classic differential you can nd in most of the normal cars. The Free differential is a good choice for tracks with high friction and not many bumps for the qualifying. When you leave the track with the Free differential you perhaps get stuck in the grass, sand or dirt. The Limited Slip differential allows just a certain amount of speed difference between the left and right wheel. This allows you to come back to the track from grass and dirt. This is most often the best setting for races. The Spool setting links together the right and the left wheel, so its usually a bad setting for all turns. It could make sense on a very bumpy road or for offroad races.
6.4.8 Brakes
<s e c t i o n name= Brake System> <attnum name= f r o n tr e a r brake r e p a r t i t i o n min= 0 . 3 max= 0 . 7 v a l = 0 . 5 2 /> <attnum name=max p r e s s u r e u n i t =kPa min= 100 max= 150000 v a l = 11000 /> </s e c t i o n >
The front-rear brake repartition denes how to distribute the pressure in the brake system. If the value is 0.0 all pressure goes to the rear brakes, if 89
90
it is 1.0 all pressure hits the front brakes. The max pressure setting denes the pressure for applying full brakes.
6.4.9 Wheels
<s e c t i o n name= F r o n t Right Wheel> <attnum name= r i d e h e i g h t u n i t =mm min= 100 max= 200 v a l = 100 /> <attnum name= t o e u n i t =deg min=5 max= 5 v a l = 0 /> <attnum name= camber min=5 max=0 u n i t =deg v a l = 0/> </s e c t i o n >
Figure 6.2: toe and camber The ride height setting denes the initial distance of the cars oor to the track. When you decrease the ride height the downforce increases because of the ground effect, but you cars oor may hit the track. With toe (on the left on the sketch) you can dene the initial angle of the wheel to the car. You can improve the stability or the response to steer commands with these settings. Camber (on the right on the sketch) denes the angle of the wheel to the track. At the moment it increases simply the adherence on the track in the simulation when you decrease the angle (toward negative values).
The anti-roll bars prevents the car body from rolling (e. g. in fast turns). When the car body rolls (on the bottom of the sketch) the tire contact patch 90
91
Figure 6.3: rotation of car body becomes smaller and the grip drops down. The spring setting denes how strong the anti-roll bar is linked. With a high bellcrank value you can increase the strength of the link further.
6.4.11 Suspension
<s e c t i o n name= F r o n t Right Suspension> <attnum name= s p r i n g u n i t = l b s / i n min= 0 max= 10000 v a l = 2500 /> <attnum name= suspension co u r s e u n i t =m min= 0 max= 0 . 2 v a l = 0 . 2 /> <attnum name= b e l l c r a n k min= 1 max=5 v a l = 2 /> <attnum name= p a ck e r s u n i t =mm min=0 max= 10 v a l = 0 /> <attnum name= slow bump u n i t = l b s / i n /s min= 0 max= 1000 v a l = 80 /> <attnum name= slow rebound u n i t = l b s / i n /s min= 0 max= 1000 v a l = 80 /> <attnum name= f a s t bump u n i t = l b s / i n /s min= 0 max= 1000 v a l = 100 /> <attnum name= f a s t rebound u n i t = l b s / i n /s min= 0 max= 1000 v a l = 100 /> </s e c t i o n >
The spring setting denes how stiff the spring is. The suspension course denes the distance which the suspension can move. With the bellcrank you can make the whole suspension system more stiff (higher values) or soft (lower values). The packers limit the suspension movement. You need this if you want a soft setup and avoiding the car oor hitting the track on fast track sections. Slow bump and rebound allow you to set up the damping of low frequency oscillations, e. g. caused by braking and steering. Fast bump and rebound are for damping high frequency oscillations, e. g. caused by bumps on the track or if you hit the curbs.
6.4.12 Summary
You know the most important car parameters and what they are good for. 91
92
6.5.2 Implementation
First I show you the changes needed in the setup le. For the example we take the driver bt 1 on g-track-3 in practice mode. From that follows that we have to modify the le 0/practice/g-track-3.xml. Add the following to the le above from /params.
<s e c t i o n name= b t p r i v a t e > <attnum name= f u e l p e r l a p v a l = 2 . 0 /> </s e c t i o n >
We dene a new section type for all our private settings. Inside the section we dene our new property fuelperlap as numeric property and assign the value 2.0. You could also put your properties into another le. Now we add the code to load the property to driver.cpp. First we dene new constants to access the section and the attribute.
# define BT SECT PRIV b t p r i v a t e # define BT ATT FUELPERLAP f u e l p e r l a p
The code to load and initialize settings has to be in Driver::initTrack(). Append the following code at the end of the method.
f l o a t f u e l = GfParmGetNum ( carParmHandle , BT SECT PRIV , BT ATT FUELPERLAP , ( char )NULL, 5 . 0 ) ;
Try to load our property. If it fails the value defaults to 5.0. After that compute the required fuel for the race. Thats the number of laps times the fuel required per lap. To play safe we add fuel for an additional lap. After that set the initial fuel up. 92
93
f u e l = ( s t o t L a p s + 1 . 0 ) ; > GfParmSetNum ( carParmHandle , SECT CAR , PRM FUEL, ( char )NULL, MIN( f u e l , 1 0 0 . 0 ) ) ;
If you run a practice session with bt 1 on g-track-3 the initial fuel should be 42.0 if everything works.
To access the parameter cool you have to assemble a path for the subsection, which contains simply the section names top down to the subsection separated by /. So the path would be bt private/test. Here is an example how to implement it.
# define BT SECT PRIV TEST t e s t # define BT ATT COOL c o o l f l o a t cool ; char path [ 2 5 6 ] ; s p r i n t f ( path , %s/%s , BT SECT PRIV , BT SECT PRIV TEST ) ; c o o l = GfParmGetNum ( carParmHandle , path , BT ATT COOL , ( char )NULL, 0 . 0 ) ;
6.5.4 Downloads
In case you got lost, you can download my robot for TORCS 1.2.0 or later.
6.5.5 Summary
\ item You know how t o load custom p r o p e r t i e s . \ item You know how t o a c c e s s and c r e a t e s u b s e c t i o n s . \ item You know how t o s e t up your c a r with computed v a l u e s .
93
94
You can see on picture 1 the heavy understeering of the car in this narrow turn. It happens in the acceleration part of it, so we can conclude that the Limited Slip differential on the driven rear wheels disallows us to pass the turn. So we will set the differential to type Free. On picture 2 94
95
you can see the result of the understeering. The car leaves the track and starts skidding. On picture 3 you can see the skidmarks form the braking before the turn. You can conclude that the braking balance or pressure is not set up correct. Perhaps we have to move the braking balance towards the rear wheels or to reduce the pressure. Finally on picture 4 you can see classic understeering. Its a quite wide turn, so the main cause is probably not enough grip on the front wheels. We can x that with setting the camber to the minimum value and with proper suspension setup. Perhaps we have to reduce the angle of attack of the rear wing (rebember our speed limit computation). Now make the following changes in the setup le (0/practice/g-track-3.xml) and deploy it (make install).
Set the differential to type FREE. Set camber of the left and right front wheel to -5. 95
96
Like you can see on picture 5 we pass this turn now ne without any skidmarks or other trouble. But we face a new problem on the next turn like you can see on the pictures 6, 7 and 8. If you watch the practice session carefully you can observe that the reason for this are locking front wheels. We can conclude that we have to move the braking balance toward the rear wheels and to reduce the pressure. Set the brakes front-rear brake repartition to 0.47. Set the max pressure to 9000. 96
97
98
6.6.4 Downloads
In case you got lost, you can download my robot for TORCS 1.2.0 or later.
6.6.5 Feedback
Let me know if you read this chapter and your thoughts about it. Please send me also spelling, grammar, math and code corrections. Thank you for the feedback.
6.6.6 Summary
You know how to setup the car. You have got a feeling for the different settings.
98
99
100
7.1.1 Textures
The pictures that dene the look of your car are called textures. These textures are put onto your car in a process called texture mapping. Car and pit logo textures have to be stored in the SGI rgb format. The size must be a power of two, eg. for the pit logo 128x128 and for the car 256x256. Textures need to be quadratic (width equals height). You can see the default texture of the cg-nascar-rwd car on the left. To create your custom car texture you need to download a default texture and to modify it. To make your work easier there are for some cars textures available as multilayer xcf images in several resolutions. To work with the textures I recommend you to take the highest resolution available, you can reduce it later with your tool.
7.1.2 Tools
To work with the textures I recommend the GIMP (GNU Image Manipulation Program). It is for sure included in your Linux distribution. There 100
101
7.1.3 Summary
You know what a texture is.
You have a tool to work with xcf and rgb images. 101
102
103
to
SHIP = ${ROBOT} . xml logo . rgb
If you want to put the logo in the car directory you have to put the logo there and to edit the Makele there. After you have installed the logo with make install it should appear in the simulation.
7.2.4 Summary
You know how to create a pit logo. You know how to integrate the logo. 103
104
105
to
SHIP = d e f a u l t . xml cgnascar rwd . rgb
After you have deployed the texture with make install it should appear in the simulation.
7.3.6 Downloads
In case you got lost, you can download my robot for TORCS 1.2.0 or later.
7.3.7 Feedback
Let me know if you read this chapter and your thoughts about it. Please send me also spelling, grammar, math and code corrections. Thank you for the feedback.
7.3.8 Summary
You know where and how to get car textures. You know about xcf and rgb les. You know how to deploy your cars texture.
105
108
sary). You can achieve such an update with checking the simulation time and just update the data when the simulation time has changed. The code would then look somelike that:
s t a t i c double c u r r e n t t i m e ; / c l a s s v a r i a b l e in Driver / ... i f ( c u r r e n t t i m e != s i t u a t i o n >currentTime ) { currenttime = situation >currentTime ; a l l c a r s . update ( ) ; }
/ / T h i s program i s f r e e s o f t w a r e ; you c an r e d i s t r i b u t e i t and / o r m o d i f y i t u n d e r t h e t e r m s o f t h e GNU G e n e r a l P u b l i c L i c e n s e a s p u b l i s h e d by t h e Free Software Foundation ; e i t h e r v e r s ion 2 o f t h e License , or ( a t y ou r o p t i o n ) any l a t e r v e r s i o n . / # ifndef # define # include # include # include # include # include # include # include # include # include # include OPPONENT H OPPONENT H <s t d i o . h> <s t d l i b . h> <s t r i n g . h> <math . h> <t g f . h> <t r a c k . h> <c a r . h> <raceman . h> <r o b o t t o o l s . h> <r o b o t . h>
108
109
# include l i n a l g . h # include d r i v e r . h
The following lines dene bitmasks which we will use to classify the opponents. I will explain them later.
# define # define # define # define # define OPP IGNORE OPP FRONT OPP BACK OPP SIDE OPP COLL 0 (1<<0) (1<<1) (1<<2) (1<<3)
Because of a cross dependency we need to dene a Driver class prototype. After that the declaration of the Opponent class starts.
c l a s s Dr i v e r ; / Opponent m a i n t a i n s t h e d a t a f o r on e o p p o n e n t / c l a s s Opponent { public : Opponent ( ) ; void s e t C a r P t r ( t C a r E l t c a r ) { t h i s a r = c a r ; } >c s t a t i c void s e t T r a c k P t r ( t T r a ck t r a c k ) { Opponent : : t r a c k = t r a c k ; } s t a t i c f l o a t getSpeed ( t C a r E l t c a r ) ; t C arE lt getCarPtr ( ) { return car ; } i n t g e t S t at e ( ) { return s t a t e ; } f l o a t g e t C a t ch Di s t ( ) { r e t u r n c a t c h d i s t ; } f l o a t getDistance ( ) { return distance ; } f l o a t getSideDist ( ) { return s i d e d i s t ; } f l o a t getWidth ( ) { r e t u r n width ; } f l o a t getSpeed ( ) { r e t u r n speed ; } void update ( t S i t u a t i o n s , Dr i v e r d r i v e r ) ; private : f l o at getDistToSegStart ( ) ; tCarElt car ; f l o a t distance ; f l o a t speed ; float catchdist ; f l o a t width ; float sidedist ; int state ; / / / / / / / p o i n t e r to the opponents car / approximation o f the r e a l d i s t a n c e / speed in d i r e c t i o n o f t he t r a c k / d i s t a n c e needed to c at c h t h e opponent / t h e c a r s n e e d e d w id t h on t h e t r a c k / d i s t a n c e o f c e n t e r o f gr avity o f the car s / s t a t e bitmask o f t h e opponent /
/ c l a s s v a r i a b l e s / s t a t i c t T r a ck t r a c k ; / constants static float static float static float static float static float }; / FRONTCOLLDIST ; BACKCOLLDIST ; SIDECOLLDIST ; LENGTH MARGIN; SIDE MARGIN ;
109
110
Finally follows the declaration of the Opponents class. Like you can see it is just responsible for the update of the opponents.
/ The Opponents c l a s s h o l d s an a r r a y o f a l l Opponents / c l a s s Opponents { public : Opponents ( t S i t u a t i o n s , Dr i v e r d r i v e r ) ; Opponents ( ) ; void update ( t S i t u a t i o n s , Dr i v e r d r i v e r ) ; Opponent getOpponentPtr ( ) { r e t u r n opponent ; } i n t getNOpponents ( ) { r e t u r n nopponents ; } private : Opponent opponent ; i n t nopponents ; };
# endif / /
OPPONENT H
8.1.4 Summary
You have created opponent.h. You have an idea of the data we collect.
110
111
/ / / /
To avoid collisions it is not necessary to take into account cars that are farther away than a certain distance. With FRONTCOLLDIST and BACKCOLLDIST you can set the range you want to check for opponents in front of your car and behind your car. The safety margins dene the minimal distance you would like to maintain to opponents. If you would like to be able to change these parameters without recompilation, you could also put them into the setup XML le and load them at startup.
Opponent : : Opponent ( ) { } / c om p u t e s p e e d c om p on e n t p a r a l l e l t o t h e t r a c k / f l o a t Opponent : : getSpeed ( t C a r E l t c a r ) { v2d speed , d i r ; f l o a t t r a c k a n g l e = RtTrackSideTgAngleL (&( car t r k P o s ) ) ; > speed . x = car speed X ; > speed . y = car speed Y ; > d i r . x = cos ( t r a c k a n g l e ) ; dir . y = sin ( trackangle ) ; r e t u r n speed d i r ; }
The Opponent::getSpeed(tCarElt *car) method computes the speed of the car in the direction of the track. First we get the direction of the track at the cars location. After that we compose the speed vector of the car. We use speed X and speed Y here, the uppercase X and Y means that the speed vector is in global coordinates, the lowercase x and y are the speeds in the instantaneously coincident frame of reference, a coordinate system that is oriented like the cars body but doesnt move with the car (if it would move with the car the speed x and speed y would always be zero). From the trackangle we compute a direction vector of the track (its length is one). Finally we compute the dot product of the speed and the direction, which will give us the speed in the direction of the track.
/ Compute t h e l e n g t h t o t h e s t a r t o f t h e s e g m e n t /
111
112
f l o a t Opponent : : g e t D i s t T o S e g S t a r t ( ) { i f ( car t r k P o s . seg > >type == TR STR ) { r e t u r n car t r k P o s . t o S t a r t ; > } else { r e t u r n car t r k P o s . t o S t a r t car t r k P o s . seg a d i u s ; > > >r } }
This method computes the distance of the opponent to the segments start. The reason is that car >t rkP os.toStart contains the length of the segment for straight segments and for turns the arc, so we need a convertion to the length. Now we will have a look at the update function. It is responsible for the update of the values in the Opponent instances.
/ Update t h e v a l u e s i n Opponent t h i s / void Opponent : : update ( t S i t u a t i o n s , Dr i v e r d r i v e r ) { t C a r E l t mycar = d r i v e r e t C a r P t r ( ) ; >g / i n i t s t a t e o f opponent to ig n or e / s t a t e = OPP IGNORE ; / i f t h e car i s out o f t h e s im u lat ion ig n or e i t / i f ( car s t a t e & RM CAR STATE NO SIMU ) { > return ; }
First we get a pointer to the drivers car, set the initial state to ignore the opponent and if the opponent is not longer part of the simulation we return.
/ updating d i s t a n c e along t h e middle / f l o a t oppToStart = car t r k P o s . seg g f r o m s t a r t + g e t D i s t T o S e g S t a r t ( ) ; > >l d i s t a n c e = oppToStart mycar d i s t F r o m S t a r t L i n e ; > i f ( d i s t a n c e > t r a ck e n g t h / 2 . 0 ) { >l d i s t a n c e = t r a ck e n g t h ; >l } e l s e i f ( d i s t a n c e < t r a ck e n g t h / 2 . 0 ) { >l d i s t a n c e += t r a ck e n g t h ; >l }
Now we compute the distance of the center of the drivers car to the center of the opponents car. We achieve that by computing the distances to the start line and taking the difference. If you think about that you will recognize that this is just an approximation of the real distance. If you want a more accurate value you have to compute it with the cars corners (look up car.h). The if part is to normalize the distance. From our position up to a half track length the opponent is in front of us (distance is 112
113
positive), else it is behind (distance is negative). A detail is that we cant use distFromStartLine of the opponent, because it is a private eld.
/ update speed in t r a c k d i r e c t i o n / speed = Opponent : : getSpeed ( c a r ) ; f l o a t cosa = speed/ s q r t ( car speed X car speed X + car speed Y car speed Y ) ; > > > > f l o a t alpha = acos ( cosa ) ; width = car d i m e n s i o n x s i n ( alpha ) + car dimension y cosa ; > > f l o a t SIDECOLLDIST = MIN( car dimension x , mycar d i m e n s i o n x ) ; > >
We update the speed with the previously introduced getSpeed() method. Then we compute the width of the opponents car on the track (think the car is turned 90 degrees on the track, then the needed width is its length).
/ i s o p p o n e n t i n r e l e v a n t r a n g e 50..200 m / i f ( d i s t a n c e > BACKCOLLDIST && d i s t a n c e < FRONTCOLLDIST ) { / i s o p p o n e n t i n f r o n t and s l o w e r / i f ( d i s t a n c e > SIDECOLLDIST && speed < d r i v e r >getSpeed ( ) ) { c a t c h d i s t = driver >getSpeed ( ) d i s t a n c e /( d r i v e r >getSpeed ( ) speed ) ; s t a t e | = OPP FRONT ; d i s t a n c e = MAX( car dimension x , mycar d i m e n s i o n x ) ; > > d i s t a n c e = LENGTH MARGIN; f l o a t c a r d i s t = car t r k P o s . toMiddle mycar t r k P o s . toMiddle ; > > sidedist = cardist ; c a r d i s t = f a b s ( c a r d i s t ) f a b s ( width / 2 . 0 ) mycar dimension y / 2 . 0 ; > i f ( c a r d i s t < SIDE MARGIN ) s t a t e | = OPP COLL ; } else
Here we check if the opponent is in the range we dened as relevant. After that the classication of the opponent starts. In this part we check if it is in front of us and slower. If that is the case we compute the distance we need to drive to catch the opponent (catchdist, we assume the speeds are constant) and set the opponents ag OPP FRONT. Because the distance contains the value of the cars centers we need to subtract a car length and the safety margin. At the end we check if we could collide with to opponent. If yes, we set the ag OPP COLL.
/ i s o p p o n e n t b e h i n d and f a s t e r / i f ( d i s t a n c e < SIDECOLLDIST && speed > d r i v e r >getSpeed ( ) ) { c a t c h d i s t = driver >getSpeed ( ) d i s t a n c e /( speed d r i v e r >getSpeed ( ) ) ; s t a t e | = OPP BACK ; d i s t a n c e = MAX( car dimension x , mycar d i m e n s i o n x ) ; > > d i s t a n c e = LENGTH MARGIN; } else
Here we check if the opponent is behind us and faster. We wont use that in the tutorial robot, but you will need it if you want to let overlap faster opponents.
/ i s opponent a s i d e / i f ( d i s t a n c e > SIDECOLLDIST && d i s t a n c e < SIDECOLLDIST ) {
113
114
This part is responsible to check if the opponent is aside of us. If yes we compute the distance (sideways) and set the ag OPP SIDE. Now the implementation of the Opponents class follows.
/ I n i t i a l i z e the l i s t o f opponents / Opponents : : Opponents ( t S i t u a t i o n s , Dr i v e r d r i v e r ) { opponent = new Opponent [ s n c a r s 1 ] ; > int i , j = 0; f o r ( i = 0 ; i < s n c a r s ; i ++) { > i f ( s a r s [ i ] != d r i v e r e t C a r P t r ( ) ) { >c >g opponent [ j ] . s e t C a r P t r ( s a r s [ i ] ) ; >c j ++; } } Opponent : : s e t T r a c k P t r ( d r i v e r e t T r a c k P t r ( ) ) ; >g nopponents = s n c a r s 1 ; > } Opponents : : Opponents ( ) { d e l e t e [ ] opponent ; }
The constructor allocates memory and generates the Opponent instances. The destructor deletes the instances and frees the memory.
/ U p d at e s a l l t h e Opponent i n s t a n c e s / void Opponents : : update ( t S i t u a t i o n s , Dr i v e r d r i v e r ) { int i ; f o r ( i = 0 ; i < s n c a r s 1 ; i ++) { > opponent [ i ] . update ( s , d r i v e r ) ; } }
The update method simply iterates through the Opponent instances and calls update on them.
115
We need also the speed of our car. For that we add a variable to the private section.
f l o a t speed ; / speed in t r a c k d i r e c t i o n /
SOURCES
= ${ROBOT}.cpp driver.cpp
to
SOURCES
8.2.4 Downloads
In case you got lost, you can download my robot for TORCS 1.2.0 or later.
8.2.5 Summary
You have created opponent.cpp. You know how it works. 115
116
We add a destructor to the public section, the method prototype for the lter and the variables in the private section.
Dr i v e r ( ) ; f l o a t f i l t e r B C o l l ( f l o a t brake ) ; Opponents opponents ; Opponent opponent ;
We create the instance of Opponents in Driver::newRace() and get a pointer to the array of Opponent objects. 116
117
Now call the lter as usual in the Driver::drive() method. Change the line
car t r l . brakeCmd = f i l t e r A B S ( g e t B r a k e ( ) ) ; >c
to
car t r l . brakeCmd = f i l t e r A B S ( f i l t e r B C o l l ( g e t B r a k e ( ) ) ) ; >c
Here we update the speed of the drivers car in track direction and the opponents data. Put this code at the end of Driver::update().
speed = Opponent : : getSpeed ( c a r ) ; opponents >update ( s , t h i s ) ;
Finally we implement the brake lter. In a nutshell it iterates through the opponents and checks for every opponent which has been tagged with OPP COLL if we need to brake to avoid a collision. If yes we brake full and leave the method.
/ Brake f i l t e r f o r c o l l i s i o n av oid an c e / f l o a t Dr i v e r : : f i l t e r B C o l l ( f l o a t brake ) { f l o a t cu r r e n t s p e e d s q r = car s p e e d x car s p e e d x ; > > f l o a t mu = car t r k P o s . seg u r f a c e F r i c t i o n ; > >s >k f l o a t cm = muGmass ; f l o a t ca = CAmu + CW; int i ; f o r ( i = 0 ; i < opponents >getNOpponents ( ) ; i ++) {
We set up some variables and then we enter the loop to iterate through all opponents. The presented solution is not optimal. You could replace the call to getDistance() with getChatchDist(), but additional code is necessary to make it work. The computation of the brakedistance should be familiar from previous chapters.
i f ( opponent [ i ] . g e t S t a t e ( ) & OPP COLL) { f l o a t allowedspeedsqr = opponent [ i ] . getSpeed ( ) ; allowedspeedsqr = allowedspeedsqr ; f l o a t b r a k e d i s t = mass ( cu r r e n t s p e e d s q r allowedspeedsqr ) / ( 2 . 0 ( cm + allowedspeedsqr ca ) ) ; i f ( b r a k e d i s t > opponent [ i ] . g e t D i s t a n c e ( ) ) { return 1 . 0 ; } }
117
118
} r e t u r n brake ; }
8.3.4 Testdrive
To check if the code works run a race with bt 3, bt 1 and bt 2 on wheel 1. The cars should drive behind each other without collisions.
8.3.5 Downloads
In case you got lost, you can download my robot for TORCS 1.2.0 or later.
8.3.6 Summary
You can run your robot family without collisions. You know how it works.
118
119
In the Driver::drive() method we call the steer lter. Change the line
car t r l . s t e e r = g e t S t e e r ( ) ; >c
to
car t r l . s t e e r = f i l t e r S C o l l ( g e t S t e e r ( ) ) ; >c
The lterSColl method searches rst for the nearest car aside of ours (approximately). If there is another car it checks if it is inside the SIDECOLL MARGIN. If yes, it computes a new steer value and returns it.
/ S te e r f i l t e r f o r c o l l i s i o n avoidan ce / f l o a t Dr i v e r : : f i l t e r S C o l l ( f l o a t s t e e r ) { int i ; f l o a t s i d e d i s t = 0 . 0 , f s i d e d i s t = 0 . 0 , m i n s i d e d i s t = FLT MAX ; Opponent o = NULL; / ge t the index o f the n e a r e s t car ( o ) / f o r ( i = 0 ; i < opponents >getNOpponents ( ) ; i ++) { i f ( opponent [ i ] . g e t S t a t e ( ) & OPP SIDE ) {
Again we iterate through all cars and check those which have been marked with the OPP SIDE ag. We init minsidedist with a large value because we search for the minimum.
s i d e d i s t = opponent [ i ] . g e t S i d e D i s t ( ) ; f s i d e d i s t = fabs ( sid e d ist ) ; i f ( f s i d e d i s t < minsidedist ) { minsidedist = f s i d e d i s t ; o = &opponent [ i ] ; } } }
Here we check if the current opponent is the nearest one found so far. If that is the case we store a pointer to it and update the minimal distance found (minsidedist).
/ i f t h e r e i s an othe r car handle the s i t u a t i o n / i f ( o != NULL) { f l o a t d = f s i d e d i s t o >getWidth ( ) ; / n e a r enough / i f ( d < SIDECOLL MARGIN ) {
119
120
If we found an opponent we check if it is inside the margin. To keep the method simple I make some assumptions, e. g. our car is parallel to the track (that is obiously not true).
/ c om p u t e a n g l e b e t w e e n c a r s / t C a r E l t o ca r = o e t C a r P t r ( ) ; >g f l o a t d i f f a n g l e = ocar yaw car yaw ; > > NORM PI PI ( d i f f a n g l e ) ; co n st f l o a t c = SIDECOLL MARGIN / 2 . 0 ; d = d c; if (d < 0.0) d = 0 .0 ; f l o a t p s t e e r = d i f f a n g l e /car s t e e r L o c k ; > r e t u r n s t e e r ( d/c ) + 2 . 0 p s t e e r (1.0 d/c ) ; } } return s t e e r ; }
Here we compute the angle between the two cars and the weight d. Psteer holds the steer value needed to drive parallel to the opponent (if you draw the scene it is obvious). Finally we weight the original steer and the psteer value and sum them up. This will push away our car from the opponent when we are near. You could improve this function with more accurate distance values and checking if the normal steer value points already more away from the opponent than the new computed value.
8.4.3 Downloads
In case you got lost, you can download my robot for TORCS 1.2.0 or later.
8.4.4 Summary
You have implemented side collision avoidance. You know how it works. You know how to improve it.
8.5 Overtaking
To be able to overtake we need to modify again the steer value. This time we achieve that with the modication of the target point. Depending on 120
8.5. Overtaking
121
the side we want to pass an opponent we slightly add an offset vector to the target point. This offset is perpendicular to the track tangent. The opponent we choose for overtaking is the one with the smallest catchdistance, because we will reach it rst.
\ s u b s e c t i o n { Implementation i n d r i v e r . cpp} At t h e beginning o f t h e f i l e we d e f i n e t h e new c o n s t a n t s . You should a l s o change t h e WIDTHDIV c o n s t a n t t o 3 . 0 . \ begin { l s t l i s t i n g } co n st f l o a t Dr i v e r : : BORDER OVERTAKE MARGIN = 0 . 5 ; / [m] / co n st f l o a t Dr i v e r : : OVERTAKE OFFSET INC = 0 . 1 ; / [m/ t i m e s t e p ] / co n st f l o a t Dr i v e r : : WIDTHDIV = 3 . 0 ; / [ ] /
The BORDER OVERTAKE MARGIN is the border relative to the lterTrk width. It should avoid that we leave the range on the track where lterTrk allows acceleration. OVERTAKE OFFSET INC is the increment of the offset value. In Driver::newRace() we initialize myoffset to zero.
myoffset = 0 . 0 ;
Now we have a look on how we compute the offset of the target point.
/ Compute an o f f s e t t o t h e t a r g e t p o i n t / f l o a t Dr i v e r : : g e t O v e r t a k e O f f s e t ( ) { int i ; f l o a t c a t c h d i s t , m i n c a t c h d i s t = FLT MAX ; Opponent o = NULL; f o r ( i = 0 ; i < opponents >getNOpponents ( ) ; i ++) { i f ( opponent [ i ] . g e t S t a t e ( ) & OPP FRONT ) { c a t c h d i s t = opponent [ i ] . g e t C a t ch Di s t ( ) ; i f ( ca t ch d is t < mincatchdist ) {
121
122
First we nd the opponent with the smallest catchdist. Remember, that is the car which we will reach rst. Of course the candidates need to have OPP FRONT set.
i f ( o != NULL) { f l o a t w = o e t C a r P t r ()> t r k P o s . seg >g >width/WIDTHDIVBORDER OVERTAKE MARGIN; f l o a t otm = o e t C a r P t r ()> t r k P o s . toMiddle ; >g i f ( otm > 0 . 0 && m y o f f s e t > w) m y o f f s e t = OVERTAKE OFFSET INC ; e l s e i f ( otm < 0 . 0 && m y o f f s e t < w) m y o f f s e t += OVERTAKE OFFSET INC ; } else {
In case there is an opponent to overtake we let slightly grow the offset toward the other track side to pass it. The borders are checked so that the offset stays within w.
i f ( m y o f f s e t > OVERTAKE OFFSET INC ) m y o f f s e t = OVERTAKE OFFSET INC ; e l s e i f ( m y o f f s e t < OVERTAKE OFFSET INC ) m y o f f s e t += OVERTAKE OFFSET INC ; else myoffset = 0 . 0 ; } return myoffset ; }
In case we have not found an opponent the offset goes back slightly toward zero. In the next section we will add the offset to the target point.
8.5.2 Summary
You have implemented the code above. You know how it works.
123
8.6.1 Implementation
Have a look on the changed Driver::getTargetPoint() method.
/ c om p u t e t a r g e t p o i n t f o r s t e e r i n g / v2d Dr i v e r : : g e t T a r g e t P o i n t ( ) { t T r a ck S e g seg = car t r k P o s . seg ; > f l o a t lookahead = LOOKAHEAD CONST + car s p e e d x LOOKAHEAD FACTOR; > f l o a t l e n g t h = getDistToSegEnd ( ) ; f l o a t o f f s e t = getOvertakeOffset ( ) ;
Here the new v2d variable n has been added. It is initialized with the normalized vector perpendicular to the track tanget.
d . x = ( seg e r t e x [ TR EL ] . x seg e r t e x [ TR SL ] . x )/ seg e n g t h ; >v >v >l d . y = ( seg e r t e x [ TR EL ] . y seg e r t e x [ TR SL ] . y )/ seg e n g t h ; >v >v >l r e t u r n s + d l e n g t h + o f f s e t n ; } else {
Finally n is multiplied with offset and added to the target point. A similar implementation follows for the turns.
v2d c , n ; c . x = seg e n t e r . x ; >c c . y = seg e n t e r . y ; >c f l o a t a r c = l e n g t h /seg a d i u s ; >r f l o a t a r c s i g n = ( seg >type == TR RGT ) ? 1.0 : 1 . 0 ; arc = arc arcsign ; s = s . rot at e ( c , arc ) ; n = c s; n . normalize ( ) ; r e t u r n s + a r c s i g n o f f s e t n ; } }
123
124
8.6.2 Testdrive
Before you go to the track you should set the camber of the front wheels to -5 degrees. Change this in the le 0/default.xml. My conclusion after some races is that the collision avoidance and overtaking works really well (at least for its simplicity).
8.6.3 Downloads
In case you got lost, you can download my robot for TORCS 1.2.0 or later.
8.6.4 Feedback
Let me know if you read this chapter and your thoughts about it. Please send me also spelling, grammar, math and code corrections. Thank you for the feedback.
8.6.5 Summary
You have implemented and understood collision avoidance and overtaking. You have a robot which is able to race. You know how to improve it.
124
You might have recognized that we do not ask if our pit is free, that is because TORCS provides currently one pit per car (not per team). From the above list we can derive the items we need to implement: A path to the pit and back to the track. We will use a spline to compute an offset to the middle of the track (do you remember the overtake offset?). A strategy part which decides if we need a pit stop. The callback which tells TORCS how much repair and fuel we need. An additional lter for the brake value to stop in the pit. Some helper methods.
9.2 Summary
You know what pit stops are good for. You know about repairing, damage points and refueling. You have an idea what we need to implement.
9.3 Splines
9.3.1 Introduction
In the last section you have seen that we need to nd somehow a path into our pit. I decided to show you an implementation which uses cubic splines to create the path. Cubic splines are interpolating polynomials between given base points, such that the function itself and the rst derivative is smooth. We need a smooth path because our car can not follow a path with cracks. First I will show you the implementation of the spline algorithm, after that we will prepare the base points. 126
9.3. Splines
127
Figure 9.1: spline picture If you want to know how the following algorithm works, you can look it up here(Appendix A). In a nutshell, you query for a given parameter (x) the value of the spline function (y). The algorithm searches rst the interval in which x falls, then it applies the interpolation to compute y(x). Now lets have a look on the spline.h le (create it in the bt directory).
/ file created copyright email : : : : spline . h Wed Mai 14 1 9 : 5 3 : 0 0 CET 2003 ( C ) 2003 by B e r n h a r d Wymann b e r n iw @ b lu e w in . ch
/ / T h i s program i s f r e e s o f t w a r e ; you c an r e d i s t r i b u t e i t and / o r m o d i f y i t u n d e r t h e t e r m s o f t h e GNU G e n e r a l P u b l i c L i c e n s e a s p u b l i s h e d by t h e Free Software Foundation ; e i t h e r v e r s ion 2 o f t h e License , or ( a t y ou r o p t i o n ) any l a t e r v e r s i o n . / # ifndef # define SPLINE H SPLINE H
The SplinePoint class holds the data for each base point of the spline. For the interpolation algorithm we need to dene also the slope at (x,y).
class Spline { public :
127
128
S p l i n e ( i n t dim , S p l i n e P o i n t s ) ; f lo at evaluate ( f lo at z ) ; private : SplinePoint s ; i n t dim ; }; # endif / / SPLINE H
The Spline class provides just two methods. The constructor to initialize the number of base points (dim) and a pointer to an array of SplinePoints. The second method nally computes the spline values. Here follows the implementation, put it into spline.cpp.
/ file created copyright email : : : : s p l i n e . cpp Wed Mai 14 2 0 : 1 0 : 0 0 CET 2003 (C ) 2003 by B e r n h a r d Wymann b e r n iw @ b lu e w in . ch
/ / T h i s program i s f r e e s o f t w a r e ; you c an r e d i s t r i b u t e i t and / o r m o d i f y i t u n d e r t h e t e r m s o f t h e GNU G e n e r a l P u b l i c L i c e n s e a s p u b l i s h e d by t h e Free Software Foundation ; e i t h e r v e r s ion 2 o f t h e License , or ( a t y ou r o p t i o n ) any l a t e r v e r s i o n . /
# include s p l i n e . h
First the algorithm searches for the interval in which the parameter z falls.
do { i = (a + b) / 2;
128
9.3. Splines
i f ( s [ i ] . x <= z ) a = i ; e l s e b = i ; } while ( ( a + 1 ) != b ) ;
129
Now we know the interval and do the interpolation. For that we compute rst the parameters of the polynomial, then we evaluate it.
i = a ; h = s [ i + 1 ] . x s [ i ] . x ; t = ( zs [ i ] . x ) / h ; a0 = s [ i ] . y ; a1 = s [ i + 1 ] . y a0 ; a2 = a1 h s [ i ] . s ; a3 = h s [ i + 1 ] . s a1 ; a3 = a2 ; r e t u r n a0 + ( a1 + ( a2 + a3 t ) ( t 1)) t ; }
to
SOURCES = ${ROBOT} . cpp d r i v e r . cpp opponent . cpp s p l i n e . cpp
Figure 9.2: path to the pit We compute the base points of the spline in the constructor of a new class, put it into a new le and save it as pit.cpp. That we dont need to change the constructor all the time I will present you the nal result. We will discuss all the new variables later in detail, at the moment we just focus on the base points. The header le will follow later.
/
129
130
file created copyright email : : : : p i t . cpp Thu Mai 15 2 : 4 3 : 0 0 CET 2003 (C ) 2003 by B e r n h a r d Wymann b e r n iw @ b lu e w in . ch
/ / T h i s program i s f r e e s o f t w a r e ; you c an r e d i s t r i b u t e i t and / o r m o d i f y i t u n d e r t h e t e r m s o f t h e GNU G e n e r a l P u b l i c L i c e n s e a s p u b l i s h e d by t h e Free Software Foundation ; e i t h e r v e r s ion 2 o f t h e License , or ( a t y ou r o p t i o n ) any l a t e r v e r s i o n . /
The speed limit margin denes how close our speed limiter should go to the allowed speed. The pit damage denes the threshold when we decide to go into the pit to repair our car.
P i t : : P i t ( t S i t u a t i o n s , Dr i v e r d r i v e r ) { t r a ck = driver etTrackPtr ( ) ; >g car = driver etCarPtr ( ) ; >g mypit = d r i v e r e t C a r P t r ()> p i t ; >g p i t i n f o = &t r a ck i t s ; >p pitstop = inpitlane = false ; f u e l ch e ck e d = f a l s e ; fuelperlap = 0 . 0 ; lastpitfuel = 0.0; l a s t f u e l = car r i v . f u e l ; >p i f ( mypit != NULL) {
Set up several variables for strategy and pit stops. Of interest is the mypit variable, if it is not null we have a pit (there are tracks without pits or more drivers in the race than pits available), and the pitinfo variable. In mypit you can nd specic information about your own pit, pitinfo holds data about the pit lane. If we do not own a pit we cannot plan a path.
speedlimit = pitinfo >speedLimit SPEED LIMIT MARGIN ; speedlimitsqr = speedlimit speedlimit ;
130
9.3. Splines
131
The position of our pit along the track relative to the start is the position of the pit segment start plus the relative position of our pit in the pit segment.
p [ 2 ] . x = p [ 3 ] . x pit in fo en ; >l
A reasonable point to go from the pit lane into our pit is the middle of the previous pit. If you experiment with this value you have to make sure that you do not collide with other cars in their pit.
p [ 4 ] . x = p [ 3 ] . x + pit in fo en ; >l
The same applies for the way back to the pit lane.
p [ 0 ] . x = p it in f o itEntry g f r o m s t a r t ; >p >l
The entry point where we start leaving the track and heading to the pit lane. This value is on much tracks not reasonable, so you want to make it later adjustable in the xml le or compute it with a heuristic.
p [ 1 ] . x = pitinfo itStart gfromstart ; >p >l
The start of the pit lane, from here we have to respect the speed limit.
p [ 5 ] . x = p [ 3 ] . x + ( pitinfo >nMaxPits car >index ) p i t i n f o e n ; >l
The end of the pit lane. It is computed this way, because on some tracks the end is not reported correct.
p [ 6 ] . x = pitinfo itExit gfromstart ; >p >l
And nally the point where we are back on the track. Now we have computed all x values of our spline base points.
pitentry = p [ 0 ] . x ; pitexit = p[6]. x ; / n o r m a l i z i n g s p l i n e s e g m e n t s t o >= 0 . 0 / int i ; f o r ( i = 0 ; i < NPOINTS ; i ++) { p[ i ] . s = 0. 0; p [ i ] . x = toSplineCoord ( p [ i ] . x ) ; }
The above loop initializes all the slopes to zero and converts the x values into a range from zero to the distance of p[6] and p[0]. The conversion is necessary because the start line can be within the pit lane (the x 131
132
value grows till the start line, then it drops to zero and grows again). To get proper interpolation we need a continuous x.
if (p [ 1 ] . x > p [ 2 ] . x ) p [ 1 ] . x = p [ 2 ] . x ; if (p [ 4 ] . x > p [ 5 ] . x ) p [ 5 ] . x = p [ 4 ] . x ;
If we have the rst or the last pit we need to adjust the ordering of the points.
f l o a t s i g n = ( p i t i n f o i d e == TR LFT ) ? 1 . 0 : 1.0; >s p[0].y = 0.0; p[6].y = 0.0; f o r ( i = 1 ; i < NPOINTS 1 ; i ++) { p[ i ] . y = fabs ( pitinfo riversPits >d >pos . toMiddle ) p i t i n f o >width ; p [ i ] . y = s i g n ; }
The above loop initializes the y values of the spline. You can also experiment with other values.
p [ 3 ] . y = fabs ( pitinfo riversPits >d >pos . toMiddle ) s i g n ; s p l i n e = new S p l i n e (NPOINTS, p ) ; } }
Finally we set the y value of our pit and create a spline object.
Pit : : Pit () { i f ( mypit != NULL) d e l e t e s p l i n e ; }
9.3.4 Summary
You know the way into the pit. You know how to use the spline class. 132
133
/ / T h i s program i s f r e e s o f t w a r e ; you c an r e d i s t r i b u t e i t and / o r m o d i f y i t u n d e r t h e t e r m s o f t h e GNU G e n e r a l P u b l i c L i c e n s e a s p u b l i s h e d by t h e Free Software Foundation ; e i t h e r v e r s ion 2 o f t h e License , or ( a t y ou r o p t i o n ) any l a t e r v e r s i o n . / # ifndef # define PIT H PIT H
First we dene the number of points for the pit path spline.
c l a s s Dr i v e r ; class Pit { public : P i t ( t S i t u a t i o n s , Dr i v e r d r i v e r ) ; Pit ( ) ;
The constructor and destructor. We have already implemented them in the last section.
void s e t P i t s t o p ( bool p i t s t o p ) ; bool g e t P i t s t o p ( ) { r e t u r n p i t s t o p ; }
134
Computes for a given distance from the start line the offset of the path.
bool isBetween ( f l o a t f r o m s t a r t ) ;
Checks if the given distance from start is between pitentry and pitexit. Such a method is handy because the value wraps-around at the start line.
f l o a t g e t NP it S t art ( ) { return p [ 1 ] . x ; } f l o a t getNPitLoc ( ) { r e t u r n p [ 3 ] . x ; } f l o a t getNPitEnd ( ) { r e t u r n p [ 5 ] . x ; }
Methods to get the spline x coordinates of the points 1,3 and 5. We need them to control the speed limit in the pit lane and to stop in the pit.
f l o a t toSplineCoord ( f l o a t x ) ;
Converts the track (distance from start) x coordinates of the spline into spline x coordinates, starting from zero with no wrap-around in the x value.
f l o a t getSpeedlimitSqr ( ) { return speedlimitsqr ; } f l o a t getSpeedlimit ( ) { return speedlimit ; }
Methods to get the speed limit in the pits. The returned value already includes the security margin.
void update ( ) ; i n t g e t Re p a i r ( ) ; float getFuel ( ) ;
The update() method does the housekeeping of the data used for the strategy and decides if we need to pit. The getRepair() and getFuel() methods decides how much damage gets repaired and how much fuel we get at the pit stop.
private : t T r a ck t r a c k ; tCarElt car ; tTrackOwnPit mypit ; / p o i n t e r t o my p i t / tTrackPitInfo p it in fo ; / ge n e r al p i t i n f o /
134
135
Pointers to the track, car, the pit and the pitinfo structure. I use them to avoid too much indirection.
S p l i n e P o i n t p [NPOINTS ] ; / s p l i n e p o i n t s / Spline spline ; / s p l i n e /
The pitstop variable indicates if we plan a pit stop. It will become set to false right after the stop, because we do not need to stop anymore.
bool i n p i t l a n e ; / we a r e s t i l l i n t h e p i t l a n e /
The inpitlane variable indicates if we are in the pit lane. It will become set to true if pitstop is true and we are in the pit lane (pitentry). It will become set to false if we passed pitexit. The variable is necessary to nish the pit stop correctly.
float float float float pitentry ; pitexit ; speedlimitsqr ; speedlimit ; / / / / d i s t a n c e to s t a r t l i n e o f the p i t entry / d i s t a n c e to the s t a r t l i n e o f the p i t e x i t / p i t speed l i m i t squared / p i t speed l i m i t /
The data necessary to decide if we need to refuel or not. The fuelperlap variable contains the maximum amount of fuel we needed so far for a lap. The amount of fuel we request on the pit stop will be computed based on that value.
s t a t i c co n st f l o a t SPEED LIMIT MARGIN ; s t a t i c co n st i n t PIT DAMMAGE; }; # endif / / PIT H
Finally the declaration of the constants. You already know them from the last section. 135
136
9.4.2 Summary
You have implemented the header le pit.h.
First we subtract of the given value the distance of the pit entry from the start. Because the start line can lie between the pit entry and the pit exit, it is possible that the result becomes negative. In such a case we have to add a track length to correct that.
137
/ c om p u t e s o f f s e t t o t r a c k m i d d l e f o r t r a j e c t o r y / f lo at Pit : : g e t Pit Offse t ( f lo at offse t , f lo at fromstart ) { i f ( mypit != NULL) { i f ( g e t I n P i t ( ) | | ( g e t P i t s t o p ( ) && isBetween ( f r o m s t a r t ) ) ) { f r o m s t a r t = toSplineCoord ( f r o m s t a r t ) ; return spline valuate ( fromstart ) ; >e } } return o f f s e t ; }
First we check if there is a pit available. If we have a pit we check if we are already performing a pit stop with getInPit() or if we plan a pit stop and the target point falls onto the pit path. If the expression evaluates to true we want to follow the pit path, therefore we compute a new offset and return it. If one of the above expressions fails, we simply return the given offset.
If we do not own a pit return. If we have a pit do the above explained checks and leave the pitstop variable in the correct state. 137
138
First we check if pitentry is smaller than pitexit. If that is the case the start line falls not into the pit range. So we can simply check if the given argument is greater than pitentry and smaller than pitexit. If pitentry is greater than pitexit, then the start line is in the pit range. The given argument falls in this case in the pit range if it is between zero and pitexit or between pitentry and the track end (equals the length).
9.5.6 Summary
You have understood and implemented the above methods.
139
will work quite well for endurance races, but not for short ones. It works exactly the same way like in the berniw robot.
If we are between pitentry and pitexit and pitstop is set, set inpit to true. We need that to remember after the pit stop that we have to follow the pit path to leave the pit lane. That is necessary because the pitstop variable is set to false immediately after the pit stop, when we still need to drive out of the pit lane and back to the track. If we are not between pitentry and pitexit we set inpit to false.
/ c h e c k f o r damage / i f ( car dammage > PIT DAMMAGE) { > set Pit st op ( true ) ; }
The rst very simple strategy rule. If the damage of the car is greater than PIT DAMMAGE, we set pitstop to true. Here is very much room for improvement, you could make that dependent on the remaining laps and the situation in the race.
/ f u e l update / i n t i d = car t r k P o s . seg d ; > >i i f ( i d >= 0 && i d < 5 && ! f u e l ch e ck e d ) {
We update the fuel values once per lap when we cross the track segment zero. We check the range of 5 segments that we catch it for sure.
i f ( car a c e . l a p s > 0 ) { >r f u e l p e r l a p = MAX( f u e l p e r l a p , ( l a s t f u e l + l a s t p i t f u e l car r i v . f u e l ) ) ; >p } l a s t f u e l = car r i v . f u e l ; >p
139
140
lastpitfuel f u e l ch e ck e d } else i f ( id > f u e l ch e ck e d } = 0.0; = true ; 5) { = false ;
We compute the amount of fuel which the car has consumed till the check one lap before. The amount of the consumed fuel on the last lap is usually the difference between the available amount of fuel one lap ago (lastfuel) and the current amount of fuel available (car-priv.fuel). If we have performed a pit stop, this is not true. To make the computation work we have to add to the lastfuel value the amount of new fuel (lastpitfuel). We take the maximum because if we get some damage or have to brake and accelarate more than usual the car consumes more fuel. If you take the average you may run out of fuel.
i n t l a p s = car remainingLapscar l a p s B e h i n d L e a de r ; > > i f ( ! g e t P i t s t o p ( ) && l a p s > 0 ) { i f ( car f u e l < 1 . 5 f u e l p e r l a p && > car f u e l < l a p s f u e l p e r l a p ) { > set Pit st op ( true ) ; } }
Here comes the second strategy rule, feel also free to improve that. If there are some laps remaining for our robot check if we have fuel for the next one and a half laps and if we need to refuel at all. If that is the case set pitstop to true.
i f ( g e t P i t s t o p ( ) ) car raceCmd = RM CMD PIT ASKED ; > } }
If pitstop is true set the race command accordingly (here we let TORCS know that we want to pit). Our car is captured by the pit if the car is slow enough, near enough to the center of our pit and if the race command is set to RM CMD PIT ASKED.
141
/ Computes t h e amount o f f u e l / float Pit : : getFuel ( ) { float fuel ; f u e l = MAX(MIN( ( car remainingLaps + 1 . 0 ) f u e l p e r l a p car f u e l , > > car t a n k car f u e l ) , > > 0.0); l a s t p i tf u e l = fuel ; return f u e l ; }
The fuel we need to nish the race is the difference between the remaining laps times the fuel we need per lap and the fuel in the tank. To play safe we request fuel for an additional lap and take the remaining laps of our car (perhaps the leader will have an accident). If the amount of fuel we need is bigger than the tank, we cut the amount with the second expression in the MIN statement. Because we perhaps stopped in the pit to repair damage, the needed amount of fuel can become negative. For that is the surrounding MAX statement.
to 141
142
SOURCES
9.6.6 Downloads
In case you got lost, you can download my robot for TORCS 1.2.0 or later.
9.6.7 Summary
You have understood and implemented the above methods. You know that you can improve the strategy a lot.
143
f l o a t b r a k e d i s t ( f l o a t allowedspeed , f l o a t mu ) ;
Because we will use the calculation of the braking distance from the pit brake lter and other places, we encapsulate the formula nally into a method (at the moment we have it hardwired in the getBrake and lterBColl methods).
f l o a t f i l t e r B P i t ( f l o a t brake ) ;
We need another (much shorter) lookahead value for the pit, because we have to make a very tough turn from the pit lane into the pit.
s t a t i c co n st f l o a t PIT BRAKE AHEAD ; s t a t i c co n st f l o a t PIT MU ;
These constants are used as workaround for two problems. The pit has on much tracks another surface than the track itself, so it would be necessary to analyse the friction to brake early enough. Because we do not take care (of course you will implement that, dont you?) we simply multiply on all tracks the friction with PIT MU for the pit stop. The PIT BRAKE AHEAD is used to workaround the problem of pitentry points which are very close to the rst pit. On such tracks we will simply brake too late to get the turn into the pit right, so we have to brake earlier. If you have added better pitentry points (via XML or computed with a heuristic) you can remove that.
144
f l o a t Dr i v e r : : b r a k e d i s t ( f l o a t allowedspeed , f l o a t mu) { f l o a t allowedspeedsqr = allowedspeed allowedspeed ; f l o a t cm = muGmass ; f l o a t ca = CAmu + CW; r e t u r n mass ( cu r r e n t s p e e d s q r allowedspeedsqr ) / ( 2 . 0 ( cm + allowedspeedsqr ca ) ) ; }
Here we encapsulate the brake distance formula into its own method (remember chapter 3). Now everything is in place and we can start with the implementation of the brake lter.
f l o a t Dr i v e r : : f i l t e r B P i t ( f l o a t brake ) { i f ( p i t e t P i t s t o p ( ) && ! p i t e t I n P i t ( ) ) { >g >g f l o a t dl , dw; R t D i s t T o P i t ( car , t r a ck , &dl , &dw) ; i f ( d l < PIT BRAKE AHEAD) { f l o a t mu = car t r k P o s . seg u r f a c e F r i c t i o n PIT MU ; > >s >k i f ( b r a k e d i s t ( 0 . 0 , mu) > d l ) r e t u r n 1 . 0 ; } }
The above code is responsible for braking early enough to make the turn into the pit lane. Like already mentioned in the discusson of PIT BRAKE AHEAD you can remove that code if you have better pitentry values. Now lets have a look on the nice part.
i f ( pit e t In Pit ( ) ) { >g f l o a t s = pit >toSplineCoord ( car d i s t F r o m S t a r t L i n e ) ; >
If we are on the pit trajectory, convert the position of our car into spline coordinates.
/ p i t entry / i f ( pit etPitstop ( ) ) { >g
If we are not yet in the range of the speed limit we have to check if we need to brake to the speed limit.
/ b r ake to p i t speed l i m i t / f l o a t d i s t = pit etNPitStart ( ) s ; >g i f ( b r a k e d i s t ( p i t e t S p e e d l i m i t ( ) , mu) > d i s t ) r e t u r n 1 . 0 ; >g
First compute the distance to the speed limit zone (p1). Then check if we need already to brake. 144
145
If we take the else we are already in the speed limit zone and have to maintain the speed limit. It is (like always in this tutorial) a very simple implementation.
/ b r ake in to p i t ( speed l i m i t 0.0 to s top ) / f l o a t d i s t = pit >getNPitLoc ( ) s ; i f ( b r a k e d i s t ( 0 . 0 , mu) > d i s t ) r e t u r n 1 . 0 ;
If we did not brake till now we have to check if we need to brake to stop in the pit. We compute the distance to the pit and check the brake distance.
/ hold in t he p i t / i f ( s > pit >getNPitLoc ( ) ) r e t u r n 1 . 0 ;
Finally if we passed the pit center and still want to pit we have to brake full, else we will miss the pit.
} else { / p i t e x i t / i f ( s < pit >getNPitEnd ( ) ) { / p i t speed l i m i t / i f ( cu r r e n t s p e e d s q r > p i t e t S p e e d l i m i t S q r ( ) ) r e t u r n 1 . 0 ; >g } } }
In this code section the pitstop variable is false, so we have just to check if we are in the speed limit zone and to maintain the speed limit in it.
r e t u r n brake ; }
If none of the above expressions returned a brake value, we return the given value.
9.7.4 Conclusion
You have implemented a simple brake lter to stop in the pit. If you analyse the code, you will discover some unpleasant things. Assume for example that an opponent hits us before we stop in the pit and pushes our 145
146
car over the pit center. Our code will then apply full brakes, but if we got pushed too far away from the pit center TORCS will not capture our car. That means we stay there with full applied brakes forever. I did not x that because it happens not often, my berniw robot has the same problem. You can solve that with kind of a timeout mechanism or distance measurement to the pit. I leave it up to you to replace the brake distance code from getBrake and lterBColl through the new brakedist call. In the next section we will put all the stuff together. If you want to celebrate the pit stops with a cold juice it is now time to put it into the fridge.
9.7.5 Summary
You have implemented and understood the brake lter.
To remember our pit instance we need a variable. We also compute and remember the current speed square, because we need it in several places.
Pit pit ; f l o a t cu r r e n t s p e e d s q r ;
146
147
Add the creation of the pit object into Driver::newRace(tCarElt* car, tSituation *s).
/ c r e a t e the p i t o b j e c t / p i t = new P i t ( s , t h i s ) ;
to
memset(& car t r l , 0 , s i z e o f ( t C a r C t r l ) ) ; >c update ( s ) ;
Change the brake value computation such that it takes into account the pit brake lter. Change
car t r l . brakeCmd = f i l t e r A B S ( f i l t e r B C o l l ( g e t B r a k e ( ) ) ) ; >c
to
car t r l . brakeCmd = f i l t e r A B S ( f i l t e r B C o l l ( f i l t e r B P i t ( g e t B r a k e ( ) ) ) ) ; >c
148
(remember chapter 2 where you registered it). We tell TORCS our wishes and set pitstop to false. TORCS holds our car captured during the repair and refueling time, so we cant drive away. After TORCS releases our car the drive function is called as usual and we leave the pit. Replace
/ S e t p i t s t o p commands . / i n t Dr i v e r : : pitCommand ( t S i t u a t i o n s ) { r e t u r n ROB PIT IM ; / r e t u r n i m m e d i a t e l y / }
with
/ S e t p i t s t o p commands / i n t Dr i v e r : : pitCommand ( t S i t u a t i o n s ) { car p i t R e p a i r = p i t e t Re p a i r ( ) ; > >g car p i t F u e l = p i t e t F u e l ( ) ; > >g pit e t P i t s t o p ( false ) ; >s r e t u r n ROB PIT IM ; / r e t u r n i m m e d i a t e l y / }
If we are on the pit trajectory we switch to another computation of the lookahead value. If we are in the speed limit range we even take a shorter lookahead value. You could implement this also with one good formula.
i f ( pit e t In Pit ( ) ) { >g i f ( cu r r e n t s p e e d s q r > p i t e t S p e e d l i m i t S q r ( ) ) { >g lookahead = PIT LOOKAHEAD + car s p e e d x LOOKAHEAD FACTOR; > } else { lookahead = PIT LOOKAHEAD ; } } while ( l e n g t h < lookahead ) { seg = seg e x t ; >n l e n g t h += seg e n g t h ; >l }
148
149
We have to pass the distance to the start line of the target point to getPitOffset, so we compute that rst. After that we call the computation of the pit offset.
f l o a t f r o m s t a r t = seg g f r o m s t a r t ; >l f r o m s t a r t += l e n g t h ; o f f s e t = pit e t P i t O f f s e t ( o f f s e t , fromstart ) ; >g v2d s ; s . x = ( seg e r t e x [ TR SL ] . x + seg e r t e x [ TR SR ] . x ) / 2 . 0 ; >v >v s . y = ( seg e r t e x [ TR SL ] . y + seg e r t e x [ TR SR ] . y ) / 2 . 0 ; >v >v
\ s u b s e c t i o n {Change t h e Track F i l t e r } F i n a l l y we need t o modify Dr i v e r : : f i l t e r T r k ( f l o a t a c c e l ) t h a t i t does allow a c c e l e r a t o r commands i n t h e p i t s . Change \ begin { l s t l i s t i n g } i f ( car s p e e d x < MAX UNSTUCK SPEED) r e t u r n a c c e l ; >
to
i f ( car s p e e d x < MAX UNSTUCK SPEED | | > pit e t I n P i t ( ) ) return acce l ; >g
149
150
9.8.10 Downloads
In case you got lost, you can download my robot for TORCS 1.2.0 or later.
9.8.11 Feedback
Let me know if you read this chapter and your thoughts about it. Please send me also spelling, grammar, math and code corrections. Thank you for the feedback.
9.8.12 Summary
You have understood a full featured robot and you are ready to improve it or build your own from scratch.
150
151
152
We call the above polynomial P, and state four equations for the four unknown parameters a, b, c and d. The rst line states the equations of the two surrounding points, which are known, and the second line shows two equations for the rst derivatives, which are also known. Now you could already solve this system, but it would lead to quite unpleasant expressions.
To avoid that we introduce the new parameter t. Like you can see from the denition and the expressions it is 0 at xi and 1 at xi+1. This will be very pleasant, because evaluating a polynomial for 0 or 1 is much easier than for most of the other numbers. Now we work with t, so I state a new polynomial Q(t) := a t3 + b t +ct+d
2
152
A.4. Summary
153
We need to convert the given points from P(x) to Q(t), and we get the expected Q(0) and Q(1).
The same applies for the derivatives P(x), we convert it to Q(t) and get Q(0) and Q(1).
Now we compute the derivative of the concrete polynomial. We evaluate the equations for t=0 and get the parameters c and d. We evaluate the equations with t=1, and get two equations with two unknown parameters. You can solve this system for a and b. Finally we got all parameters a, b, c and d. We can now evaluate the polynomial. The expression used in the concrete algorithm uses the Horner-Schema to improve efciency.
A.4 Summary
You know how the spline works.
153
154
154