Back to index

supertuxkart  0.5+dfsg1
kart.cpp
Go to the documentation of this file.
00001 //  $Id: kart.cpp 2111 2008-05-31 07:04:30Z cosmosninja $
00002 //
00003 //  SuperTuxKart - a fun racing game with go-kart
00004 //  Copyright (C) 2004-2005 Steve Baker <sjbaker1@airmail.net>
00005 //  Copyright (C) 2006 SuperTuxKart-Team, Joerg Henrichs, Steve Baker
00006 //
00007 //  This program is free software; you can redistribute it and/or
00008 //  modify it under the terms of the GNU General Public License
00009 //  as published by the Free Software Foundation; either version 2
00010 //  of the License, or (at your option) any later version.
00011 //
00012 //  This program is distributed in the hope that it will be useful,
00013 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
00014 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015 //  GNU General Public License for more details.
00016 //
00017 //  You should have received a copy of the GNU General Public License
00018 //  along with this program; if not, write to the Free Software
00019 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
00020 #include <math.h>
00021 #include <iostream>
00022 #include <plib/ssg.h>
00023 #include "bullet/Demos/OpenGL/GL_ShapeDrawer.h"
00024 
00025 #include "loader.hpp"
00026 #include "herring_manager.hpp"
00027 #include "sound_manager.hpp"
00028 #include "file_manager.hpp"
00029 #include "skid_mark.hpp"
00030 #include "user_config.hpp"
00031 #include "constants.hpp"
00032 #include "shadow.hpp"
00033 #include "track.hpp"
00034 #include "world.hpp"
00035 #include "kart.hpp"
00036 #include "ssg_help.hpp"
00037 #include "physics.hpp"
00038 #include "kart_properties_manager.hpp"
00039 #include "gui/menu_manager.hpp"
00040 #include "gui/race_gui.hpp"
00041 #include "translation.hpp"
00042 #include "smoke.hpp"
00043 #include "material_manager.hpp"
00044 
00045 #if defined(WIN32) && !defined(__CYGWIN__)
00046 #  define snprintf  _snprintf
00047 #endif
00048 
00049 
00050 Kart::Kart (const std::string& kart_name, int position_ ,
00051             sgCoord init_pos) 
00052     : TerrainInfo(1),
00053 #if defined(WIN32) && !defined(__CYGWIN__)
00054    // Disable warning for using 'this' in base member initializer list
00055 #  pragma warning(disable:4355)
00056 #endif
00057        Moveable(true), m_attachment(this), m_collectable(this)
00058 #if defined(WIN32) && !defined(__CYGWIN__)
00059 #  pragma warning(1:4355)
00060 #endif
00061 {
00062     m_kart_properties      = kart_properties_manager->getKart(kart_name);
00063     m_grid_position        = position_;
00064     m_initial_position     = position_;
00065     m_num_herrings_gobbled = 0;
00066     m_eliminated           = false;
00067     m_finished_race        = false;
00068     m_finish_time          = 0.0f;
00069     m_wheelie_angle        = 0.0f;
00070     m_smokepuff            = NULL;
00071     m_smoke_system         = NULL;
00072     m_exhaust_pipe         = NULL;
00073     m_skidmark_left        = NULL;
00074     m_skidmark_right       = NULL;
00075     m_track_sector         = Track::UNKNOWN_SECTOR;
00076     sgCopyCoord(&m_reset_pos, &init_pos);
00077     // Neglecting the roll resistance (which is small for high speeds compared
00078     // to the air resistance), maximum speed is reached when the engine
00079     // power equals the air resistance force, resulting in this formula:
00080     m_max_speed               = m_kart_properties->getMaximumSpeed();
00081     m_max_speed_reverse_ratio = m_kart_properties->getMaxSpeedReverseRatio();
00082     m_speed                   = 0.0f;
00083 
00084     // Setting rescue to false is important! If rescue is set when reset() is
00085     // called, it is assumed that this was triggered by a restart, and that
00086     // the vehicle must be added back to the physics world. Since reset() is
00087     // also called at the very start, it must be guaranteed that rescue is
00088     // not set.
00089     m_rescue                  = false;
00090     m_wheel_rotation          = 0;
00091 
00092     m_wheel_front_l           = NULL;
00093     m_wheel_front_r           = NULL;
00094     m_wheel_rear_l            = NULL;
00095     m_wheel_rear_r            = NULL;
00096     m_lap_start_time          = -1.0f;
00097     loadData();
00098 }   // Kart
00099 
00100 // -----------------------------------------------------------------------------v
00101 void Kart::createPhysics(ssgEntity *obj)
00102 {
00103     // First: Create the chassis of the kart
00104     // -------------------------------------
00105 
00106     float kart_width  = m_kart_properties->getKartWidth();
00107     float kart_length = m_kart_properties->getKartLength();
00108     float kart_height = m_kart_properties->getKartHeight();
00109 
00110     btBoxShape *shape = new btBoxShape(btVector3(0.5f*kart_width,
00111                                                  0.5f*kart_length,
00112                                                  0.5f*kart_height));
00113     btTransform shiftCenterOfGravity;
00114     shiftCenterOfGravity.setIdentity();
00115     // Shift center of gravity downwards, so that the kart 
00116     // won't topple over too easy. This must be between 0 and 0.5
00117     // (it's in units of kart_height)
00118     const float CENTER_SHIFT = getGravityCenterShift();
00119     shiftCenterOfGravity.setOrigin(btVector3(0.0f,0.0f,CENTER_SHIFT*kart_height));
00120 
00121     m_kart_chassis.addChildShape(shiftCenterOfGravity, shape);
00122 
00123     // Set mass and inertia
00124     // --------------------
00125     float mass=getMass();
00126 
00127     // Position the chassis
00128     // --------------------
00129     btTransform trans;
00130     trans.setIdentity();
00131     createBody(mass, trans, &m_kart_chassis);
00132     m_user_pointer.set(this);
00133     m_body->setDamping(m_kart_properties->getChassisLinearDamping(), 
00134                        m_kart_properties->getChassisAngularDamping() );
00135 
00136     // Reset velocities
00137     // ----------------
00138     m_body->setLinearVelocity (btVector3(0.0f,0.0f,0.0f));
00139     m_body->setAngularVelocity(btVector3(0.0f,0.0f,0.0f));
00140 
00141     // Create the actual vehicle
00142     // -------------------------
00143     m_vehicle_raycaster = 
00144         new btDefaultVehicleRaycaster(world->getPhysics()->getPhysicsWorld());
00145     m_tuning  = new btRaycastVehicle::btVehicleTuning();
00146     m_vehicle = new btRaycastVehicle(*m_tuning, m_body, m_vehicle_raycaster);
00147 
00148     // never deactivate the vehicle
00149     m_body->setActivationState(DISABLE_DEACTIVATION);
00150     m_vehicle->setCoordinateSystem(/*right: */ 0,  /*up: */ 2,  /*forward: */ 1);
00151     
00152     // Add wheels
00153     // ----------
00154     float wheel_width  = m_kart_properties->getWheelWidth();
00155     float wheel_radius = m_kart_properties->getWheelRadius();
00156     float suspension_rest = m_kart_properties->getSuspensionRest();
00157     float connection_height = -(0.5f-CENTER_SHIFT)*kart_height;
00158     btVector3 wheel_direction(0.0f, 0.0f, -1.0f);
00159     btVector3 wheel_axle(1.0f,0.0f,0.0f);
00160 
00161     // right front wheel
00162     btVector3 wheel_coord(0.5f*kart_width-0.3f*wheel_width,
00163                           0.5f*kart_length-wheel_radius,
00164                           connection_height);
00165     m_vehicle->addWheel(wheel_coord, wheel_direction, wheel_axle,
00166                         suspension_rest, wheel_radius, *m_tuning,
00167                         /* isFrontWheel: */ true);
00168 
00169     // left front wheel
00170     wheel_coord = btVector3(-0.5f*kart_width+0.3f*wheel_width,
00171                             0.5f*kart_length-wheel_radius,
00172                             connection_height);
00173     m_vehicle->addWheel(wheel_coord, wheel_direction, wheel_axle,
00174                         suspension_rest, wheel_radius, *m_tuning,
00175                         /* isFrontWheel: */ true);
00176 
00177     // right rear wheel
00178     wheel_coord = btVector3(0.5f*kart_width-0.3f*wheel_width, 
00179                             -0.5f*kart_length+wheel_radius,
00180                             connection_height);
00181     m_vehicle->addWheel(wheel_coord, wheel_direction, wheel_axle,
00182                         suspension_rest, wheel_radius, *m_tuning,
00183                         /* isFrontWheel: */ false);
00184 
00185     // right rear wheel
00186     wheel_coord = btVector3(-0.5f*kart_width+0.3f*wheel_width,
00187                             -0.5f*kart_length+wheel_radius,
00188                             connection_height);
00189     m_vehicle->addWheel(wheel_coord, wheel_direction, wheel_axle,
00190                         suspension_rest, wheel_radius, *m_tuning,
00191                         /* isFrontWheel: */ false);
00192 
00193     for(int i=0; i<m_vehicle->getNumWheels(); i++)
00194     {
00195         btWheelInfo& wheel               = m_vehicle->getWheelInfo(i);
00196         wheel.m_suspensionStiffness      = m_kart_properties->getSuspensionStiffness();
00197         wheel.m_wheelsDampingRelaxation  = m_kart_properties->getWheelDampingRelaxation();
00198         wheel.m_wheelsDampingCompression = m_kart_properties->getWheelDampingCompression();
00199         wheel.m_frictionSlip             = m_kart_properties->getFrictionSlip();
00200         wheel.m_rollInfluence            = m_kart_properties->getRollInfluence();
00201     }
00202     // Obviously these allocs have to be properly managed/freed
00203     btTransform t;
00204     t.setIdentity();
00205     m_uprightConstraint=new btUprightConstraint(*m_body, t);
00206     m_uprightConstraint->setLimit(m_kart_properties->getUprightTolerance());
00207     m_uprightConstraint->setBounce(0.0f);
00208     m_uprightConstraint->setMaxLimitForce(m_kart_properties->getUprightMaxForce());
00209     m_uprightConstraint->setErp(1.0f);
00210     m_uprightConstraint->setLimitSoftness(1.0f);
00211     m_uprightConstraint->setDamping(0.0f);
00212     world->getPhysics()->addKart(this, m_vehicle);
00213 }   // createPhysics
00214 
00215 // -----------------------------------------------------------------------------
00216 Kart::~Kart() 
00217 {
00218     if(m_smokepuff) delete m_smokepuff;
00219     if(m_smoke_system != NULL) delete m_smoke_system;
00220 
00221     sgMat4 wheel_steer;
00222     sgMakeIdentMat4(wheel_steer);
00223     if (m_wheel_front_l) m_wheel_front_l->setTransform(wheel_steer);
00224     if (m_wheel_front_r) m_wheel_front_r->setTransform(wheel_steer);
00225 
00226 
00227     ssgDeRefDelete(m_shadow);
00228     ssgDeRefDelete(m_wheel_front_l);
00229     ssgDeRefDelete(m_wheel_front_r);
00230     ssgDeRefDelete(m_wheel_rear_l);
00231     ssgDeRefDelete(m_wheel_rear_r);
00232 
00233     if(m_skidmark_left ) delete m_skidmark_left ;
00234     if(m_skidmark_right) delete m_skidmark_right;
00235 
00236     world->getPhysics()->removeKart(this);
00237     delete m_vehicle;
00238     delete m_tuning;
00239     delete m_vehicle_raycaster;
00240     delete m_uprightConstraint;
00241     for(int i=0; i<m_kart_chassis.getNumChildShapes(); i++)
00242     {
00243         delete m_kart_chassis.getChildShape(i);
00244     }
00245 }   // ~Kart
00246 
00247 //-----------------------------------------------------------------------------
00248 void Kart::eliminate()
00249 {
00250     m_eliminated = true;
00251     world->getPhysics()->removeKart(this);
00252 
00253     // make the kart invisible by placing it way under the track
00254     sgVec3 hell; hell[0]=0.0f; hell[1]=0.0f; hell[2] = -10000.0f;
00255     getModelTransform()->setTransform(hell);
00256 }   // eliminate
00257 
00258 //-----------------------------------------------------------------------------
00263 bool Kart::isInRest() const
00264 {
00265     return fabs(m_body->getLinearVelocity ().z())<0.2;
00266 }  // isInRest
00267 
00268 //-----------------------------------------------------------------------------
00272 void Kart::adjustSpeedWeight(float f)
00273 {
00274     m_body->setLinearVelocity(m_body->getLinearVelocity()*f);
00275     // getMass returns the mass increased by the attachment
00276     btVector3 inertia;
00277     float m=getMass();
00278     m_kart_chassis.calculateLocalInertia(m, inertia);
00279     m_body->setMassProps(m, inertia);
00280 }   // adjustSpeedWeight
00281 
00282 //-----------------------------------------------------------------------------
00283 void Kart::reset()
00284 {
00285     if(m_eliminated)
00286     {
00287         world->getPhysics()->addKart(this, m_vehicle);
00288     }
00289     Moveable::reset();
00290 
00291     m_attachment.clear();
00292     m_collectable.reset();
00293 
00294     m_race_lap             = -1;
00295     m_lap_start_time       = -1.0f;
00296     m_time_at_last_lap     = 99999.9f;
00297     m_shortcut_sector      = Track::UNKNOWN_SECTOR;
00298     m_race_position        = 9;
00299     m_finished_race        = false;
00300     m_eliminated           = false;
00301     m_finish_time          = 0.0f;
00302     m_zipper_time_left     = 0.0f;
00303     m_num_herrings_gobbled = 0;
00304     m_wheel_rotation       = 0;
00305     m_wheelie_angle        = 0.0f;
00306 
00307     m_controls.lr      = 0.0f;
00308     m_controls.accel   = 0.0f;
00309     m_controls.brake   = false;
00310     m_controls.wheelie = false;
00311     m_controls.jump    = false;
00312     m_controls.fire    = false;
00313 
00314     // Set the brakes so that karts don't slide downhill
00315     for(int i=0; i<4; i++) m_vehicle->setBrake(5.0f, i);
00316 
00317     world->m_track->findRoadSector(m_curr_pos.xyz, &m_track_sector);
00318 
00319     //If m_track_sector == UNKNOWN_SECTOR, then the kart is not on top of
00320     //the road, so we have to use another function to find the sector.
00321     if (m_track_sector == Track::UNKNOWN_SECTOR )
00322     {
00323         m_on_road = false;
00324         m_track_sector = world->m_track->findOutOfRoadSector(
00325             m_curr_pos.xyz, Track::RS_DONT_KNOW, Track::UNKNOWN_SECTOR );
00326     }
00327     else
00328     {
00329         m_on_road = true;
00330     }
00331 
00332     world->m_track->spatialToTrack( m_curr_track_coords, m_curr_pos.xyz,
00333         m_track_sector );
00334 
00335     m_vehicle->applyEngineForce (0.0f, 2);
00336     m_vehicle->applyEngineForce (0.0f, 3);
00337     // Set heading:
00338     m_transform.setRotation(btQuaternion(btVector3(0.0f, 0.0f, 1.0f), 
00339                                          DEGREE_TO_RAD(m_reset_pos.hpr[0])) );
00340     // Set position
00341     m_transform.setOrigin(btVector3(m_reset_pos.xyz[0],
00342                                     m_reset_pos.xyz[1],
00343                                     m_reset_pos.xyz[2]+0.5f*getKartHeight()));
00344     m_body->setCenterOfMassTransform(m_transform);
00345     m_body->setLinearVelocity (btVector3(0.0f,0.0f,0.0f));
00346     m_body->setAngularVelocity(btVector3(0.0f,0.0f,0.0f));
00347     m_motion_state->setWorldTransform(m_transform);
00348     for(int j=0; j<m_vehicle->getNumWheels(); j++)
00349     {
00350         m_vehicle->updateWheelTransform(j, true);
00351     }
00352 
00353     // if the kart was being rescued when a restart is called,
00354     // add the vehicle back into the physical world!
00355     if(m_rescue)
00356     {
00357         world->getPhysics()->addKart(this, m_vehicle);
00358     }
00359     m_rescue               = false;
00360 
00361     placeModel();
00362     TerrainInfo::update(m_transform.getOrigin());
00363 }   // reset
00364 
00365 //-----------------------------------------------------------------------------
00366 void Kart::doLapCounting ()
00367 {
00368     bool newLap = m_last_track_coords[1] > 300.0f && m_curr_track_coords[1] <  20.0f;
00369     if ( newLap )
00370     {
00371         // Only increase the lap counter and set the new time if the
00372         // kart hasn't already finished the race (otherwise the race_gui
00373         // will begin another countdown).
00374         if(m_race_lap+1<=race_manager->getNumLaps())
00375         {
00376             setTimeAtLap(world->getTime());
00377             m_race_lap++ ;
00378         }
00379         // Only do timings if original time was set properly. Driving backwards
00380         // over the start line will cause the lap start time to be set to -1.
00381         if(m_lap_start_time>=0.0)
00382         {
00383             float time_per_lap;
00384             if (m_race_lap == 1) // just completed first lap
00385             {
00386               time_per_lap=world->getTime();
00387             }
00388             else //completing subsequent laps
00389             {
00390               time_per_lap=world->getTime()-m_lap_start_time;
00391             }
00392                         
00393             if(time_per_lap < world->getFastestLapTime() &&
00394                 race_manager->raceHasLaps())
00395             {
00396                 world->setFastestLap(this, time_per_lap);
00397                 RaceGUI* m=(RaceGUI*)menu_manager->getRaceMenu();
00398                 if(m)
00399                 {
00400                     m->addMessage(_("New fastest lap"), NULL, 
00401                                   2.0f, 40, 100, 210, 100);
00402                     char s[20];
00403                     m->TimeToString(time_per_lap, s);
00404                     snprintf(m_fastest_lap_message, sizeof(m_fastest_lap_message),
00405                              "%s: %s",s, getName().c_str());
00406                     m->addMessage(m_fastest_lap_message, NULL, 
00407                                   2.0f, 40, 100, 210, 100);
00408                 }   // if m
00409             }   // if time_per_lap < world->getFasterstLapTime()
00410             if(isPlayerKart())
00411             {
00412                 // Put in in the highscore list???
00413                 //printf("Time per lap: %s %f\n", getName().c_str(), time_per_lap);
00414             }
00415         }
00416         m_lap_start_time = world->getTime();
00417     }
00418     else if ( m_curr_track_coords[1] > 300.0f && m_last_track_coords[1] <  20.0f)
00419     {
00420         m_race_lap-- ;
00421         // Prevent cheating by setting time to a negative number, indicating
00422         // that the line wasn't crossed properly.
00423         m_lap_start_time = -1.0f;
00424     } else
00425     {   // Switch to fast music in case of follow the leader when only 3 karts are left
00426         if(race_manager->getRaceMode()==RaceManager::RM_FOLLOW_LEADER &&
00427             world->getCurrentNumKarts()==3)  
00428         {
00429             sound_manager->switchToFastMusic();
00430         }
00431     }
00432 }   // doLapCounting
00433 
00434 //-----------------------------------------------------------------------------
00435 void Kart::collectedHerring(Herring* herring)
00436 {
00437     const herringType TYPE = herring->getType();
00438     const int OLD_HERRING_GOBBLED = m_num_herrings_gobbled;
00439 
00440     switch (TYPE)
00441     {
00442     case HE_GREEN  : m_attachment.hitGreenHerring(); break;
00443     case HE_SILVER : m_num_herrings_gobbled++ ;       break;
00444     case HE_GOLD   : m_num_herrings_gobbled += 3 ;    break;
00445     case HE_RED    : int n=1 + 4*getNumHerring() / MAX_HERRING_EATEN;
00446         m_collectable.hitRedHerring(n); break;
00447     }   // switch TYPE
00448 
00449     if ( m_num_herrings_gobbled > MAX_HERRING_EATEN )
00450         m_num_herrings_gobbled = MAX_HERRING_EATEN;
00451 
00452     if(OLD_HERRING_GOBBLED < m_num_herrings_gobbled &&
00453        m_num_herrings_gobbled == MAX_HERRING_EATEN)
00454         sound_manager->playSfx(SOUND_FULL);
00455 }   // hitHerring
00456 
00457 //-----------------------------------------------------------------------------
00458 // Simulates gears
00459 float Kart::getActualWheelForce()
00460 {
00461     float zipperF=(m_zipper_time_left>0.0f) ? stk_config->m_zipper_force : 0.0f;
00462     const std::vector<float>& gear_ratio=m_kart_properties->getGearSwitchRatio();
00463     for(unsigned int i=0; i<gear_ratio.size(); i++)
00464     {
00465         if(m_speed <= m_max_speed*gear_ratio[i]) 
00466             return getMaxPower()*m_kart_properties->getGearPowerIncrease()[i]+zipperF;
00467     }
00468     return getMaxPower()+zipperF;
00469 
00470 }   // getActualWheelForce
00471 
00472 //-----------------------------------------------------------------------------
00475 bool Kart::isOnGround() const
00476 {
00477     return m_vehicle->getWheelInfo(0).m_raycastInfo.m_isInContact &&
00478            m_vehicle->getWheelInfo(1).m_raycastInfo.m_isInContact &&
00479            m_vehicle->getWheelInfo(2).m_raycastInfo.m_isInContact &&
00480            m_vehicle->getWheelInfo(3).m_raycastInfo.m_isInContact;
00481 }   // isOnGround
00482 //-----------------------------------------------------------------------------
00483 void Kart::handleExplosion(const btVector3& pos, bool direct_hit)
00484 {
00485     if(direct_hit) 
00486     {
00487         btVector3 diff((float)(rand()%16/16), (float)(rand()%16/16), 2.0f);
00488         diff.normalize();
00489         diff*=stk_config->m_explosion_impulse/5.0f;
00490         this->m_uprightConstraint->setDisableTime(10.0f);
00491         getVehicle()->getRigidBody()->applyCentralImpulse(diff);
00492         getVehicle()->getRigidBody()->applyTorqueImpulse(btVector3(float(rand()%32*5),
00493                                                                    float(rand()%32*5),
00494                                                                    float(rand()%32*5)));
00495     }
00496     else  // only affected by a distant explosion
00497     {
00498         btVector3 diff=getPos()-pos;
00499         //if the z component is negative, the resulting impulse could push the 
00500         // kart through the floor. So in this case ignore z.
00501         if(diff.getZ()<0) diff.setZ(0.0f);
00502         float len2=diff.length2();
00503 
00504         // The correct formhale would be to first normalise diff,
00505         // then apply the impulse (which decreases 1/r^2 depending
00506         // on the distance r), so:
00507         // diff/len(diff) * impulseSize/len(diff)^2
00508         // = diff*impulseSize/len(diff)^3
00509         // We use diff*impulseSize/len(diff)^2 here, this makes the impulse
00510         // somewhat larger, which is actually more fun :)
00511         diff *= stk_config->m_explosion_impulse/len2;
00512         getVehicle()->getRigidBody()->applyCentralImpulse(diff);
00513     }
00514 }   // handleExplosion
00515 
00516 //-----------------------------------------------------------------------------
00517 void Kart::update(float dt)
00518 {
00519     m_zipper_time_left = m_zipper_time_left>0.0f ? m_zipper_time_left-dt : 0.0f;
00520 
00521     //m_wheel_rotation gives the rotation around the X-axis, and since velocity's
00522     //timeframe is the delta time, we don't have to multiply it with dt.
00523     m_wheel_rotation += m_speed*dt / m_kart_properties->getWheelRadius();
00524     m_wheel_rotation=fmodf(m_wheel_rotation, 2*M_PI);
00525 
00526     if ( m_rescue )
00527     {
00528         // Let the kart raise 2m in the 2 seconds of the rescue
00529         const float rescue_time   = 2.0f;
00530         const float rescue_height = 2.0f;
00531         if(m_attachment.getType() != ATTACH_TINYTUX)
00532         {
00533             if(isPlayerKart()) sound_manager -> playSfx ( SOUND_BZZT );
00534             m_attachment.set( ATTACH_TINYTUX, rescue_time ) ;
00535             m_rescue_pitch = m_curr_pos.hpr[1];
00536             m_rescue_roll  = m_curr_pos.hpr[2];
00537             world->getPhysics()->removeKart(this);
00538         }
00539         m_curr_pos.xyz[2] += rescue_height*dt/rescue_time;
00540 
00541         m_transform.setOrigin(btVector3(m_curr_pos.xyz[0],m_curr_pos.xyz[1],
00542                                         m_curr_pos.xyz[2]));
00543         btQuaternion q_roll (btVector3(0.f, 1.f, 0.f),
00544                              -m_rescue_roll*dt/rescue_time*M_PI/180.0f);
00545         btQuaternion q_pitch(btVector3(1.f, 0.f, 0.f),
00546                              -m_rescue_pitch*dt/rescue_time*M_PI/180.0f);
00547         m_transform.setRotation(m_transform.getRotation()*q_roll*q_pitch);
00548         m_body->setCenterOfMassTransform(m_transform);
00549 
00550         //printf("Set %f %f %f\n",pos.getOrigin().x(),pos.getOrigin().y(),pos.getOrigin().z());     
00551     }   // if m_rescue
00552     m_attachment.update(dt);
00553 
00554     /*smoke drawing control point*/
00555     if ( user_config->m_smoke )
00556     {
00557         if (m_smoke_system != NULL)
00558             m_smoke_system->update (dt);
00559     }  // user_config->smoke
00560     updatePhysics(dt);
00561 
00562     sgCopyVec2  ( m_last_track_coords, m_curr_track_coords );
00563     
00564     Moveable::update(dt);
00565 
00566     // Check if a kart is (nearly) upside down and not moving much --> automatic rescue
00567     if((fabs(m_curr_pos.hpr[2])>60 && fabs(getSpeed())<3.0f) )
00568     {
00569         forceRescue();
00570     }
00571 
00572     btTransform trans=getTrans();
00573     // Add a certain epsilon (0.2) to the height of the kart. This avoids
00574     // problems of the ray being cast from under the track (which happened
00575     // e.g. on tux tollway when jumping down from the ramp).
00576     // FIXME: this should be more thoroughly fixed, the constant is probably
00577     // dependent on the wheel size, suspension data etc.: when jumping,
00578     // the wheel suspension will be fully compressed, resulting in the
00579     // ray to start too low (under the track).
00580     btVector3 pos_plus_epsilon = trans.getOrigin()+btVector3(0,0,0.2f);
00581     //btVector3 pos_plus_epsilon (-56.6874237, -137.48851, -3.06826854);
00582     TerrainInfo::update(pos_plus_epsilon);
00583 
00584     const Material* material=getMaterial();
00585     if (getHoT()==Track::NOHIT)   // kart falling off the track
00586     {
00587         forceRescue();    
00588     } 
00589     else if(material)
00590     {
00591         // Sometimes the material can be 0. This can happen if a kart is above
00592         // another kart (e.g. mass collision, or one kart falling on another 
00593         // kart). Bullet does not have any triangle information in this case,
00594         // and so material can not be set. In this case it is simply ignored 
00595         // since it can't hurt (material is only used for friction, zipper and
00596         // rescue, so those things are not triggered till the kart is on the 
00597         // track again)
00598         if     (material->isReset()  && isOnGround()) forceRescue();
00599         else if(material->isZipper() && isOnGround()) handleZipper();
00600         else if(user_config->m_skidding)  // set friction otherwise if it's enabled
00601         {
00602             for(int i=0; i<m_vehicle->getNumWheels(); i++)
00603             {
00604                 // terrain dependent friction
00605                 m_vehicle->getWheelInfo(i).m_frictionSlip = 
00606                                 getFrictionSlip() * material->getFriction();
00607             }   // for i<getNumWheels
00608         } // neither reset nor zipper material
00609     }   // if there is material
00610 
00611     // Check if any herring was hit.
00612     herring_manager->hitHerring(this);
00613 
00614     // Save the last valid sector for forced rescue on shortcuts
00615     if(m_track_sector  != Track::UNKNOWN_SECTOR && 
00616        !m_rescue                                    ) 
00617     {
00618         m_shortcut_sector = m_track_sector;
00619     }
00620 
00621     int prev_sector = m_track_sector;
00622     if(!m_rescue)
00623         world->m_track->findRoadSector(m_curr_pos.xyz, &m_track_sector);
00624 
00625     // Check if the kart is taking a shortcut (if it's not already doing one):
00626     if(!m_rescue && world->m_track->isShortcut(prev_sector, m_track_sector))
00627     {
00628            forceRescue(/*is rescue*/ true);  // bring karts back to where they left the track.     
00629            if(isPlayerKart())
00630         {
00631             
00632             RaceGUI* m=(RaceGUI*)menu_manager->getRaceMenu();
00633             // Can happen if the option menu is called
00634             if(m)
00635                 m->addMessage(_("Invalid short-cut!!"), this, 2.0f, 60);
00636         }
00637     }
00638 
00639     if (m_track_sector == Track::UNKNOWN_SECTOR && !m_rescue)
00640     {
00641         m_on_road = false;
00642         if( m_curr_track_coords[0] > 0.0 )
00643             m_track_sector = world->m_track->findOutOfRoadSector(
00644                m_curr_pos.xyz, Track::RS_RIGHT, prev_sector );
00645         else
00646             m_track_sector = world->m_track->findOutOfRoadSector(
00647                m_curr_pos.xyz, Track::RS_LEFT, prev_sector );
00648     }
00649     else
00650     {
00651         m_on_road = true;
00652     }
00653 
00654     world->m_track->spatialToTrack( m_curr_track_coords, 
00655                                     m_curr_pos.xyz,
00656                                     m_track_sector      );
00657 
00658     doLapCounting () ;
00659     processSkidMarks();
00660 }   // update
00661 
00662 //-----------------------------------------------------------------------------
00663 // Set zipper time, and apply one time additional speed boost
00664 void Kart::handleZipper()
00665 {
00666     // Ignore a zipper that's activated while braking
00667     if(m_controls.brake) return;
00668     m_zipper_time_left  = stk_config->m_zipper_time;
00669     const btVector3& v  = m_body->getLinearVelocity();
00670     float current_speed = v.length();
00671     float speed         = std::min(current_speed+stk_config->m_zipper_speed_gain, 
00672                                    getMaxSpeed());
00673     // Avoid NAN problems, which can happen if e.g. a kart is rescued on
00674     // top of zipper, and then dropped.
00675     if(current_speed>0.00001) m_body->setLinearVelocity(v*(speed/current_speed));
00676 }   // handleZipper
00677 //-----------------------------------------------------------------------------
00678 #define sgn(x) ((x<0)?-1.0f:((x>0)?1.0f:0.0f))
00679 
00680 // -----------------------------------------------------------------------------
00681 void Kart::draw()
00682 {
00683     float m[16];
00684     btTransform t=getTrans();
00685     t.getOpenGLMatrix(m);
00686 
00687     btVector3 wire_color(0.5f, 0.5f, 0.5f);
00688     world->getPhysics()->debugDraw(m, m_body->getCollisionShape(), 
00689                                    wire_color);
00690     btCylinderShapeX wheelShape( btVector3(0.3f,
00691                                         m_kart_properties->getWheelRadius(),
00692                                         m_kart_properties->getWheelRadius()));
00693     btVector3 wheelColor(1,0,0);
00694     for(int i=0; i<m_vehicle->getNumWheels(); i++)
00695     {
00696         m_vehicle->updateWheelTransform(i, true);
00697         float m[16];
00698         m_vehicle->getWheelInfo(i).m_worldTransform.getOpenGLMatrix(m);
00699         world->getPhysics()->debugDraw(m, &wheelShape, wheelColor);
00700     }
00701 }   // draw
00702 
00703 // -----------------------------------------------------------------------------
00707 float Kart::handleWheelie(float dt)
00708 {
00709     // Handle wheelies
00710     // ===============
00711     if ( m_controls.wheelie && 
00712          m_speed >= getMaxSpeed()*getWheelieMaxSpeedRatio())
00713     {
00714         // Disable the upright constraint, since it will otherwise
00715         // work against the wheelie
00716         m_uprightConstraint->setLimit(M_PI);
00717 
00718         if ( m_wheelie_angle < getWheelieMaxPitch() )
00719             m_wheelie_angle += getWheeliePitchRate() * dt;
00720         else
00721             m_wheelie_angle = getWheelieMaxPitch();
00722     }
00723     else if ( m_wheelie_angle > 0.0f )
00724     {
00725         m_wheelie_angle -= getWheelieRestoreRate() * dt;
00726         if ( m_wheelie_angle <= 0.0f ) m_wheelie_angle = 0.0f ;
00727     }
00728     if(m_wheelie_angle <=0.0f) 
00729     {
00730         m_uprightConstraint->setLimit(m_kart_properties->getUprightTolerance());
00731         return 0.0f;
00732     }
00733 
00734     const btTransform& chassisTrans = getTrans();
00735     btVector3 targetUp(0.0f, 0.0f, 1.0f);
00736     btVector3 forwardW (chassisTrans.getBasis()[0][1],
00737                         chassisTrans.getBasis()[1][1],
00738                         chassisTrans.getBasis()[2][1]);
00739     btVector3 crossProd = targetUp.cross(forwardW);
00740     crossProd.normalize();
00741 
00742     return m_kart_properties->getWheeliePowerBoost() * getMaxPower()
00743           * m_wheelie_angle/getWheelieMaxPitch();
00744 }   // handleWheelie
00745 
00746 // -----------------------------------------------------------------------------
00752 void Kart::resetBrakes()
00753 {
00754     for(int i=0; i<4; i++) m_vehicle->setBrake(0.0f, i);
00755 }   // resetBrakes
00756 // -----------------------------------------------------------------------------
00757 void Kart::updatePhysics (float dt) 
00758 {
00759     float engine_power = getActualWheelForce() + handleWheelie(dt);
00760     if(m_attachment.getType()==ATTACH_PARACHUTE) engine_power*=0.2f;
00761 
00762     if(m_controls.accel)
00763     {   // accelerating
00764         m_vehicle->applyEngineForce(engine_power, 2);
00765         m_vehicle->applyEngineForce(engine_power, 3);
00766     }
00767     else
00768     {   // not accelerating
00769         if(m_controls.brake)
00770         {   // braking or moving backwards
00771             if(m_speed > 0.f)
00772             {   // going forward, apply brake force
00773                 m_vehicle->applyEngineForce(-getBrakeFactor()*engine_power, 2);
00774                 m_vehicle->applyEngineForce(-getBrakeFactor()*engine_power, 3);
00775             }
00776             else
00777             {   // going backward, apply reverse gear ratio
00778                 if ( fabs(m_speed) <  m_max_speed*m_max_speed_reverse_ratio )
00779                 {
00780                     m_vehicle->applyEngineForce(-engine_power*m_controls.brake, 2);
00781                     m_vehicle->applyEngineForce(-engine_power*m_controls.brake, 3);
00782                 }
00783                 else
00784                 {
00785                     m_vehicle->applyEngineForce(0.f, 2);
00786                     m_vehicle->applyEngineForce(0.f, 3);
00787                 }
00788             }
00789         }
00790         else
00791         {   // lift the foot from throttle, brakes with 10% engine_power
00792             m_vehicle->applyEngineForce(-m_controls.accel*engine_power*0.1f, 2);
00793             m_vehicle->applyEngineForce(-m_controls.accel*engine_power*0.1f, 3);
00794         }
00795     }
00796 
00797     if(isOnGround() && m_controls.jump)
00798     { 
00799       //Vector3 impulse(0.0f, 0.0f, 10.0f);
00800       //        getVehicle()->getRigidBody()->applyCentralImpulse(impulse);
00801         btVector3 velocity         = m_body->getLinearVelocity();
00802         velocity.setZ( m_kart_properties->getJumpVelocity() );
00803 
00804         getBody()->setLinearVelocity( velocity );
00805 
00806     }
00807     if(m_wheelie_angle<=0.0f)
00808     {
00809         const float steering = DEGREE_TO_RAD(getMaxSteerAngle()) * m_controls.lr;
00810         m_vehicle->setSteeringValue(steering, 0);
00811         m_vehicle->setSteeringValue(steering, 1);
00812     } 
00813     else 
00814     {
00815         m_vehicle->setSteeringValue(0.0f, 0);
00816         m_vehicle->setSteeringValue(0.0f, 1);
00817     }
00818 
00819     //store current velocity
00820     m_speed = getVehicle()->getRigidBody()->getLinearVelocity().length();
00821 
00822     // calculate direction of m_speed
00823     const btTransform& chassisTrans = getVehicle()->getChassisWorldTransform();
00824     btVector3 forwardW (
00825                chassisTrans.getBasis()[0][1],
00826                chassisTrans.getBasis()[1][1],
00827                chassisTrans.getBasis()[2][1]);
00828 
00829     if (forwardW.dot(getVehicle()->getRigidBody()->getLinearVelocity()) < btScalar(0.))
00830         m_speed *= -1.f;
00831 
00832     //cap at maximum velocity
00833     const float max_speed = m_kart_properties->getMaximumSpeed();
00834     if ( m_speed >  max_speed )
00835     {
00836         const float velocity_ratio = max_speed/m_speed;
00837         m_speed                    = max_speed;
00838         btVector3 velocity         = m_body->getLinearVelocity();
00839 
00840         velocity.setY( velocity.getY() * velocity_ratio );
00841         velocity.setX( velocity.getX() * velocity_ratio );
00842 
00843         getVehicle()->getRigidBody()->setLinearVelocity( velocity );
00844 
00845     }
00846     //at low velocity, forces on kart push it back and forth so we ignore this
00847     if(fabsf(m_speed) < 0.2f) // quick'n'dirty workaround for bug 1776883
00848          m_speed = 0;
00849 }   // updatePhysics
00850 
00851 
00852 //-----------------------------------------------------------------------------
00853 void Kart::forceRescue(bool is_shortcut)
00854 {
00855     m_rescue=true;
00856     // If rescue is triggered while doing a shortcut, reset the kart to the
00857     // segment where the shortcut started!! And then reset the shortcut
00858     // flag, so that this shortcut is not counted!
00859     if(is_shortcut)
00860     {
00861         m_track_sector   = m_shortcut_sector;
00862     } 
00863 }   // forceRescue
00864 //-----------------------------------------------------------------------------
00867 void Kart::endRescue()
00868 {
00869     if ( m_track_sector > 0 ) m_track_sector-- ;
00870     world ->m_track -> trackToSpatial ( m_curr_pos.xyz, m_track_sector ) ;
00871     m_curr_pos.hpr[0] = world->m_track->m_angle[m_track_sector] ;
00872     m_rescue = false ;
00873 
00874     m_body->setLinearVelocity (btVector3(0.0f,0.0f,0.0f));
00875     m_body->setAngularVelocity(btVector3(0.0f,0.0f,0.0f));
00876     // FIXME: This code positions the kart correctly back on the track
00877     // (nearest waypoint) - but if the kart is simply upside down,
00878     // it feels better if the kart is left where it was. Perhaps
00879     // this code should only be used if a rescue was not triggered
00880     // by the kart being upside down??
00881     btTransform pos;
00882     // A certain epsilon is added here to the Z coordinate (0.1), in case
00883     // that the drivelines are somewhat under the track. Otherwise, the
00884     // kart will be placed a little bit under the track, triggering
00885     // a rescue, ...
00886     pos.setOrigin(btVector3(m_curr_pos.xyz[0],m_curr_pos.xyz[1],
00887                             m_curr_pos.xyz[2]+0.5f*getKartHeight()+0.1f));
00888     pos.setRotation(btQuaternion(btVector3(0.0f, 0.0f, 1.0f), 
00889                                  DEGREE_TO_RAD(world->m_track->m_angle[m_track_sector])));
00890     m_body->setCenterOfMassTransform(pos);
00891     world->getPhysics()->addKart(this, m_vehicle);
00892     setTrans(pos);
00893 }   // endRescue
00894 
00895 //-----------------------------------------------------------------------------
00896 void Kart::processSkidMarks()
00897 {
00898     // FIXME: disable skidmarks for now, they currently look ugly, and are
00899     //        sometimes hovering in the air
00900     return;
00901     assert(m_skidmark_left);
00902     assert(m_skidmark_right);
00903     const float threshold = 0.3f;
00904     const float ANGLE     = 43.0f;
00905     const float LENGTH    = 0.57f;
00906     bool skid_front = m_vehicle->getWheelInfo(0).m_skidInfo < threshold ||
00907                       m_vehicle->getWheelInfo(1).m_skidInfo < threshold;
00908     bool skid_rear  = m_vehicle->getWheelInfo(2).m_skidInfo < threshold ||
00909                       m_vehicle->getWheelInfo(3).m_skidInfo < threshold;
00910     if(skid_rear || skid_front)
00911     {
00912         if(isOnGround())
00913         {
00914             m_skidmark_left ->add(*getCoord(),  ANGLE, LENGTH);
00915             m_skidmark_right->add(*getCoord(),  ANGLE, LENGTH);            
00916         }
00917         else
00918         {   // not on ground
00919             m_skidmark_left->addBreak(*getCoord(),  ANGLE, LENGTH);
00920             m_skidmark_right->addBreak(*getCoord(), ANGLE, LENGTH);
00921         }   // on ground
00922     }
00923     else
00924     {   // !skid_rear && !skid_front    
00925         if(m_skidmark_left->wasSkidMarking())
00926             m_skidmark_left->addBreak(*getCoord(),  ANGLE, LENGTH);
00927 
00928         if(m_skidmark_right->wasSkidMarking())
00929             m_skidmark_right->addBreak(*getCoord(), ANGLE, LENGTH);
00930     }
00931 }   // processSkidMarks
00932 
00933 //-----------------------------------------------------------------------------
00934 void Kart::load_wheels(ssgBranch* branch)
00935 {
00936     if (!branch) return;
00937 
00938     for(ssgEntity* i = branch->getKid(0); i != NULL; i = branch->getNextKid())
00939     {
00940         if (i->getName())
00941         { // We found something that might be a wheel
00942             if (strcmp(i->getName(), "WheelFront.L") == 0)
00943             {
00944                 m_wheel_front_l = add_transform(dynamic_cast<ssgTransform*>(i));
00945             }
00946             else if (strcmp(i->getName(), "WheelFront.R") == 0)
00947             {
00948                 m_wheel_front_r = add_transform(dynamic_cast<ssgTransform*>(i));
00949             }
00950             else if (strcmp(i->getName(), "WheelRear.L") == 0)
00951             {
00952                 m_wheel_rear_l = add_transform(dynamic_cast<ssgTransform*>(i));
00953             }
00954             else if (strcmp(i->getName(), "WheelRear.R") == 0)
00955             {
00956                 m_wheel_rear_r = add_transform(dynamic_cast<ssgTransform*>(i));
00957             }
00958             else
00959             {
00960                 // Wasn't a wheel, continue searching
00961                 load_wheels(dynamic_cast<ssgBranch*>(i));
00962             }
00963         }
00964         else
00965         { // Can't be a wheel,continue searching
00966             load_wheels(dynamic_cast<ssgBranch*>(i));
00967         }
00968     }   // for i
00969 }   // load_wheels
00970 
00971 //-----------------------------------------------------------------------------
00972 void Kart::loadData()
00973 {
00974     float r [ 2 ] = { -10.0f, 100.0f } ;
00975 
00976     m_smokepuff = new ssgSimpleState ();
00977     m_smokepuff->setTexture(material_manager->getMaterial("smoke.rgb")->getState()->getTexture());
00978     m_smokepuff -> setTranslucent    () ;
00979     m_smokepuff -> enable            ( GL_TEXTURE_2D ) ;
00980     m_smokepuff -> setShadeModel     ( GL_SMOOTH ) ;
00981     m_smokepuff -> enable            ( GL_CULL_FACE ) ;
00982     m_smokepuff -> enable            ( GL_BLEND ) ;
00983     m_smokepuff -> enable            ( GL_LIGHTING ) ;
00984     m_smokepuff -> setColourMaterial ( GL_EMISSION ) ;
00985     m_smokepuff -> setMaterial       ( GL_AMBIENT, 0, 0, 0, 1 ) ;
00986     m_smokepuff -> setMaterial       ( GL_DIFFUSE, 0, 0, 0, 1 ) ;
00987     m_smokepuff -> setMaterial       ( GL_SPECULAR, 0, 0, 0, 1 ) ;
00988     m_smokepuff -> setShininess      (  0 ) ;
00989 
00990     ssgEntity *obj = m_kart_properties->getModel();
00991     createPhysics(obj);
00992 
00993     load_wheels(dynamic_cast<ssgBranch*>(obj));
00994 
00995     // Optimize the model, this can't be done while loading the model
00996     // because it seems that it removes the name of the wheels or something
00997     // else needed to load the wheels as a separate object.
00998     ssgFlatten(obj);
00999 
01000     createDisplayLists(obj);  // create all display lists
01001     ssgRangeSelector *lod = new ssgRangeSelector ;
01002 
01003     lod -> addKid ( obj ) ;
01004     lod -> setRanges ( r, 2 ) ;
01005 
01006     this-> getModelTransform() -> addKid ( lod ) ;
01007 
01008     // Attach Particle System
01009     //JH  sgCoord pipe_pos = {{0, 0, .3}, {0, 0, 0}} ;
01010     m_smoke_system = new Smoke(this, 50, 100.0f, true, 0.35f, 1000);
01011     m_smoke_system -> init(5);
01012     //JH      m_smoke_system -> setState (getMaterial ("smoke.png")-> getState() );
01013     //m_smoke_system -> setState ( m_smokepuff ) ;
01014     //      m_exhaust_pipe = new ssgTransform (&pipe_pos);
01015     //      m_exhaust_pipe -> addKid (m_smoke_system) ;
01016     //      comp_model-> addKid (m_exhaust_pipe) ;
01017 
01018     // 
01019     m_skidmark_left  = new SkidMark(/* angle sign */ -1);
01020     m_skidmark_right = new SkidMark(/* angle sign */  1);
01021 
01022     m_shadow = createShadow(m_kart_properties->getShadowFile(), -1, 1, -1, 1);
01023     m_shadow->ref();
01024     m_model_transform->addKid ( m_shadow );
01025 }   // loadData
01026 
01027 //-----------------------------------------------------------------------------
01028 void Kart::placeModel ()
01029 {
01030     sgMat4 wheel_front;
01031     sgMat4 wheel_steer;
01032     sgMat4 wheel_rot;
01033 
01034     sgMakeRotMat4( wheel_rot, 0, RAD_TO_DEGREE(-m_wheel_rotation), 0);
01035     sgMakeRotMat4( wheel_steer, m_controls.lr * 30.0f , 0, 0);
01036 
01037     sgMultMat4(wheel_front, wheel_steer, wheel_rot);
01038 
01039     if (m_wheel_front_l) m_wheel_front_l->setTransform(wheel_front);
01040     if (m_wheel_front_r) m_wheel_front_r->setTransform(wheel_front);
01041 
01042     if (m_wheel_rear_l) m_wheel_rear_l->setTransform(wheel_rot);
01043     if (m_wheel_rear_r) m_wheel_rear_r->setTransform(wheel_rot);
01044 
01045     // Only transfer the bullet data to the plib tree if no history is being
01046     // replayed.
01047     if(!user_config->m_replay_history)
01048     {
01049         float m[4][4];
01050         getTrans().getOpenGLMatrix((float*)&m);
01051         
01052         //printf(" is %f %f %f\n",t.getOrigin().x(),t.getOrigin().y(),t.getOrigin().z());
01053         // Transfer the new position and hpr to m_curr_pos
01054         sgSetCoord(&m_curr_pos, m);
01055     }
01056     sgCoord c ;
01057     sgCopyCoord ( &c, &m_curr_pos );
01058     const float CENTER_SHIFT  = getGravityCenterShift();
01059     const float offset_pitch  = m_wheelie_angle;
01060     const float offset_z      = 0.3f*fabs(sin(m_wheelie_angle*SG_DEGREES_TO_RADIANS))
01061                               - (0.5f-CENTER_SHIFT)*getKartHeight();
01062     
01063     m_curr_pos.xyz[2] += offset_z;
01064     m_curr_pos.hpr[1] += offset_pitch;
01065     Moveable::placeModel();
01066     m_curr_pos.xyz[2] -= offset_z;
01067     m_curr_pos.hpr[1] -= offset_pitch;
01068 }   // placeModel
01069 //-----------------------------------------------------------------------------
01070 void Kart::setFinishingState(float time)
01071 {
01072     m_finished_race = true;
01073     m_finish_time   = time;
01074 }   // setFinishingState
01075 
01076 //-----------------------------------------------------------------------------
01077 float Kart::estimateFinishTime  ()
01078 {
01079     // Estimate the arrival time of any karts that haven't arrived
01080     // yet by using their average speed up to now and the distance
01081     // still to race. This approach guarantees that the order of 
01082     // the karts won't change anymore (karts ahead will have a 
01083     // higher average speed and therefore finish the race earlier 
01084     // than karts further behind), so the position doesn't have to
01085     // be updated to get the correct scoring.
01086     float distance_covered  = getLap()*world->m_track->getTrackLength()
01087                             + getDistanceDownTrack();
01088     // In case that a kart is rescued behind start line, or ...
01089     if(distance_covered<0) distance_covered =1.0f;
01090 
01091     float average_speed     = distance_covered/world->getTime();
01092 
01093     // Finish time is the time needed for the whole race with 
01094     // the average speed computed above.
01095     return race_manager->getNumLaps()*world->getTrack()->getTrackLength() 
01096           / average_speed;
01097 
01098 }   // estimateFinishTime
01099 /* EOF */