//  $Id$
//
//  SuperTuxKart - a fun racing game with go-kart
//  Copyright (C) 2004-2005 Steve Baker <sjbaker1@airmail.net>
//  Copyright (C) 2006 SuperTuxKart-Team, Joerg Henrichs, Steve Baker
//
//  This program is free software; you can redistribute it and/or
//  modify it under the terms of the GNU General Public License
//  as published by the Free Software Foundation; either version 2
//  of the License, or (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.


#include <iostream>
#include <plib/ssg.h>

#include "herring_manager.hpp"
#include "sound_manager.hpp"
#include "loader.hpp"
#include "skid_mark.hpp"
#include "user_config.hpp"
#include "constants.hpp"
#include "shadow.hpp"
#include "track.hpp"
#include "world.hpp"
#include "kart.hpp"
#include "ssg_help.hpp"
#include "physics.hpp"
#include "gui/menu_manager.hpp"
#include "gui/race_gui.hpp"
#include "translation.hpp"
#ifdef BULLET
#include "../bullet/Demos/OpenGL/GL_ShapeDrawer.h"
#endif
#if defined(WIN32) && !defined(__CYGWIN__)
#  define snprintf  _snprintf
#endif

KartParticleSystem::KartParticleSystem(Kart* kart_,
                                       int num, float _create_rate, int _ttf,
                                       float sz, float bsphere_size)
        : ParticleSystem (num, _create_rate, _ttf, sz, bsphere_size),
        m_kart(kart_)
{
    getBSphere () -> setCenter ( 0, 0, 0 ) ;
    getBSphere () -> setRadius ( 1000.0f ) ;
    dirtyBSphere();
}   // KartParticleSystem

//-----------------------------------------------------------------------------
void KartParticleSystem::update ( float t )
{
#if 0
    std::cout << "BSphere: r:" << getBSphere()->radius
    << " ("  << getBSphere()->center[0]
    << ", "  << getBSphere()->center[1]
    << ", "  << getBSphere()->center[2]
    << ")"
    << std::endl;
#endif
    getBSphere () -> setRadius ( 1000.0f ) ;
    ParticleSystem::update(t);
}   // update

//-----------------------------------------------------------------------------
void KartParticleSystem::particle_create(int, Particle *p)
{
    sgSetVec4 ( p -> m_col, 1, 1, 1, 1 ) ; /* initially white */
    sgSetVec3 ( p -> m_pos, 0, 0, 0 ) ;    /* start off on the ground */
    sgSetVec3 ( p -> m_vel, 0, 0, 0 ) ;
    sgSetVec3 ( p -> m_acc, 0, 0, 2.0f ) ; /* Gravity */
    p -> m_size = .5f;
    p -> m_time_to_live = 0.5 ;            /* Droplets evaporate after 5 seconds */

    const sgCoord* POS = m_kart->getCoord();
    const sgCoord* VEL = m_kart->getVelocity();

    const float X_DIRECTION = sgCos (POS->hpr[0] - 90.0f); // Point at the rear
    const float Y_DIRECTION = sgSin (POS->hpr[0] - 90.0f); // Point at the rear

    sgCopyVec3 (p->m_pos, POS->xyz);

    p->m_pos[0] += X_DIRECTION * 0.7f;
    p->m_pos[1] += Y_DIRECTION * 0.7f;

    const float ABS_VEL = sqrt((VEL->xyz[0] * VEL->xyz[0]) + (VEL->xyz[1] * VEL->xyz[1]));

    p->m_vel[0] = X_DIRECTION * -ABS_VEL/2;
    p->m_vel[1] = Y_DIRECTION * -ABS_VEL/2;

    p->m_vel[0] += sgCos ((float)(rand()%180));
    p->m_vel[1] += sgSin ((float)(rand()%180));
    p->m_vel[2] += sgSin ((float)(rand()%100));

    getBSphere () -> setCenter ( POS->xyz[0], POS->xyz[1], POS->xyz[2] ) ;
}   // particle_create

//-----------------------------------------------------------------------------
void KartParticleSystem::particle_update (float delta, int,
        Particle * particle)
{
    particle->m_size    += delta*2.0f;
    particle->m_col[3]  -= delta * 2.0f;

    particle->m_pos[0] += particle->m_vel[0] * delta;
    particle->m_pos[1] += particle->m_vel[1] * delta;
    particle->m_pos[2] += particle->m_vel[2] * delta;
}  // particle_update

//-----------------------------------------------------------------------------
void KartParticleSystem::particle_delete (int , Particle* )
{}   // particle_delete

//=============================================================================
Kart::Kart (const KartProperties* kartProperties_, int position_ ,
            sgCoord init_pos)
        : Moveable(true), m_attachment(this), m_collectable(this)
{
    m_kart_properties      = kartProperties_;
    m_grid_position        = position_ ;
    m_num_herrings_gobbled = 0;
    m_finished_race        = false;
    m_finish_time          = 0.0f;
    m_prev_accel           = 0.0f;
    m_wheelie_angle        = 0.0f;
    m_current_friction     = 1.0f;
    m_smokepuff            = NULL;
    m_smoke_system         = NULL;
    m_exhaust_pipe         = NULL;
    m_skidmark_left        = NULL;
    m_skidmark_right       = NULL;
    m_track_sector         = Track::UNKNOWN_SECTOR;
    sgCopyCoord(&m_reset_pos, &init_pos);
    // Neglecting the roll resistance (which is small for high speeds compared
    // to the air resistance), maximum speed is reached when the engine
    // power equals the air resistance force, resulting in this formula:
#ifdef BULLET
    m_max_speed            = m_kart_properties->getMaximumSpeed();
    m_speed                = 0.0f;
#else
    m_max_speed            = sqrt(getMaxPower()/getAirResistance());
#endif

    m_wheel_position = 0;

    m_wheel_front_l  = NULL;
    m_wheel_front_r  = NULL;
    m_wheel_rear_l   = NULL;
    m_wheel_rear_r   = NULL;
    m_lap_start_time = -1.0f;
    loadData();
}   // Kart

// -----------------------------------------------------------------------------v
void Kart::createPhysics(ssgEntity *obj)
{
    // First: Create the chassis of the kart
    // -------------------------------------
    // The size for bullet must be specified in half extends!
    //    ssgEntity *model = getModel();
    float x_min, x_max, y_min, y_max, z_min, z_max;
    MinMax(obj, &x_min, &x_max, &y_min, &y_max, &z_min, &z_max);
    m_kart_width  = x_max-x_min;
#ifdef BULLET
    float kart_length = y_max-y_min;
    if(kart_length<1.2) kart_length=1.5f;

    // The kart height is needed later to reset the physics to the correct
    // position.
    m_kart_height  = z_max-z_min;

    m_kart_chassis = new btBoxShape(btVector3(0.5*kart_width,
                                              0.5*kart_length,
                                              0.5*m_kart_height));
    // Set mass and inertia
    // --------------------
    float mass=getMass();
    btVector3 inertia;
    m_kart_chassis->calculateLocalInertia(mass, inertia);

    // Position the chassis
    // --------------------
    btTransform trans;
    trans.setIdentity();
    m_motion_state = new btDefaultMotionState(trans);

    // Then create a rigid body
    // ------------------------
    m_kart_body = new btRigidBody(mass, m_motion_state, 
                                  m_kart_chassis, inertia);
    m_kart_body->setDamping(m_kart_properties->getChassisLinearDamping(), 
                            m_kart_properties->getChassisAngularDamping() );

    // Reset velocities
    // ----------------
    m_kart_body->setLinearVelocity (btVector3(0.0f,0.0f,0.0f));
    m_kart_body->setAngularVelocity(btVector3(0.0f,0.0f,0.0f));

    // Create the actual vehicle
    // -------------------------
    m_vehicle_raycaster = 
        new btDefaultVehicleRaycaster(world->getPhysics()->getPhysicsWorld());
    m_tuning = new btRaycastVehicle::btVehicleTuning();
    m_vehicle = new btRaycastVehicle(*m_tuning, m_kart_body, m_vehicle_raycaster);
    // never deactivate the vehicle
    m_kart_body->setActivationState(DISABLE_DEACTIVATION);
    m_vehicle->setCoordinateSystem(/*right: */ 0,  /*up: */ 2,  /*forward: */ 1);
    
    // Add wheels
    // ----------
    float wheel_width  = m_kart_properties->getWheelWidth();
    float wheel_radius = m_kart_properties->getWheelRadius();
    float suspension_rest = 0;
    float connection_height = -0.5*m_kart_height;
    btVector3 wheel_direction(0.0f, 0.0f, -1.0f);
    btVector3 wheel_axle(1.0f,0.0f,0.0f);

    // right front wheel
    btVector3 wheel_coord(0.5f*kart_width-0.3f*wheel_width,
                          0.5f*kart_length-wheel_radius,
                          connection_height);
    m_vehicle->addWheel(wheel_coord, wheel_direction, wheel_axle,
                        suspension_rest, wheel_radius, *m_tuning,
                        /* isFrontWheel: */ true);

    // left front wheel
    wheel_coord = btVector3(-0.5f*kart_width+0.3f*wheel_width,
                            0.5f*kart_length-wheel_radius,
                            connection_height);
    m_vehicle->addWheel(wheel_coord, wheel_direction, wheel_axle,
                        suspension_rest, wheel_radius, *m_tuning,
                        /* isFrontWheel: */ true);

    // right rear wheel
    wheel_coord = btVector3(0.5*kart_width-0.3f*wheel_width, 
                            -0.5*kart_length+wheel_radius,
                            connection_height);
    m_vehicle->addWheel(wheel_coord, wheel_direction, wheel_axle,
                        suspension_rest, wheel_radius, *m_tuning,
                        /* isFrontWheel: */ false);

    // right rear wheel
    wheel_coord = btVector3(-0.5*kart_width+0.3f*wheel_width,
                            -0.5*kart_length+wheel_radius,
                            connection_height);
    m_vehicle->addWheel(wheel_coord, wheel_direction, wheel_axle,
                        suspension_rest, wheel_radius, *m_tuning,
                        /* isFrontWheel: */ false);

    for(int i=0; i<m_vehicle->getNumWheels(); i++)
    {
        btWheelInfo& wheel = m_vehicle->getWheelInfo(i);
        wheel.m_suspensionStiffness      = m_kart_properties->getSuspensionStiffness();
        wheel.m_wheelsDampingRelaxation  = m_kart_properties->getWheelDampingRelaxation();
        wheel.m_wheelsDampingCompression = m_kart_properties->getWheelDampingCompression();
        wheel.m_frictionSlip             = m_kart_properties->getFrictionSlip();
        wheel.m_rollInfluence            = m_kart_properties->getRollInfluence();
    }
    world->getPhysics()->addKart(this, m_vehicle);

#endif
}   // createPhysics

// -----------------------------------------------------------------------------
Kart::~Kart() 
{
    delete m_smokepuff;

    sgMat4 wheel_steer;
    sgMakeIdentMat4(wheel_steer);
    if (m_wheel_front_l) m_wheel_front_l->setTransform(wheel_steer);
    if (m_wheel_front_r) m_wheel_front_r->setTransform(wheel_steer);


    ssgDeRefDelete(m_shadow);
    ssgDeRefDelete(m_wheel_front_l);
    ssgDeRefDelete(m_wheel_front_r);
    ssgDeRefDelete(m_wheel_rear_l);
    ssgDeRefDelete(m_wheel_rear_r);

    if(m_skidmark_left ) delete m_skidmark_left ;
    if(m_skidmark_right) delete m_skidmark_right;

#ifdef BULLET
    delete m_tuning;
    delete m_vehicle_raycaster;
    delete m_vehicle;
    delete m_kart_chassis;
    delete m_kart_body;
    delete m_motion_state;
#endif
}   // ~Kart

//-----------------------------------------------------------------------------
/** Returns true if the kart is 'resting'
 *
 * Returns true if the kart is 'resting', i.e. (nearly) not moving.
 */
#ifdef BULLET
bool Kart::isInRest()
{
    return fabs(m_kart_body->getLinearVelocity ().z())<0.2;
}  // isInRest
#endif
//-----------------------------------------------------------------------------
/** Modifies the physics parameter to simulate an attached anvil.
 *  The velocity is multiplicated by f, and the mass of the kart is increased.
 */
void Kart::adjustSpeedWeight(float f)
{
#ifdef BULLET
    m_kart_body->setLinearVelocity(m_kart_body->getLinearVelocity()*f);
    // getMass returns the mass increased by the attachment
    btVector3 inertia;
    float m=getMass();
    m_kart_chassis->calculateLocalInertia(m, inertia);
    m_kart_body->setMassProps(m, inertia);
#else
    getVelocity()->xyz[1] *= f;
#endif
}   // adjustSpeedWeight

//-----------------------------------------------------------------------------
void Kart::reset()
{
    Moveable::reset();

    m_attachment.clear();
    m_collectable.clear();

    m_race_lap             = -1;
    m_lap_start_time       = -1.0f;
    m_shortcut_count       = 0;
    m_shortcut_sector      = Track::UNKNOWN_SECTOR;
    m_shortcut_type        = SC_NONE;
    m_race_position        = 9;
    m_finished_race        = false;
    m_finish_time          = 0.0f;
    m_zipper_time_left     = 0.0f;
    m_rescue               = false;
    m_num_herrings_gobbled = 0;
    m_wheel_position       = 0;
    m_wheelie_angle        = 0.0f;

    m_controls.lr      = 0.0f;
    m_controls.accel   = 0.0f;
    m_controls.brake   = false;
    m_controls.wheelie = false;
    m_controls.jump    = false;
    m_controls.fire    = false;

    m_track_sector = world->m_track->findRoadSector(m_curr_pos.xyz);

    //If m_track_sector == UNKNOWN_SECTOR, then the kart is not on top of
    //the road, so we have to use another function to find the sector.
    if (m_track_sector == Track::UNKNOWN_SECTOR )
    {
        m_on_road = false;
        m_track_sector = world->m_track->findOutOfRoadSector(
            m_curr_pos.xyz, Track::RS_DONT_KNOW, Track::UNKNOWN_SECTOR );
    }
    else
    {
        m_on_road = true;
    }

    world->m_track->spatialToTrack( m_curr_track_coords, m_curr_pos.xyz,
        m_track_sector );

#ifdef BULLET
    btTransform *trans=new btTransform();
    trans->setIdentity();
    trans->setOrigin(btVector3(m_reset_pos.xyz[0],
                               m_reset_pos.xyz[1],
                               m_reset_pos.xyz[2]+0.5*m_kart_height));
    m_vehicle->applyEngineForce (0.0f, 2);
    m_vehicle->applyEngineForce (0.0f, 3);
    m_kart_body->setCenterOfMassTransform(*trans);
    m_kart_body->setLinearVelocity (btVector3(0.0f,0.0f,0.0f));
    m_kart_body->setAngularVelocity(btVector3(0.0f,0.0f,0.0f));
    for(int j=0; j<m_vehicle->getNumWheels(); j++)
    {
        m_vehicle->updateWheelTransform(j, true);
    }

#endif
    placeModel();
}   // reset

//-----------------------------------------------------------------------------
void Kart::handleZipper()
{
    m_wheelie_angle  = ZIPPER_ANGLE;
    m_zipper_time_left = ZIPPER_TIME;
}   // handleZipper

//-----------------------------------------------------------------------------
void Kart::doLapCounting ()
{
    bool newLap = m_last_track_coords[1] > 300.0f && m_curr_track_coords[1] <  20.0f;
    if (  newLap   &&
         (world->m_race_setup.m_difficulty==RD_EASY                         ||
          world->m_race_setup.m_difficulty==RD_MEDIUM && m_shortcut_count<2 ||
          world->m_race_setup.m_difficulty==RD_HARD   && m_shortcut_count<1   ) )
    {
        // Only increase the lap counter and set the new time if the
        // kart hasn't already finished the race (otherwise the race_gui
        // will begin another countdown).
        if(m_race_lap+1<=world->m_race_setup.m_num_laps)
        {
            setTimeAtLap(world->m_clock);
            m_race_lap++ ;
        }

        m_shortcut_count = 0;
        // Only do timings if original time was set properly. Driving backwards
        // over the start line will cause the lap start time to be set to 0.
        if(m_lap_start_time>=0.0)
        {
            float time_per_lap=world->m_clock-m_lap_start_time;
            if(time_per_lap < world->getFastestLapTime() )
            {
                world->setFastestLap(this, time_per_lap);
                RaceGUI* m=(RaceGUI*)menu_manager->getRaceMenu();
                if(m)
                {
                    m->addMessage(_("New fastest lap"), NULL, 
                                  2.0f, 40, 100, 210, 100);
                    char s[20];
                    m->TimeToString(time_per_lap, s);
                    snprintf(m_fastest_lap_message, sizeof(m_fastest_lap_message),
                             "%s: %s",s, getName().c_str());
                    m->addMessage(m_fastest_lap_message, NULL, 
                                  2.0f, 40, 100, 210, 100);
                }   // if m
            }   // if time_per_lap < world->getFasterstLapTime()
            if(isPlayerKart())
            {
                // Put in in the highscore list???
                //printf("Time per lap: %s %f\n", getName().c_str(), time_per_lap);
            }
        }
        m_lap_start_time = world->m_clock;
    }
    else if ( newLap )
    {
        // Might happen if the option menu is called
        RaceGUI* m=(RaceGUI*)menu_manager->getRaceMenu();
        if(m)
        {
            m->addMessage(_("Lap not counted"),  this, 2.0f, 60);
            m->addMessage(_("(shortcut taken)"), this, 2.0f, 60);
        }
        m_shortcut_count = 0;
    }
    else if ( m_curr_track_coords[1] > 300.0f && m_last_track_coords[1] <  20.0f)
    {
        m_race_lap-- ;
        // Prevent cheating by setting time to a negative number, indicating
        // that the line wasn't crossed properly.
        m_lap_start_time = -1.0f;
    }
}   // doLapCounting

//-----------------------------------------------------------------------------
void Kart::doObjectInteractions ()
{
    int i;
    for ( i = 0 ; i < m_grid_position ; i++ )
    {
        sgVec3 xyz ;
        Kart *other_kart = world->getKart(i);
        sgSubVec3(xyz, getCoord()->xyz, other_kart->getCoord()->xyz );
        float dist = sgLengthSquaredVec3(xyz);
        if ( dist < 1.1f )
        {
            // Avoid division by zero
            if(dist>0.00001) sgNormalizeVec2(xyz) ;

            world->addCollisions(m_grid_position, 1);
            world->addCollisions(i,             1);
            if ( m_velocity.xyz[1] > other_kart->getVelocity()->xyz[1] )
            {
                forceCrash () ;
                sgSubVec2 ( other_kart->getCoord()->xyz, xyz ) ;
            }
            else
            {
                other_kart->forceCrash () ;
                sgAddVec2 ( getCoord()->xyz, xyz ) ;
            }
            if(m_attachment.getType()==ATTACH_BOMB &&
               m_attachment.getPreviousOwner()!=other_kart) 
            {
                m_attachment.moveBombFromTo(this, other_kart);
            }
            if(other_kart->m_attachment.getType()==ATTACH_BOMB &&
               other_kart->m_attachment.getPreviousOwner()!=this)
            {
                m_attachment.moveBombFromTo(other_kart, this);
            }
        }   // if sgLengthSquaredVec2(xy)<1.0
    }   // for i

    // Check if any herring was hit.
    herring_manager->hitHerring(this);
}   // doObjectInteractions

//-----------------------------------------------------------------------------
void Kart::collectedHerring(Herring* herring)
{
    const herringType TYPE = herring->getType();
    const int OLD_HERRING_GOBBLED = m_num_herrings_gobbled;

    switch (TYPE)
    {
    case HE_GREEN  : m_attachment.hitGreenHerring(); break;
    case HE_SILVER : m_num_herrings_gobbled++ ;       break;
    case HE_GOLD   : m_num_herrings_gobbled += 3 ;    break;
    case HE_RED    : int n=1 + 4*getNumHerring() / MAX_HERRING_EATEN;
        m_collectable.hitRedHerring(n); break;
    }   // switch TYPE

    if ( m_num_herrings_gobbled > MAX_HERRING_EATEN )
        m_num_herrings_gobbled = MAX_HERRING_EATEN;

    if(OLD_HERRING_GOBBLED < m_num_herrings_gobbled &&
       m_num_herrings_gobbled == MAX_HERRING_EATEN)
        sound_manager->playSfx(SOUND_FULL);
}   // hitHerring

//-----------------------------------------------------------------------------
void Kart::doZipperProcessing (float delta)
{
    if ( m_zipper_time_left > delta )
    {
        m_zipper_time_left -= delta ;
        if ( m_velocity.xyz[1] < ZIPPER_VELOCITY )
            m_velocity.xyz[1] = ZIPPER_VELOCITY ;
    }
    else m_zipper_time_left = 0.0f ;
}   // doZipperProcessing

//-----------------------------------------------------------------------------
void Kart::forceCrash ()
{

    m_wheelie_angle = CRASH_PITCH ;

    m_velocity.xyz[0] = m_velocity.xyz[1] = m_velocity.xyz[2] =
    m_velocity.hpr[0] = m_velocity.hpr[1] = m_velocity.hpr[2] = 0.0f ;
}  // forceCrash

//-----------------------------------------------------------------------------
void Kart::doCollisionAnalysis ( float delta, float hot )
{
    if ( m_collided )
    {
        if ( m_velocity.xyz[1] > MIN_COLLIDE_VELOCITY )
        {
            m_velocity.xyz[1] -= COLLIDE_BRAKING_RATE * delta ;
        }
        else if ( m_velocity.xyz[1] < -MIN_COLLIDE_VELOCITY )
        {
            m_velocity.xyz[1] += COLLIDE_BRAKING_RATE * delta ;
        }
    }   // if collided

    if ( m_crashed && m_velocity.xyz[1] > MIN_CRASH_VELOCITY )
    {
        forceCrash () ;
    }
    else if ( m_wheelie_angle < 0.0f )
    {
        m_wheelie_angle += getWheelieRestoreRate() * delta;
        if ( m_wheelie_angle >= 0.0f ) m_wheelie_angle = 0.0f ;
    }

    /* Make sure that the car doesn't go through the floor */
    if ( isOnGround() )
    {
        m_velocity.xyz[2] = 0.0f ;
    }   // isOnGround
}   // doCollisionAnalysis

//-----------------------------------------------------------------------------
void Kart::update (float dt)
{
    //m_wheel_position gives the rotation around the X-axis, and since velocity's
    //timeframe is the delta time, we don't have to multiply it with dt.
    m_wheel_position += m_velocity.xyz[1];

    if ( m_rescue )
    {
        // Let the kart raise 2m in the 2 seconds of the rescue
        const float rescue_time   = 2.0f;
#ifdef BULLET
        const float rescue_height = 2.0f;
#endif
        if(m_attachment.getType() != ATTACH_TINYTUX)
        {
            if(isPlayerKart()) sound_manager -> playSfx ( SOUND_BZZT );
            m_attachment.set( ATTACH_TINYTUX, rescue_time ) ;
#ifdef BULLET
            m_rescue_pitch = m_curr_pos.hpr[1];
            m_rescue_roll  = m_curr_pos.hpr[2];
            world->getPhysics()->removeKart(this, m_vehicle);
#endif
        }
#ifdef BULLET
        m_curr_pos.xyz[2] += rescue_height*dt/rescue_time;

        btTransform pos=m_kart_body->getCenterOfMassTransform();
        pos.setOrigin(btVector3(m_curr_pos.xyz[0],m_curr_pos.xyz[1],m_curr_pos.xyz[2]));
        btQuaternion q_roll (btVector3(0., 1., 0.),
                             -m_rescue_roll*dt/rescue_time*M_PI/180.0);
        btQuaternion q_pitch(btVector3(1., 0., 0.),
                             -m_rescue_pitch*dt/rescue_time*M_PI/180.0);
        pos.setRotation(pos.getRotation()*q_roll*q_pitch);
        m_kart_body->setCenterOfMassTransform(pos);
        m_motion_state->setWorldTransform(pos);
        //printf("Set %f %f %f\n",pos.getOrigin().x(),pos.getOrigin().y(),pos.getOrigin().z());
#else
        sgZeroVec3 ( m_velocity.xyz ) ;
        sgZeroVec3 ( m_velocity.hpr ) ;
        m_velocity.xyz[2] = 1.1f * GRAVITY * dt *10.0f;
#endif        
    }   // if m_rescue
    m_attachment.update(dt, &m_velocity);

    /*smoke drawing control point*/
    if ( user_config->m_smoke )
    {
        if (m_smoke_system != NULL)
            m_smoke_system->update (dt);
    }  // user_config->smoke
    doZipperProcessing (dt) ;
    updatePhysics(dt);

    sgCopyVec2  ( m_last_track_coords, m_curr_track_coords );
    if(m_material_hot && m_on_ground)
    {
        float r=m_material_hot->getFriction();
        if(r<m_current_friction) 
        {
            m_velocity.xyz[1]-= (m_current_friction*m_current_friction-r*r)
                                *m_velocity.xyz[1];
        }   // r<m_current_friction
        m_current_friction = r;
    }   // if m_material_hot
    Moveable::update (dt);
    doObjectInteractions();


    // Save the last valid sector for forced rescue on shortcuts
    if(m_track_sector  != Track::UNKNOWN_SECTOR && 
       !m_rescue                                    ) 
    {
        m_shortcut_sector = m_track_sector;
    }

    int prev_sector = m_track_sector;
    if(!m_rescue)
        m_track_sector = world->m_track->findRoadSector(m_curr_pos.xyz);
    
    // Check if the kart is taking a shortcut (if it's not already doing one):
    if(m_shortcut_type!=SC_SKIPPED_SECTOR && !m_rescue)
    {
        if(world->m_track->isShortcut(prev_sector, m_track_sector))
        {
            // Skipped sectors are more severe then getting outside the 
            // road, so count this as two. But if the kart is already
            // outside the track, only one is added (since the outside
            // track shortcut already added 1).
            
            // This gets subtracted again when doing the rescue
            m_shortcut_count+= m_shortcut_type==SC_NONE ? 2 : 1;
            m_shortcut_type  = SC_SKIPPED_SECTOR;
            if(isPlayerKart())
            {
                forceRescue();  // bring karts back to where they left the track.
                RaceGUI* m=(RaceGUI*)menu_manager->getRaceMenu();
                // Can happen if the option menu is called
                if(m)
                    m->addMessage(_("Invalid short-cut!!"), this, 2.0f, 60);
            }
        }
    } 
    else
    {   // The kart is already doing a skipped sector --> reset
        // the flag, since from now on (it's on a new sector) it's
        // not a shortcut anymore.
        m_shortcut_type=SC_NONE;
    }

    if (m_track_sector == Track::UNKNOWN_SECTOR && !m_rescue)
    {
        m_on_road = false;
        if( m_curr_track_coords[0] > 0.0 )
            m_track_sector = world->m_track->findOutOfRoadSector(
               m_curr_pos.xyz, Track::RS_RIGHT, prev_sector );
        else
            m_track_sector = world->m_track->findOutOfRoadSector(
               m_curr_pos.xyz, Track::RS_LEFT, prev_sector );
    }
    else
    {
        m_on_road = true;
    }

    int sector = world->m_track->spatialToTrack( m_curr_track_coords, 
                                                 m_curr_pos.xyz,
                                                 m_track_sector      );
    // If the kart is more thanm_max_road_distance away from the border of
    // the track, the kart is considered taking a shortcut (but not on level
    // easy, and not while being rescued)

    if(world->m_race_setup.m_difficulty             != RD_EASY               &&
       !m_rescue                                                             &&
       m_shortcut_type                              != SC_SKIPPED_SECTOR     &&
         fabsf(m_curr_track_coords[0])-stk_config->m_max_road_distance 
         >  m_curr_track_coords[2] ) 
    {
        m_shortcut_sector = sector;
        // Increase the error count the first time this happens
        if(m_shortcut_type==SC_NONE)
            m_shortcut_count++;
        m_shortcut_type   = SC_OUTSIDE_TRACK;
    }
    else 
    {
        // Kart was taking a shortcut before, but it finished. So increase the
        // overall shortcut count.
        if(m_shortcut_type == SC_OUTSIDE_TRACK) 
            m_shortcut_type = SC_NONE;
    }
    doLapCounting () ;
    processSkidMarks();
}   // update

//-----------------------------------------------------------------------------
#define sgn(x) ((x<0)?-1.0f:((x>0)?1.0f:0.0f))

// -----------------------------------------------------------------------------
#ifdef BULLET
void Kart::draw()
{
    float m[16];
    btTransform t;
    m_motion_state->getWorldTransform(t);
    t.getOpenGLMatrix(m);

    btVector3 wire_color(0.5f, 0.5f, 0.5f);
    world->getPhysics()->debugDraw(m, m_kart_body->getCollisionShape(), 
                                   wire_color);
    btCylinderShapeX *wheelShape = 
        new btCylinderShapeX( btVector3(0.3,
                                        m_kart_properties->getWheelRadius(),
                                        m_kart_properties->getWheelRadius()) );
    btVector3 wheelColor(1,0,0);
    for(int i=0; i<m_vehicle->getNumWheels(); i++)
    {
        m_vehicle->updateWheelTransform(i, true);
        float m[16];
        m_vehicle->getWheelInfo(i).m_worldTransform.getOpenGLMatrix(m);
        world->getPhysics()->debugDraw(m, wheelShape, wheelColor);
    }
}   // draw
#endif
// -----------------------------------------------------------------------------
/** Returned an additional engine power boost when doing a wheele.
***/
#ifdef BULLET
float Kart::handleWheelie(float dt)
{
    // Handle wheelies
    // ===============
    if ( m_controls.wheelie && 
         m_speed >= getMaxSpeed()*getWheelieMaxSpeedRatio())
    {
        if ( m_wheelie_angle < getWheelieMaxPitch() )
            m_wheelie_angle += getWheeliePitchRate() * dt;
        else
            m_wheelie_angle = getWheelieMaxPitch();
    }
    else if ( m_wheelie_angle > 0.0f )
    {
        m_wheelie_angle -= getWheelieRestoreRate() * dt;
        if ( m_wheelie_angle <= 0.0f ) m_wheelie_angle = 0.0f ;
    }
    if(m_wheelie_angle <=0.0f) return 0.0f;

    const btTransform& chassisTrans = m_kart_body->getCenterOfMassTransform();
    btVector3 targetUp(0.0f, 0.0f, 1.0f);
    btVector3 forwardW (chassisTrans.getBasis()[0][1],
                        chassisTrans.getBasis()[1][1],
                        chassisTrans.getBasis()[2][1]);
    btVector3 crossProd = targetUp.cross(forwardW);
    crossProd.normalize();
    
    const float gLeanRecovery   = m_kart_properties->getWheelieLeanRecovery();
    const float step            = m_kart_properties->getWheelieStep();
    const float balance_recovery= m_kart_properties->getWheelieBalanceRecovery();
    float alpha                 = (targetUp.dot(forwardW));
    float deltaalpha            = m_wheelie_angle*M_PI/180.0f - alpha;
    btVector3 angvel            = m_kart_body->getAngularVelocity();
    float projvel               = angvel.dot(crossProd);
    float deltavel              = -projvel    * gLeanRecovery    / step
                                  -deltaalpha * balance_recovery / step;
    btVector3 deltaangvel       = deltavel * crossProd;
    
    angvel                     += deltaangvel;
    m_kart_body->setAngularVelocity(angvel);
    return m_kart_properties->getWheeliePowerBoost() * getMaxPower()
          * m_wheelie_angle/getWheelieMaxPitch();
}   // handleWheelie
#endif

// -----------------------------------------------------------------------------
void Kart::updatePhysics (float dt) 
{

#ifdef BULLET
    float engine_power = getMaxPower() + handleWheelie(dt);
    if(m_attachment.getType()==ATTACH_PARACHUTE) engine_power*=0.2;

    if(m_controls.brake)
    {
        //only apply braking force when moving forward
        if(m_speed > 0.f)
        {
            m_vehicle->applyEngineForce(-m_controls.brake*getBrakeFactor()*engine_power, 2);
            m_vehicle->applyEngineForce(-m_controls.brake*getBrakeFactor()*engine_power, 3);
        }
        //should probably not allow the kart to reverse at same velocity as forward
        else
        {
            m_vehicle->applyEngineForce(-m_controls.brake*engine_power, 2);
            m_vehicle->applyEngineForce(-m_controls.brake*engine_power, 2);
        }

    }
    else
    {   // not braking
        if(m_speed < 0.0f)   // if going backwards, accelerating is braking
        {
            m_vehicle->setBrake(getBrakeForce(), 2);
            m_vehicle->setBrake(getBrakeForce(), 3);
        }
        else
        {
            m_vehicle->applyEngineForce(m_controls.accel*engine_power, 2);
            m_vehicle->applyEngineForce(m_controls.accel*engine_power, 3);
        }

    }
    if(m_controls.jump)
    { // ignore gravity down when jumping
        // no jumping yet
    }
    const float steering = getMaxSteerAngle() * m_controls.lr * 0.00444;
    m_vehicle->setSteeringValue(steering, 0);
    m_vehicle->setSteeringValue(steering, 1);

    //store current velocity
    m_speed = getVehicle()->getRigidBody()->getLinearVelocity().length();

    //cap at maximum velocity
    const float max_speed = m_kart_properties->getMaximumSpeed();
    if ( m_speed >  max_speed )
    {
        const float velocity_ratio = max_speed/m_speed;
        m_speed                    = max_speed;
        btVector3 velocity         = m_kart_body->getLinearVelocity();

        velocity.setY( velocity.getY() * velocity_ratio );
        velocity.setX( velocity.getX() * velocity_ratio );

        getVehicle()->getRigidBody()->setLinearVelocity( velocity );

    }
    //at low velocity, forces on kart push it back and forth so we ignore this
    if(m_speed > 0.13f) {
        const btTransform& chassisTrans = getVehicle()->getChassisWorldTransform();

        btVector3 forwardW (
            chassisTrans.getBasis()[0][1],
            chassisTrans.getBasis()[1][1],
            chassisTrans.getBasis()[2][1]);

        if ( forwardW.dot(getVehicle()->getRigidBody()->getLinearVelocity()) < 0.f )
            m_speed *= -1.f;
     }
     else
         m_speed = 0;

    
#else      // ! BULLET
    m_skid_front = m_skid_rear = false;
    sgVec3 AirResistance, SysResistance;
    // Get some values once, to avoid calling them more than once.
    const float  WORLD_GRAVITY     = world->getGravity();
    const float  WHEEL_BASE   = getWheelBase();
    const float  MASS        = getMass();         // includes m_attachment.WeightAdjust
    const float  AIR_FRICTION = getAirResistance(); // includes attachmetn.AirFrictAdjust
    const float  ROLL_RESIST  = getRollResistance();

    if(m_wheelie_angle>0.0f)
    {
        m_velocity.xyz[1]-=getWheelieSpeedBoost()*m_wheelie_angle/getWheelieMaxPitch();
        if(m_velocity.xyz[1]<0) m_velocity.xyz[1]=0.0;
    }


    // Handle throttle and brakes
    // ==========================
    float throttle;

    if(m_on_ground)
    {
        if(m_controls.brake)
        {
            throttle = m_velocity.xyz[1]<0.0f ? -1.0f : -getBrakeFactor();
        }
        else
        {   // not braking
            throttle =  m_velocity.xyz[1]<0.0f ? getBrakeFactor() 
                     : m_controls.accel*m_current_friction*m_current_friction;
        }
        // Handle wheelies
        // ===============
        if ( m_controls.wheelie && m_velocity.xyz[1] >=
             getMaxSpeed()*getWheelieMaxSpeedRatio())
        {
            m_velocity.hpr[0]=0.0;
            if ( m_wheelie_angle < getWheelieMaxPitch() )
                m_wheelie_angle += getWheeliePitchRate() * dt;
            else
                m_wheelie_angle = getWheelieMaxPitch();
        }
        else if ( m_wheelie_angle > 0.0f )
        {
            m_wheelie_angle -= getWheelieRestoreRate() * dt;
            if ( m_wheelie_angle <= 0.0f ) m_wheelie_angle = 0.0f ;
        }
    }
    else
    {   // not on ground
        throttle = 0.0;
    }   // if !m_on_ground

    float ForceLong = throttle * getMaxPower();
    // apply air friction and system friction
    AirResistance[0] = 0.0f;
    AirResistance[1] = AIR_FRICTION*m_velocity.xyz[1]*fabs(m_velocity.xyz[1]);
    AirResistance[2] = 0.0f;
    SysResistance[0] = ROLL_RESIST*m_velocity.xyz[0];;
    SysResistance[1] = ROLL_RESIST*m_velocity.xyz[1];
    SysResistance[2] = 0.0f;

    //
    // Compute longitudinal acceleration for slipping
    // ----------------------------------------------
    const float FORCE_ON_REAR_TIRE = 0.5f*MASS*WORLD_GRAVITY + m_prev_accel*MASS*getHeightCOG()/WHEEL_BASE;
    const float FORCE_ON_FRONT_TIRE = MASS*WORLD_GRAVITY - FORCE_ON_REAR_TIRE;
	float maxGrip = std::max(FORCE_ON_REAR_TIRE,FORCE_ON_FRONT_TIRE)*getTireGrip();

    // If the kart is on ground, modify the grip by the friction
    // modifier for the texture/terrain.
    if(m_on_ground && m_material_hot) maxGrip *= m_material_hot->getFriction();

    // Gravity handling
    // ================
    float ForceGravity;
    if(m_on_ground)
    {
        if(m_normal_hot)
        {
            // Adjust pitch and roll according to the current terrain. To compute
            // the correspondant angles, we consider first a normalised line
            // pointing into the direction the kart is facing (pointing from (0,0,0)
            // to (x,y,0)). The angle between this line and the triangle the kart is
            // on determines the pitch. Similartly the roll is computed, using a
            // normalised line pointing to the right of the kart, for which we simply
            // use (-y,x,0).
            const float kartAngle = m_curr_pos.hpr[0]*M_PI/180.0f;
            const float X = -sin(kartAngle);
            const float Y =  cos(kartAngle);
            // Compute the angle between the normal of the plane and the line to
            // (x,y,0).  (x,y,0) is normalised, so are the coordinates of the plane,
            // simplifying the computation of the scalar product.
            float pitch = ( (*m_normal_hot)[0]*X + (*m_normal_hot)[1]*Y );  // use ( x,y,0)
            float roll  = (-(*m_normal_hot)[0]*Y + (*m_normal_hot)[1]*X );  // use (-y,x,0)

            // The actual angle computed above is between the normal and the (x,y,0)
            // line, so to compute the actual angles 90 degrees must be subtracted.
            pitch = acosf(pitch)/M_PI*180.0f-90.0f;
            roll  = acosf(roll )/M_PI*180.0f-90.0f;
            // if dt is too big, the relaxation will overshoot, and the
            // karts will either be hopping, or even turn upside down etc.
            if(dt<=0.05)
            {
# define RELAX(oldVal, newVal) (oldVal + (newVal-oldVal)*dt*20.0f)
                m_curr_pos.hpr[1] = RELAX(m_curr_pos.hpr[1],pitch);
                m_curr_pos.hpr[2] = RELAX(m_curr_pos.hpr[2],roll );
            }
            else
            {
                m_curr_pos.hpr[1] = pitch;
                m_curr_pos.hpr[2] = roll ;
            }
            if(fabsf(m_curr_pos.hpr[1])>fabsf(pitch)) m_curr_pos.hpr[1]=pitch;
            if(fabsf(m_curr_pos.hpr[2])>fabsf(roll )) m_curr_pos.hpr[2]=roll;
        }
        if(m_controls.jump)
        { // ignore gravity down when jumping
#ifdef ENABLE_JUMPING
            ForceGravity = stk_config->m_jump_impulse*WORLD_GRAVITY;
#else
	    ForceGravity = -WORLD_GRAVITY * MASS;
#endif
        }
        else
        {   // kart is on groud and not jumping
            if(user_config->m_improved_physics)
            {
                // FIXME:
                // While these physics computation is correct, it is very difficult
                // to drive, esp. the sandtrack: the grades (with the current
                // physics parameters) are too steep, so the kart needs a very high
                // initial velocity to be able to reach the top. Especially the
                // AI gets stuck very easily! Perhaps reduce the forces somewhat?
                const float PITCH_IN_RAD = m_curr_pos.hpr[1]*M_PI/180.0f;
                ForceGravity   = -WORLD_GRAVITY * MASS * cos(PITCH_IN_RAD);
                ForceLong     -=  WORLD_GRAVITY * MASS * sin(PITCH_IN_RAD);
            }
            else
            {
                ForceGravity   = -WORLD_GRAVITY * MASS;
            }
        }
    }
    else
    {  // kart is not on ground, gravity applies all to z axis.
        ForceGravity = -WORLD_GRAVITY * MASS;
    }
    m_velocity.xyz[2] += ForceGravity / MASS * dt;


    if(m_wheelie_angle <= 0.0f && m_on_ground)
    {
        // At low speed, the advanced turning mode can result in 'flickering', i.e.
        // very quick left/right movements. The reason might be:
        // 1) integration timestep to big
        // 2) the kart turning too much, thus 'oversteering', which then gets
        //    corrected (which might be caused by wrongly tuned physics parameters,
        //    or the too big timestep mentioned above
        // Since at lower speed the simple turning algorithm is good enough,
        // the advanced sliding turn algorithm is only used at higher speeds.

        // FIXME: for now, only use the simple steering algorithm.
        //        so the test for 'lower speed' is basically disabled for now,
        //        since the velocity will always be lower than 1.5*100000.0f
        if(fabsf(m_velocity.xyz[1])<150000.0f)
        {
            const float MSA       = getMaxSteerAngle();
            m_velocity.hpr[0] = m_controls.lr * ((m_velocity.xyz[1]>=0.0f) ? MSA : -MSA);
            if(m_velocity.hpr[0]> MSA) m_velocity.hpr[0] =  MSA;  // In case the AI sets
            if(m_velocity.hpr[0]<-MSA) m_velocity.hpr[0] = -MSA;  // m_controls.lr >1 or <-1
        }
        else
        {
            const float STEER_ANGLE    = m_controls.lr*getMaxSteerAngle()*M_PI/180.0f;
            const float TURN_DISTANCE   = m_velocity.hpr[0]*M_PI/180.0f * WHEEL_BASE/2.0f;
            const float SLIP_ANGLE_FRONT = atan((m_velocity.xyz[0]+TURN_DISTANCE)
                                        /fabsf(m_velocity.xyz[1]))
                                   - sgn(m_velocity.xyz[1])*STEER_ANGLE;
            const float SLIP_ANGLE_REAR  = atan((m_velocity.xyz[0]-TURN_DISTANCE)
                                        /fabsf(m_velocity.xyz[1]));
            const float FORCE_LAT_FRONT  = NormalizedLateralForce(SLIP_ANGLE_FRONT, getCornerStiffF())
                                   * FORCE_ON_FRONT_TIRE - SysResistance[0]*0.5f;
            const float FORCE_LAT_REAR   = NormalizedLateralForce(SLIP_ANGLE_REAR,  getCornerStiffR())
                                   * FORCE_ON_REAR_TIRE  - SysResistance[0]*0.5f;
            const float CORNER_FORCE    = FORCE_LAT_REAR + cos(STEER_ANGLE)*FORCE_LAT_FRONT;
            m_velocity.xyz[0]      = CORNER_FORCE/MASS*dt;
            const float TORQUE         =                  FORCE_LAT_REAR *WHEEL_BASE/2
                                                    - cos(STEER_ANGLE)*FORCE_LAT_FRONT*WHEEL_BASE/2;
            const float ANGLE_ACCELERATION         = TORQUE/getInertia();

            m_velocity.hpr[0] += ANGLE_ACCELERATION*dt*180.0f/M_PI;
        }   // fabsf(m_velocity.xyz[1])<0.5
    }   // m_wheelie_angle <=0.0f && m_on_ground

    // Longitudinal accelleration
    // ==========================
    const float EFECTIVE_FORCE  = (ForceLong-AirResistance[1]-SysResistance[1]);
    // Slipping: more force than what can be supported by the back wheels
    // --> reduce the effective force acting on the kart - currently
    //     by an arbitrary value.
    if(fabs(EFECTIVE_FORCE)>maxGrip)
    {
        //    EFECTIVE_FORCE *= 0.4f;
        //    m_skid_rear  = true;
    }   // while EFECTIVE_FORCE>maxGrip
    float ACCELERATION       = EFECTIVE_FORCE / MASS;

    m_velocity.xyz[1] += ACCELERATION           * dt;
    m_prev_accel        = ACCELERATION;

    if(m_wheelie_angle>0.0f)
    {
        m_velocity.xyz[1]+=
            getWheelieSpeedBoost()*m_wheelie_angle/getWheelieMaxPitch();
        if(m_velocity.xyz[1]<0) m_velocity.xyz[1]=0.0;
    }
#endif
}   // updatePhysics

//-----------------------------------------------------------------------------
// PHORS recommends: f=B*alpha/(1+fabs(A*alpha)^p), where A, B, and p
//                   are appropriately chosen constants.
float Kart::NormalizedLateralForce(float alpha, float corner) const
{
    float const MAX_ALPHA=3.14f/4.0f;
    if(fabsf(alpha)<MAX_ALPHA)
    {
        return corner*alpha;
    }
    else
    {
        return alpha>0.0f ? corner*MAX_ALPHA : -corner*MAX_ALPHA;
    }
}   // NormalizedLateralForce

//-----------------------------------------------------------------------------
void Kart::forceRescue()
{
    m_rescue=true;
    // If rescue is triggered while doing a shortcut, reset the kart to the
    // segment where the shortcut started!! And then reset the shortcut
    // flag, so that this shortcut is not counted!
    if(m_shortcut_type!=SC_NONE)
    {
        m_track_sector   = m_shortcut_sector;
        m_shortcut_count-= m_shortcut_type==SC_OUTSIDE_TRACK ? 1 : 2;
        m_shortcut_type  = SC_NONE;
    } 
}   // forceRescue
//-----------------------------------------------------------------------------
/** Drops a kart which was rescued back on the track.
 */
void Kart::endRescue()
{
    if ( m_track_sector > 0 ) m_track_sector-- ;
    world ->m_track -> trackToSpatial ( m_curr_pos.xyz, m_track_sector ) ;
    m_curr_pos.hpr[0] = world->m_track->m_angle[m_track_sector] ;
    m_rescue = false ;
#ifdef BULLET
    world->getPhysics()->addKart(this, m_vehicle);
    m_kart_body->setLinearVelocity (btVector3(0.0f,0.0f,0.0f));
    m_kart_body->setAngularVelocity(btVector3(0.0f,0.0f,0.0f));
    // FIXME: This code positions the kart correctly back on the track
    // (nearest waypoint) - but if the kart is simply upside down,
    // it feels better if the kart is left where it was. Perhaps
    // this code should only be used if a rescue was not triggered
    // by the kart being upside down??
    btTransform pos=m_kart_body->getCenterOfMassTransform();
    pos.setOrigin(btVector3(m_curr_pos.xyz[0],m_curr_pos.xyz[1],
                            m_curr_pos.xyz[2]+0.5*m_kart_height));
    m_kart_body->setCenterOfMassTransform(pos);
    //m_motion_state->setWorldTransform(pos);
                  
#endif

}   // endRescue

//-----------------------------------------------------------------------------
float Kart::getAirResistance() const
{
    return (m_kart_properties->getAirResistance() +
            m_attachment.AirResistanceAdjust()    )
           * stk_config->m_air_res_reduce[world->m_race_setup.m_difficulty];

}

//-----------------------------------------------------------------------------
void Kart::processSkidMarks()
{
    return;
    assert(m_skidmark_left);
    assert(m_skidmark_right);

    if(m_skid_rear || m_skid_front)
    {
        if(m_on_ground)
        {
            const float LENGTH = 0.57f;
            if(m_skidmark_left)
            {
                const float ANGLE  = -43.0f;

                sgCoord wheelpos;
                sgCopyCoord(&wheelpos, getCoord());

                wheelpos.xyz[0] += LENGTH * sgSin(wheelpos.hpr[0] + ANGLE);
                wheelpos.xyz[1] += LENGTH * -sgCos(wheelpos.hpr[0] + ANGLE);

                if(m_skidmark_left->wasSkidMarking())
                    m_skidmark_left->add(&wheelpos);
                else
                    m_skidmark_left->addBreak(&wheelpos);
            }   // if m_skidmark_left

            if(m_skidmark_right)
            {
                const float ANGLE  = 43.0f;

                sgCoord wheelpos;
                sgCopyCoord(&wheelpos, getCoord());

                wheelpos.xyz[0] += LENGTH * sgSin(wheelpos.hpr[0] + ANGLE);
                wheelpos.xyz[1] += LENGTH * -sgCos(wheelpos.hpr[0] + ANGLE);

                if(m_skidmark_right->wasSkidMarking())
                    m_skidmark_right->add(&wheelpos);
                else
                    m_skidmark_right->addBreak(&wheelpos);
            }   // if m_skidmark_right
        }
        else
        {   // not on ground
            if(m_skidmark_left)
            {
                const float LENGTH = 0.57f;
                const float ANGLE  = -43.0f;

                sgCoord wheelpos;
                sgCopyCoord(&wheelpos, getCoord());

                wheelpos.xyz[0] += LENGTH * sgSin(wheelpos.hpr[0] + ANGLE);
                wheelpos.xyz[1] += LENGTH * -sgCos(wheelpos.hpr[0] + ANGLE);

                m_skidmark_left->addBreak(&wheelpos);
            }   // if m_skidmark_left

            if(m_skidmark_right)
            {
                const float LENGTH = 0.57f;
                const float ANGLE  = 43.0f;

                sgCoord wheelpos;
                sgCopyCoord(&wheelpos, getCoord());

                wheelpos.xyz[0] += LENGTH * sgSin(wheelpos.hpr[0] + ANGLE);
                wheelpos.xyz[1] += LENGTH * -sgCos(wheelpos.hpr[0] + ANGLE);

                m_skidmark_right->addBreak(&wheelpos);
            }   // if m_skidmark_right
        }   // on ground
    }
    else
    {   // !m_skid_rear && !m_skid_front
        if(m_skidmark_left)
            if(m_skidmark_left->wasSkidMarking())
            {
                const float ANGLE  = -43.0f;
                const float LENGTH = 0.57f;

                sgCoord wheelpos;
                sgCopyCoord(&wheelpos, getCoord());

                wheelpos.xyz[0] += LENGTH * sgSin(wheelpos.hpr[0] + ANGLE);
                wheelpos.xyz[1] += LENGTH * -sgCos(wheelpos.hpr[0] + ANGLE);

                m_skidmark_left->addBreak(&wheelpos);
            }   // m_skidmark_left->wasSkidMarking

        if(m_skidmark_right)
            if(m_skidmark_right->wasSkidMarking())
            {
                const float ANGLE  = 43.0f;
                const float LENGTH = 0.57f;

                sgCoord wheelpos;
                sgCopyCoord(&wheelpos, getCoord());

                wheelpos.xyz[0] += LENGTH * sgSin(wheelpos.hpr[0] + ANGLE);
                wheelpos.xyz[1] += LENGTH * -sgCos(wheelpos.hpr[0] + ANGLE);

                m_skidmark_right->addBreak(&wheelpos);
            }   // m_skidmark_right->wasSkidMarking
    }   // m_velocity < 20
}   // processSkidMarks

//-----------------------------------------------------------------------------
void Kart::load_wheels(ssgBranch* branch)
{
    if (!branch) return;

    for(ssgEntity* i = branch->getKid(0); i != NULL; i = branch->getNextKid())
    {
        if (i->getName())
        { // We found something that might be a wheel
            if (strcmp(i->getName(), "WheelFront.L") == 0)
            {
                m_wheel_front_l = add_transform(dynamic_cast<ssgTransform*>(i));
            }
            else if (strcmp(i->getName(), "WheelFront.R") == 0)
            {
                m_wheel_front_r = add_transform(dynamic_cast<ssgTransform*>(i));
            }
            else if (strcmp(i->getName(), "WheelRear.L") == 0)
            {
                m_wheel_rear_l = add_transform(dynamic_cast<ssgTransform*>(i));
            }
            else if (strcmp(i->getName(), "WheelRear.R") == 0)
            {
                m_wheel_rear_r = add_transform(dynamic_cast<ssgTransform*>(i));
            }
            else
            {
                // Wasn't a wheel, continue searching
                load_wheels(dynamic_cast<ssgBranch*>(i));
            }
        }
        else
        { // Can't be a wheel,continue searching
            load_wheels(dynamic_cast<ssgBranch*>(i));
        }
    }   // for i
}   // load_wheels

//-----------------------------------------------------------------------------
void Kart::loadData()
{
    float r [ 2 ] = { -10.0f, 100.0f } ;

    m_smokepuff = new ssgSimpleState ();
    m_smokepuff -> setTexture        (loader->createTexture ("smoke.rgb", true, true, true)) ;
    m_smokepuff -> setTranslucent    () ;
    m_smokepuff -> enable            ( GL_TEXTURE_2D ) ;
    m_smokepuff -> setShadeModel     ( GL_SMOOTH ) ;
    m_smokepuff -> enable            ( GL_CULL_FACE ) ;
    m_smokepuff -> enable            ( GL_BLEND ) ;
    m_smokepuff -> enable            ( GL_LIGHTING ) ;
    m_smokepuff -> setColourMaterial ( GL_EMISSION ) ;
    m_smokepuff -> setMaterial       ( GL_AMBIENT, 0, 0, 0, 1 ) ;
    m_smokepuff -> setMaterial       ( GL_DIFFUSE, 0, 0, 0, 1 ) ;
    m_smokepuff -> setMaterial       ( GL_SPECULAR, 0, 0, 0, 1 ) ;
    m_smokepuff -> setShininess      (  0 ) ;

    ssgEntity *obj = m_kart_properties->getModel();
    createPhysics(obj);

    load_wheels(dynamic_cast<ssgBranch*>(obj));

    // Optimize the model, this can't be done while loading the model
    // because it seems that it removes the name of the wheels or something
    // else needed to load the wheels as a separate object.
    ssgFlatten(obj);

    createDisplayLists(obj);  // create all display lists
    ssgRangeSelector *lod = new ssgRangeSelector ;

    lod -> addKid ( obj ) ;
    lod -> setRanges ( r, 2 ) ;

    this-> getModel() -> addKid ( lod ) ;

    // Attach Particle System
    //JH  sgCoord pipe_pos = {{0, 0, .3}, {0, 0, 0}} ;
    m_smoke_system = new KartParticleSystem(this, 50, 100.0f, true, 0.35f, 1000);
    m_smoke_system -> init(5);
    //JH      m_smoke_system -> setState (getMaterial ("smoke.png")-> getState() );
    //m_smoke_system -> setState ( m_smokepuff ) ;
    //      m_exhaust_pipe = new ssgTransform (&pipe_pos);
    //      m_exhaust_pipe -> addKid (m_smoke_system) ;
    //      comp_model-> addKid (m_exhaust_pipe) ;

    m_skidmark_left  = new SkidMark();
    m_skidmark_right = new SkidMark();

    m_shadow = createShadow(m_kart_properties->getShadowFile(), -1, 1, -1, 1);
    m_shadow->ref();
    m_model->addKid ( m_shadow );
}   // loadData

//-----------------------------------------------------------------------------
void Kart::placeModel ()
{
    sgMat4 wheel_front;
    sgMat4 wheel_steer;
    sgMat4 wheel_rot;

    sgMakeRotMat4( wheel_rot, 0, -m_wheel_position, 0);
    sgMakeRotMat4( wheel_steer, getSteerAngle()/getMaxSteerAngle() * 30.0f , 0, 0);

    sgMultMat4(wheel_front, wheel_steer, wheel_rot);

    if (m_wheel_front_l) m_wheel_front_l->setTransform(wheel_front);
    if (m_wheel_front_r) m_wheel_front_r->setTransform(wheel_front);

    if (m_wheel_rear_l) m_wheel_rear_l->setTransform(wheel_rot);
    if (m_wheel_rear_r) m_wheel_rear_r->setTransform(wheel_rot);
    // We don't have to call Moveable::placeModel, since it does only setTransform

#ifdef BULLET
    btTransform t;
    if(m_rescue)
    {
             t=m_kart_body->getCenterOfMassTransform();
             //             m_motion_state->getWorldTransform(t);
    } 
    else
    {
        m_motion_state->getWorldTransform(t);
    }
    float m[4][4];
    t.getOpenGLMatrix((float*)&m);

    //printf(" is %f %f %f\n",t.getOrigin().x(),t.getOrigin().y(),t.getOrigin().z());
    // Transfer the new position and hpr to m_curr_pos
    sgSetCoord(&m_curr_pos, m);
    const btVector3 &v=m_kart_body->getLinearVelocity();
    sgSetVec3(m_velocity.xyz, v.x(), v.y(), v.z());

    sgCoord c ;
    sgCopyCoord ( &c, &m_curr_pos ) ;
    //    c.hpr[1] += m_wheelie_angle ;
    //    c.xyz[2] += 0.3f*fabs(sin(m_wheelie_angle*SG_DEGREES_TO_RADIANS));
    c.xyz[2] -= 0.5*m_kart_height;   // adjust for center of gravity
    m_model->setTransform(&c);
    
    // Check if a kart needs to be rescued.
    if(fabs(m_curr_pos.hpr[2])>60 &&
       sgLengthVec3(m_velocity.xyz)<3.0f) m_rescue=true;
#else
    sgCoord c ;
    sgCopyCoord ( &c, &m_curr_pos ) ;
    c.hpr[1] += m_wheelie_angle ;
    c.xyz[2] += 0.3f*fabs(sin(m_wheelie_angle
                              *SG_DEGREES_TO_RADIANS));
    m_model -> setTransform ( & c ) ;

#endif


}   // placeModel

//-----------------------------------------------------------------------------
void Kart::getClosestKart(float *cdist, int *closest)
{
    *cdist   = SG_MAX ;
    *closest = -1 ;

    for ( int i = 0; i < world->getNumKarts() ; ++i )
    {
        if ( world->getKart(i) == this ) continue ;
        if ( world->getKart(i)->getDistanceDownTrack() < getDistanceDownTrack() )
            continue ;

        float d = sgDistanceSquaredVec2 ( getCoord()->xyz,
                                          world->getKart(i)->getCoord()->xyz ) ;
        if ( d < *cdist && d < stk_config->m_magnet_range_sq)
        {
            *cdist = d ;
            *closest = i ;
        }
    }   // for i
}   // getClosestKart

//-----------------------------------------------------------------------------
void Kart::handleMagnet(float cdist, int closest)
{

    sgVec3 vec ;
    sgSubVec2 ( vec, world->getKart(closest)->getCoord()->xyz, getCoord()->xyz );
    vec [ 2 ] = 0.0f ;
    sgNormalizeVec3 ( vec ) ;

    sgHPRfromVec3 ( getCoord()->hpr, vec ) ;

    float tgt_velocity = world->getKart(closest)->getVelocity()->xyz[1] ;

    //JH FIXME: that should probably be changes, e.g. by increasing the throttle
    //          to something larger than 1???
    if (cdist > stk_config->m_magnet_min_range_sq)
    {
        if ( m_velocity.xyz[1] < tgt_velocity )
            m_velocity.xyz[1] = tgt_velocity * 1.4f;
    }
    else
        m_velocity.xyz[1] = tgt_velocity ;
}   // handleMagnet

//-----------------------------------------------------------------------------
void Kart::setFinishingState(float time)
{
    m_finished_race = true;
    m_finish_time   = time;
}

/* EOF */
