stk-code_catmod/src/kart.cpp

1081 lines
41 KiB
C++
Raw Normal View History

// $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 3
// 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 <math.h>
#include <iostream>
#include <plib/ssg.h>
#include "bullet/Demos/OpenGL/GL_ShapeDrawer.h"
#include "loader.hpp"
#include "coord.hpp"
#include "herring_manager.hpp"
#include "sound_manager.hpp"
#include "file_manager.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 "kart_properties_manager.hpp"
#include "gui/menu_manager.hpp"
#include "gui/race_gui.hpp"
#include "translation.hpp"
#include "smoke.hpp"
#include "material_manager.hpp"
#if defined(WIN32) && !defined(__CYGWIN__)
# define snprintf _snprintf
#endif
Kart::Kart (const std::string& kart_name, int position,
const btTransform& init_transform)
: TerrainInfo(1),
#if defined(WIN32) && !defined(__CYGWIN__)
// Disable warning for using 'this' in base member initializer list
# pragma warning(disable:4355)
#endif
Moveable(true), m_attachment(this), m_collectable(this)
#if defined(WIN32) && !defined(__CYGWIN__)
# pragma warning(1:4355)
#endif
{
m_kart_properties = kart_properties_manager->getKart(kart_name);
m_grid_position = position;
m_initial_position = position;
m_num_herrings_gobbled = 0;
m_eliminated = false;
m_finished_race = false;
m_finish_time = 0.0f;
m_wheelie_angle = 0.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;
// Set position and heading:
m_reset_transform = init_transform;
// 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:
m_max_speed = m_kart_properties->getMaximumSpeed();
m_max_speed_reverse_ratio = m_kart_properties->getMaxSpeedReverseRatio();
m_speed = 0.0f;
// Setting rescue to false is important! If rescue is set when reset() is
// called, it is assumed that this was triggered by a restart, and that
// the vehicle must be added back to the physics world. Since reset() is
// also called at the very start, it must be guaranteed that rescue is
// not set.
m_rescue = false;
m_wheel_rotation = 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();
reset();
} // Kart
// -----------------------------------------------------------------------------v
void Kart::createPhysics(ssgEntity *obj)
{
// First: Create the chassis of the kart
// -------------------------------------
float kart_width = m_kart_properties->getKartWidth();
float kart_length = m_kart_properties->getKartLength();
float kart_height = m_kart_properties->getKartHeight();
btBoxShape *shape = new btBoxShape(btVector3(0.5f*kart_width,
0.5f*kart_length,
0.5f*kart_height));
btTransform shiftCenterOfGravity;
shiftCenterOfGravity.setIdentity();
// Shift center of gravity downwards, so that the kart
// won't topple over too easy. This must be between 0 and 0.5
// (it's in units of kart_height)
const float CENTER_SHIFT = getGravityCenterShift();
shiftCenterOfGravity.setOrigin(btVector3(0.0f,0.0f,CENTER_SHIFT*kart_height));
m_kart_chassis.addChildShape(shiftCenterOfGravity, shape);
// Set mass and inertia
// --------------------
float mass=getMass();
// Position the chassis
// --------------------
btTransform trans;
trans.setIdentity();
createBody(mass, trans, &m_kart_chassis);
m_user_pointer.set(this);
m_body->setDamping(m_kart_properties->getChassisLinearDamping(),
m_kart_properties->getChassisAngularDamping() );
// Reset velocities
// ----------------
m_body->setLinearVelocity (btVector3(0.0f,0.0f,0.0f));
m_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_body, m_vehicle_raycaster);
// never deactivate the vehicle
m_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 = m_kart_properties->getSuspensionRest();
float connection_height = -(0.5f-CENTER_SHIFT)*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.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: */ false);
// right rear 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: */ 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();
}
// Obviously these allocs have to be properly managed/freed
btTransform t;
t.setIdentity();
m_uprightConstraint=new btUprightConstraint(*m_body, t);
m_uprightConstraint->setLimit(m_kart_properties->getUprightTolerance());
m_uprightConstraint->setBounce(0.0f);
m_uprightConstraint->setMaxLimitForce(m_kart_properties->getUprightMaxForce());
m_uprightConstraint->setErp(1.0f);
m_uprightConstraint->setLimitSoftness(1.0f);
m_uprightConstraint->setDamping(0.0f);
world->getPhysics()->addKart(this, m_vehicle);
} // createPhysics
// -----------------------------------------------------------------------------
Kart::~Kart()
{
if(m_smokepuff) delete m_smokepuff;
if(m_smoke_system != NULL) delete m_smoke_system;
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;
world->getPhysics()->removeKart(this);
delete m_vehicle;
delete m_tuning;
delete m_vehicle_raycaster;
delete m_uprightConstraint;
for(int i=0; i<m_kart_chassis.getNumChildShapes(); i++)
{
delete m_kart_chassis.getChildShape(i);
}
} // ~Kart
//-----------------------------------------------------------------------------
void Kart::eliminate()
{
m_eliminated = true;
world->getPhysics()->removeKart(this);
// make the kart invisible by placing it way under the track
sgVec3 hell; hell[0]=0.0f; hell[1]=0.0f; hell[2] = -10000.0f;
getModelTransform()->setTransform(hell);
} // eliminate
//-----------------------------------------------------------------------------
/** Returns true if the kart is 'resting'
*
* Returns true if the kart is 'resting', i.e. (nearly) not moving.
*/
bool Kart::isInRest() const
{
return fabs(m_body->getLinearVelocity ().z())<0.2;
} // isInRest
//-----------------------------------------------------------------------------
/** 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)
{
m_body->setLinearVelocity(m_body->getLinearVelocity()*f);
// getMass returns the mass increased by the attachment
btVector3 inertia;
float m=getMass();
m_kart_chassis.calculateLocalInertia(m, inertia);
m_body->setMassProps(m, inertia);
} // adjustSpeedWeight
//-----------------------------------------------------------------------------
void Kart::reset()
{
if(m_eliminated)
{
world->getPhysics()->addKart(this, m_vehicle);
}
m_attachment.clear();
m_collectable.reset();
m_race_lap = -1;
m_lap_start_time = -1.0f;
m_time_at_last_lap = 99999.9f;
m_shortcut_sector = Track::UNKNOWN_SECTOR;
m_race_position = 9;
m_finished_race = false;
m_eliminated = false;
m_finish_time = 0.0f;
m_zipper_time_left = 0.0f;
m_num_herrings_gobbled = 0;
m_wheel_rotation = 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;
// Set the brakes so that karts don't slide downhill
for(int i=0; i<4; i++) m_vehicle->setBrake(5.0f, i);
m_transform = m_reset_transform;
world->m_track->findRoadSector(getXYZ(), &m_track_sector);
//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(
getXYZ(), Track::RS_DONT_KNOW, Track::UNKNOWN_SECTOR );
}
else
{
m_on_road = true;
}
world->m_track->spatialToTrack(m_curr_track_coords, getXYZ(),
m_track_sector );
m_vehicle->applyEngineForce (0.0f, 2);
m_vehicle->applyEngineForce (0.0f, 3);
Moveable::reset();
for(int j=0; j<m_vehicle->getNumWheels(); j++)
{
m_vehicle->updateWheelTransform(j, true);
}
// if the kart was being rescued when a restart is called,
// add the vehicle back into the physical world!
if(m_rescue)
{
world->getPhysics()->addKart(this, m_vehicle);
}
m_rescue = false;
TerrainInfo::update(getXYZ());
} // reset
//-----------------------------------------------------------------------------
void Kart::doLapCounting ()
{
bool newLap = m_last_track_coords[1] > 300.0f && m_curr_track_coords.getY() < 20.0f;
if ( newLap )
{
// 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<=race_manager->getNumLaps())
{
setTimeAtLap(world->getTime());
m_race_lap++ ;
}
// Race finished
// =============
if(m_race_lap>=race_manager->getNumLaps())
{
raceFinished(world->getTime());
}
// 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 -1.
if(m_lap_start_time>=0.0)
{
float time_per_lap;
if (m_race_lap == 1) // just completed first lap
{
time_per_lap=world->getTime();
}
else //completing subsequent laps
{
time_per_lap=world->getTime()-m_lap_start_time;
}
if(time_per_lap < world->getFastestLapTime() &&
race_manager->raceHasLaps())
{
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->getTime();
}
else if ( m_curr_track_coords.getY() > 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;
} else
{ // Switch to fast music in case of follow the leader when only 3 karts are left
if(race_manager->getRaceMode()==RaceManager::RM_FOLLOW_LEADER &&
world->getCurrentNumKarts()==3)
{
sound_manager->switchToFastMusic();
}
}
} // doLapCounting
//-----------------------------------------------------------------------------
void Kart::raceFinished(float time)
{
m_finished_race = true;
m_finish_time = time;
race_manager->RaceFinished(this, time);
} // raceFinished
//-----------------------------------------------------------------------------
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
//-----------------------------------------------------------------------------
// Simulates gears
float Kart::getActualWheelForce()
{
float zipperF=(m_zipper_time_left>0.0f) ? stk_config->m_zipper_force : 0.0f;
const std::vector<float>& gear_ratio=m_kart_properties->getGearSwitchRatio();
for(unsigned int i=0; i<gear_ratio.size(); i++)
{
if(m_speed <= m_max_speed*gear_ratio[i])
return getMaxPower()*m_kart_properties->getGearPowerIncrease()[i]+zipperF;
}
return getMaxPower()+zipperF;
} // getActualWheelForce
//-----------------------------------------------------------------------------
/** The kart is on ground if all 4 wheels touch the ground
*/
bool Kart::isOnGround() const
{
return m_vehicle->getWheelInfo(0).m_raycastInfo.m_isInContact &&
m_vehicle->getWheelInfo(1).m_raycastInfo.m_isInContact &&
m_vehicle->getWheelInfo(2).m_raycastInfo.m_isInContact &&
m_vehicle->getWheelInfo(3).m_raycastInfo.m_isInContact;
} // isOnGround
//-----------------------------------------------------------------------------
void Kart::handleExplosion(const Vec3& pos, bool direct_hit)
{
if(direct_hit)
{
btVector3 diff((float)(rand()%16/16), (float)(rand()%16/16), 2.0f);
diff.normalize();
diff*=stk_config->m_explosion_impulse/5.0f;
this->m_uprightConstraint->setDisableTime(10.0f);
getVehicle()->getRigidBody()->applyCentralImpulse(diff);
getVehicle()->getRigidBody()->applyTorqueImpulse(btVector3(float(rand()%32*5),
float(rand()%32*5),
float(rand()%32*5)));
}
else // only affected by a distant explosion
{
btVector3 diff=getXYZ()-pos;
//if the z component is negative, the resulting impulse could push the
// kart through the floor. So in this case ignore z.
if(diff.getZ()<0) diff.setZ(0.0f);
float len2=diff.length2();
// The correct formhale would be to first normalise diff,
// then apply the impulse (which decreases 1/r^2 depending
// on the distance r), so:
// diff/len(diff) * impulseSize/len(diff)^2
// = diff*impulseSize/len(diff)^3
// We use diff*impulseSize/len(diff)^2 here, this makes the impulse
// somewhat larger, which is actually more fun :)
diff *= stk_config->m_explosion_impulse/len2;
getVehicle()->getRigidBody()->applyCentralImpulse(diff);
}
} // handleExplosion
//-----------------------------------------------------------------------------
void Kart::update(float dt)
{
m_zipper_time_left = m_zipper_time_left>0.0f ? m_zipper_time_left-dt : 0.0f;
//m_wheel_rotation 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_rotation += m_speed*dt / m_kart_properties->getWheelRadius();
m_wheel_rotation=fmodf(m_wheel_rotation, 2*M_PI);
if ( m_rescue )
{
// Let the kart raise 2m in the 2 seconds of the rescue
const float rescue_time = 2.0f;
const float rescue_height = 2.0f;
if(m_attachment.getType() != ATTACH_TINYTUX)
{
if(isPlayerKart()) sound_manager -> playSfx ( SOUND_BZZT );
m_attachment.set( ATTACH_TINYTUX, rescue_time ) ;
m_rescue_pitch = getHPR().getPitch();
m_rescue_roll = getHPR().getRoll();
world->getPhysics()->removeKart(this);
}
btQuaternion q_roll (btVector3(0.f, 1.f, 0.f),
-m_rescue_roll*dt/rescue_time*M_PI/180.0f);
btQuaternion q_pitch(btVector3(1.f, 0.f, 0.f),
-m_rescue_pitch*dt/rescue_time*M_PI/180.0f);
setXYZRotation(getXYZ()+Vec3(0, 0, rescue_height*dt/rescue_time),
getRotation()*q_roll*q_pitch);
} // if m_rescue
m_attachment.update(dt);
/*smoke drawing control point*/
if ( user_config->m_smoke )
{
if (m_smoke_system != NULL)
m_smoke_system->update (dt);
} // user_config->smoke
updatePhysics(dt);
m_last_track_coords = m_curr_track_coords;
Moveable::update(dt);
// Check if a kart is (nearly) upside down and not moving much --> automatic rescue
if((fabs(getHPR().getRoll())>60 && fabs(getSpeed())<3.0f) )
{
forceRescue();
}
btTransform trans=getTrans();
// Add a certain epsilon (0.2) to the height of the kart. This avoids
// problems of the ray being cast from under the track (which happened
// e.g. on tux tollway when jumping down from the ramp).
// FIXME: this should be more thoroughly fixed, the constant is probably
// dependent on the wheel size, suspension data etc.: when jumping,
// the wheel suspension will be fully compressed, resulting in the
// ray to start too low (under the track).
Vec3 pos_plus_epsilon = trans.getOrigin()+btVector3(0,0,0.2f);
//btVector3 pos_plus_epsilon (-56.6874237, -137.48851, -3.06826854);
TerrainInfo::update(pos_plus_epsilon);
const Material* material=getMaterial();
if (getHoT()==Track::NOHIT) // kart falling off the track
{
forceRescue();
}
else if(material)
{
// Sometimes the material can be 0. This can happen if a kart is above
// another kart (e.g. mass collision, or one kart falling on another
// kart). Bullet does not have any triangle information in this case,
// and so material can not be set. In this case it is simply ignored
// since it can't hurt (material is only used for friction, zipper and
// rescue, so those things are not triggered till the kart is on the
// track again)
if (material->isReset() && isOnGround()) forceRescue();
else if(material->isZipper() && isOnGround()) handleZipper();
else if(user_config->m_skidding) // set friction otherwise if it's enabled
{
for(int i=0; i<m_vehicle->getNumWheels(); i++)
{
// terrain dependent friction
m_vehicle->getWheelInfo(i).m_frictionSlip =
getFrictionSlip() * material->getFriction();
} // for i<getNumWheels
} // neither reset nor zipper material
} // if there is material
// Check if any herring was hit.
herring_manager->hitHerring(this);
// 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)
world->m_track->findRoadSector(getXYZ(), &m_track_sector);
// Check if the kart is taking a shortcut (if it's not already doing one):
if(!m_rescue && world->m_track->isShortcut(prev_sector, m_track_sector))
{
forceRescue(/*is rescue*/ true); // bring karts back to where they left the track.
if(isPlayerKart())
{
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);
}
}
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(
getXYZ(), Track::RS_RIGHT, prev_sector );
else
m_track_sector = world->m_track->findOutOfRoadSector(
getXYZ(), Track::RS_LEFT, prev_sector );
}
else
{
m_on_road = true;
}
world->m_track->spatialToTrack( m_curr_track_coords,
getXYZ(),
m_track_sector );
if(!m_finished_race) doLapCounting();
processSkidMarks();
} // update
//-----------------------------------------------------------------------------
// Set zipper time, and apply one time additional speed boost
void Kart::handleZipper()
{
// Ignore a zipper that's activated while braking
if(m_controls.brake) return;
m_zipper_time_left = stk_config->m_zipper_time;
const btVector3& v = m_body->getLinearVelocity();
float current_speed = v.length();
float speed = std::min(current_speed+stk_config->m_zipper_speed_gain,
getMaxSpeed());
// Avoid NAN problems, which can happen if e.g. a kart is rescued on
// top of zipper, and then dropped.
if(current_speed>0.00001) m_body->setLinearVelocity(v*(speed/current_speed));
} // handleZipper
//-----------------------------------------------------------------------------
#define sgn(x) ((x<0)?-1.0f:((x>0)?1.0f:0.0f))
// -----------------------------------------------------------------------------
void Kart::draw()
{
float m[16];
btTransform t=getTrans();
t.getOpenGLMatrix(m);
btVector3 wire_color(0.5f, 0.5f, 0.5f);
world->getPhysics()->debugDraw(m, m_body->getCollisionShape(),
wire_color);
btCylinderShapeX wheelShape( btVector3(0.3f,
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
// -----------------------------------------------------------------------------
/** Returned an additional engine power boost when doing a wheele.
***/
float Kart::handleWheelie(float dt)
{
// Handle wheelies
// ===============
if ( m_controls.wheelie &&
m_speed >= getMaxSpeed()*getWheelieMaxSpeedRatio())
{
// Disable the upright constraint, since it will otherwise
// work against the wheelie
m_uprightConstraint->setLimit(M_PI);
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)
{
m_uprightConstraint->setLimit(m_kart_properties->getUprightTolerance());
return 0.0f;
}
const btTransform& chassisTrans = getTrans();
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();
return m_kart_properties->getWheeliePowerBoost() * getMaxPower()
* m_wheelie_angle/getWheelieMaxPitch();
} // handleWheelie
// -----------------------------------------------------------------------------
/** This function is called when the race starts. Up to then all brakes are
braking (to avoid the kart from rolling downhill), but they need to be set
to zero (otherwise the brakes will be braking whenever no engine force
is set, i.e. the kart is not accelerating).
*/
void Kart::resetBrakes()
{
for(int i=0; i<4; i++) m_vehicle->setBrake(0.0f, i);
} // resetBrakes
// -----------------------------------------------------------------------------
void Kart::updatePhysics (float dt)
{
float engine_power = getActualWheelForce() + handleWheelie(dt);
if(m_attachment.getType()==ATTACH_PARACHUTE) engine_power*=0.2f;
if(m_controls.accel)
{ // accelerating
m_vehicle->applyEngineForce(engine_power, 2);
m_vehicle->applyEngineForce(engine_power, 3);
}
else
{ // not accelerating
if(m_controls.brake)
{ // braking or moving backwards
if(m_speed > 0.f)
{ // going forward, apply brake force
m_vehicle->applyEngineForce(-getBrakeFactor()*engine_power, 2);
m_vehicle->applyEngineForce(-getBrakeFactor()*engine_power, 3);
}
else
{ // going backward, apply reverse gear ratio
if ( fabs(m_speed) < m_max_speed*m_max_speed_reverse_ratio )
{
m_vehicle->applyEngineForce(-engine_power*m_controls.brake, 2);
m_vehicle->applyEngineForce(-engine_power*m_controls.brake, 3);
}
else
{
m_vehicle->applyEngineForce(0.f, 2);
m_vehicle->applyEngineForce(0.f, 3);
}
}
}
else
{ // lift the foot from throttle, brakes with 10% engine_power
m_vehicle->applyEngineForce(-m_controls.accel*engine_power*0.1f, 2);
m_vehicle->applyEngineForce(-m_controls.accel*engine_power*0.1f, 3);
}
}
if(isOnGround() && m_controls.jump)
{
//Vector3 impulse(0.0f, 0.0f, 10.0f);
// getVehicle()->getRigidBody()->applyCentralImpulse(impulse);
btVector3 velocity = m_body->getLinearVelocity();
velocity.setZ( m_kart_properties->getJumpVelocity() );
getBody()->setLinearVelocity( velocity );
}
if(m_wheelie_angle<=0.0f)
{
const float steering = DEGREE_TO_RAD(getMaxSteerAngle()) * m_controls.lr;
m_vehicle->setSteeringValue(steering, 0);
m_vehicle->setSteeringValue(steering, 1);
}
else
{
m_vehicle->setSteeringValue(0.0f, 0);
m_vehicle->setSteeringValue(0.0f, 1);
}
//store current velocity
m_speed = getVehicle()->getRigidBody()->getLinearVelocity().length();
// calculate direction of m_speed
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()) < btScalar(0.))
m_speed *= -1.f;
//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_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(fabsf(m_speed) < 0.2f) // quick'n'dirty workaround for bug 1776883
m_speed = 0;
} // updatePhysics
//-----------------------------------------------------------------------------
void Kart::forceRescue(bool is_shortcut)
{
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(is_shortcut)
{
m_track_sector = m_shortcut_sector;
}
} // forceRescue
//-----------------------------------------------------------------------------
/** Drops a kart which was rescued back on the track.
*/
void Kart::endRescue()
{
if ( m_track_sector > 0 ) m_track_sector-- ;
setXYZ( world->m_track->trackToSpatial(m_track_sector) );
btQuaternion heading(btVector3(0.0f, 0.0f, 1.0f),
DEGREE_TO_RAD(world->m_track->m_angle[m_track_sector]) );
setRotation(heading);
m_rescue = false ;
m_body->setLinearVelocity (btVector3(0.0f,0.0f,0.0f));
m_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;
// A certain epsilon is added here to the Z coordinate (0.1), in case
// that the drivelines are somewhat under the track. Otherwise, the
// kart will be placed a little bit under the track, triggering
// a rescue, ...
pos.setOrigin(getXYZ()+btVector3(0, 0, 0.5f*getKartHeight()+0.1f));
pos.setRotation(btQuaternion(btVector3(0.0f, 0.0f, 1.0f),
DEGREE_TO_RAD(world->m_track->m_angle[m_track_sector])));
m_body->setCenterOfMassTransform(pos);
world->getPhysics()->addKart(this, m_vehicle);
setTrans(pos);
} // endRescue
//-----------------------------------------------------------------------------
void Kart::processSkidMarks()
{
// FIXME: disable skidmarks for now, they currently look ugly, and are
// sometimes hovering in the air
return;
assert(m_skidmark_left);
assert(m_skidmark_right);
const float threshold = 0.3f;
const float ANGLE = 43.0f;
const float LENGTH = 0.57f;
bool skid_front = m_vehicle->getWheelInfo(0).m_skidInfo < threshold ||
m_vehicle->getWheelInfo(1).m_skidInfo < threshold;
bool skid_rear = m_vehicle->getWheelInfo(2).m_skidInfo < threshold ||
m_vehicle->getWheelInfo(3).m_skidInfo < threshold;
if(skid_rear || skid_front)
{
if(isOnGround())
{
//FIXME: no getCoord anymore m_skidmark_left ->add(*getCoord(), ANGLE, LENGTH);
//FIXME m_skidmark_right->add(*getCoord(), ANGLE, LENGTH);
}
else
{ // not on ground
//FIXME m_skidmark_left->addBreak(*getCoord(), ANGLE, LENGTH);
//FIRME m_skidmark_right->addBreak(*getCoord(), ANGLE, LENGTH);
} // on ground
}
else
{ // !skid_rear && !skid_front
//FIXME if(m_skidmark_left->wasSkidMarking())
//FIXME m_skidmark_left->addBreak(*getCoord(), ANGLE, LENGTH);
//FIXME if(m_skidmark_right->wasSkidMarking())
//FIXME m_skidmark_right->addBreak(*getCoord(), ANGLE, LENGTH);
}
} // 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(material_manager->getMaterial("smoke.rgb")->getState()->getTexture());
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-> getModelTransform() -> addKid ( lod ) ;
// Attach Particle System
//JH sgCoord pipe_pos = {{0, 0, .3}, {0, 0, 0}} ;
m_smoke_system = new Smoke(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(/* angle sign */ -1);
m_skidmark_right = new SkidMark(/* angle sign */ 1);
m_shadow = createShadow(m_kart_properties->getShadowFile(), -1, 1, -1, 1);
m_shadow->ref();
m_model_transform->addKid ( m_shadow );
} // loadData
//-----------------------------------------------------------------------------
void Kart::updateGraphics(const Vec3& off_xyz, const Vec3& off_hpr)
{
sgMat4 wheel_front;
sgMat4 wheel_steer;
sgMat4 wheel_rot;
sgMakeRotMat4( wheel_rot, 0, RAD_TO_DEGREE(-m_wheel_rotation), 0);
sgMakeRotMat4( wheel_steer, m_controls.lr * 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);
const float CENTER_SHIFT = getGravityCenterShift();
const float offset_pitch = DEGREE_TO_RAD(m_wheelie_angle);
const float offset_z = 0.3f*fabs(sin(m_wheelie_angle*SG_DEGREES_TO_RADIANS))
- (0.5f-CENTER_SHIFT)*getKartHeight();
Moveable::updateGraphics(Vec3(0, 0, offset_z), Vec3(0, offset_pitch, 0));
} // updateGraphics
//-----------------------------------------------------------------------------
float Kart::estimateFinishTime ()
{
// Estimate the arrival time of any karts that haven't arrived
// yet by using their average speed up to now and the distance
// still to race. This approach guarantees that the order of
// the karts won't change anymore (karts ahead will have a
// higher average speed and therefore finish the race earlier
// than karts further behind), so the position doesn't have to
// be updated to get the correct scoring.
float distance_covered = getLap()*world->m_track->getTrackLength()
+ getDistanceDownTrack();
// In case that a kart is rescued behind start line, or ...
if(distance_covered<0) distance_covered =1.0f;
float average_speed = distance_covered/world->getTime();
// Finish time is the time needed for the whole race with
// the average speed computed above.
return race_manager->getNumLaps()*world->getTrack()->getTrackLength()
/ average_speed;
} // estimateFinishTime
/* EOF */